├── README.md
├── chapter-01
├── files
│ └── Figure1-2.png
└── notebook.ipynb
├── chapter-02
└── notebook.ipynb
├── chapter-03
├── notebook.ipynb
└── zen.txt
├── chapter-04
├── images
│ └── example_encoding.jpg
├── notebook.ipynb
└── utils
│ ├── check_terminal.py
│ └── default_encodings.py
├── chapter-05
└── notebook.ipynb
├── chapter-06
└── notebook.ipynb
├── chapter-07
└── notebook.ipynb
├── chapter-08
└── notebook.ipynb
├── chapter-09
└── notebook.ipynb
├── chapter-10
└── notebook.ipynb
├── chapter-11
└── notebook.ipynb
├── chapter-12
└── notebook.ipynb
├── chapter-13
└── notebook.ipynb
└── chapter-14
└── notebook.ipynb
/README.md:
--------------------------------------------------------------------------------
1 | **Python Book Reading in Farsi: A Weekly Journey**
2 |
3 | _Welcome to PyHints' Python book reading series, where we dive into the world of Python!_
4 |
5 | **About this repository**
6 |
7 | This repository contains the source code and materials used in our weekly YouTube video series "Reading Fluent Python in Farsi". In each episode, we discuss a chapter from the book "Fluent Python" by Luciano Ramalho, with a focus on how to apply these concepts to real-world problems.
8 |
9 | **Getting started**
10 |
11 | To follow along with our videos, simply clone this repository and run the code. You can also join our Telegram community at [@Pyhints](https://t.me/pyhints) for discussions, Q&A sessions, and updates on new episodes!
12 |
13 | **What you'll find in this repository**
14 |
15 | - Python code examples and exercises from each episode
16 | - Materials used in the video recordings (e.g., notes, diagrams)
17 | - Links to relevant videos on our YouTube channel
18 |
19 | **Subscribe to our YouTube channel**
20 |
21 | To stay up-to-date with our latest episodes, please subscribe to our YouTube channel at [Pyhints Youtube](https://youtube.com/@pyhints). New episodes are released every week right after live sessions.
22 |
23 | **Join the conversation**
24 |
25 | We'd love to hear from you! Share your thoughts, ask questions, and connect with fellow Python enthusiasts in our Telegram community.
26 |
27 | Happy coding, and see you in the next episode!
28 |
29 | Best regards,
30 | The PyHints team
31 |
--------------------------------------------------------------------------------
/chapter-01/files/Figure1-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pyhints/fluent_python/0fd488a3d157ae9372864a675948766e29239a5c/chapter-01/files/Figure1-2.png
--------------------------------------------------------------------------------
/chapter-01/notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Chapter 1 — The Python Data Model\n",
8 | "\n",
9 | "**Sections with code snippets in this chapter:**\n",
10 | "\n",
11 | "- [A Pythonic Card Deck](#A-Pythonic-Card-Deck)\n",
12 | "- [Emulating Numeric Types](#Emulating-Numeric-Types)\n"
13 | ]
14 | },
15 | {
16 | "cell_type": "markdown",
17 | "metadata": {},
18 | "source": [
19 | "### What is a data model?\n",
20 | "\n",
21 | "- A description of Python as a framework. It formalizes the interfaces of the building blocks of the language itself such as functions, classes and etc.\n",
22 | "\n",
23 | "### Special methods\n",
24 | "\n",
25 | "- Written with leading and trailing double underscores.\n",
26 | "- The Python interpreter invokes special methods to perform basic object operations, often triggered by special syntax. For example: `obj[key] => obj.__getitem__(key)`\n",
27 | "- They are called by the interpreter\n",
28 | "- When dealing with built-in types like list, str,... (Python variable-sized collections written in C) these objects include a struct called `PyVarObject` which has an `ob_size` field that holding number of items. So when we call `len()` return value of the `ob_size` and this is much faster than calling a method.\n",
29 | "- if you need to invoke a special method, it is usually better to call the related built-in function (e.g., len, iter, str, etc.). These built-ins call the corresponding special method, but often provide other services and—for built-in types—are faster than method calls.\n"
30 | ]
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "metadata": {},
35 | "source": [
36 | "### Why do we still use \"my_fmt.format()\"?\n",
37 | "\n",
38 | "- When we need to define my_fmt in a different place (it has multiple lines or must come from a config file, or from database).\n"
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "metadata": {},
44 | "source": [
45 | "## A Pythonic Card Deck\n"
46 | ]
47 | },
48 | {
49 | "cell_type": "markdown",
50 | "metadata": {},
51 | "source": [
52 | "#### Example 1-1. A deck as a sequence of playing cards\n"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": 1,
58 | "metadata": {
59 | "ExecuteTime": {
60 | "end_time": "2023-10-06T10:30:37.890865700Z",
61 | "start_time": "2023-10-06T10:30:37.854777600Z"
62 | }
63 | },
64 | "outputs": [],
65 | "source": [
66 | "import collections\n",
67 | "\n",
68 | "# namedtuple bundles of attributes with no custom methods.\n",
69 | "Card = collections.namedtuple('Card', ['rank', 'suit'])\n",
70 | "\n",
71 | "class FrenchDeck:\n",
72 | " # tuple use less memory than list\n",
73 | " # list is mutable, so may change in futures\n",
74 | " ranks = [str(n) for n in range(2, 11)] + list('JQKA')\n",
75 | " suits = 'spades diamonds clubs hearts'.split()\n",
76 | "\n",
77 | " def __init__(self):\n",
78 | " self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]\n",
79 | "\n",
80 | " def __len__(self):\n",
81 | " return len(self._cards)\n",
82 | "\n",
83 | " def __getitem__(self, position):\n",
84 | " return self._cards[position]"
85 | ]
86 | },
87 | {
88 | "cell_type": "code",
89 | "execution_count": 2,
90 | "metadata": {
91 | "ExecuteTime": {
92 | "end_time": "2023-10-06T10:30:41.532312100Z",
93 | "start_time": "2023-10-06T10:30:41.448283300Z"
94 | }
95 | },
96 | "outputs": [
97 | {
98 | "data": {
99 | "text/plain": [
100 | "Card(rank='7', suit='diamonds')"
101 | ]
102 | },
103 | "execution_count": 2,
104 | "metadata": {},
105 | "output_type": "execute_result"
106 | }
107 | ],
108 | "source": [
109 | "beer_card = Card('7', 'diamonds')\n",
110 | "beer_card"
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": 3,
116 | "metadata": {
117 | "ExecuteTime": {
118 | "end_time": "2023-10-06T10:30:43.506197200Z",
119 | "start_time": "2023-10-06T10:30:43.444157300Z"
120 | }
121 | },
122 | "outputs": [
123 | {
124 | "data": {
125 | "text/plain": [
126 | "52"
127 | ]
128 | },
129 | "execution_count": 3,
130 | "metadata": {},
131 | "output_type": "execute_result"
132 | }
133 | ],
134 | "source": [
135 | "deck = FrenchDeck()\n",
136 | "len(deck) # call __len__"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": 11,
142 | "metadata": {
143 | "ExecuteTime": {
144 | "end_time": "2023-09-29T10:42:19.479656700Z",
145 | "start_time": "2023-09-29T10:42:19.126701600Z"
146 | }
147 | },
148 | "outputs": [
149 | {
150 | "data": {
151 | "text/plain": [
152 | "Card(rank='2', suit='spades')"
153 | ]
154 | },
155 | "execution_count": 11,
156 | "metadata": {},
157 | "output_type": "execute_result"
158 | }
159 | ],
160 | "source": [
161 | "deck[0] # call __getitem__ for slicing"
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": 4,
167 | "metadata": {
168 | "ExecuteTime": {
169 | "end_time": "2023-10-06T10:30:46.464849800Z",
170 | "start_time": "2023-10-06T10:30:46.384848600Z"
171 | }
172 | },
173 | "outputs": [
174 | {
175 | "data": {
176 | "text/plain": [
177 | "Card(rank='A', suit='hearts')"
178 | ]
179 | },
180 | "execution_count": 4,
181 | "metadata": {},
182 | "output_type": "execute_result"
183 | }
184 | ],
185 | "source": [
186 | "deck[-1]"
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": 13,
192 | "metadata": {
193 | "ExecuteTime": {
194 | "end_time": "2023-09-29T10:42:19.482657300Z",
195 | "start_time": "2023-09-29T10:42:19.159700Z"
196 | }
197 | },
198 | "outputs": [
199 | {
200 | "data": {
201 | "text/plain": [
202 | "Card(rank='8', suit='clubs')"
203 | ]
204 | },
205 | "execution_count": 13,
206 | "metadata": {},
207 | "output_type": "execute_result"
208 | }
209 | ],
210 | "source": [
211 | "# NBVAL_IGNORE_OUTPUT\n",
212 | "from random import choice\n",
213 | "\n",
214 | "choice(deck)"
215 | ]
216 | },
217 | {
218 | "cell_type": "code",
219 | "execution_count": 14,
220 | "metadata": {
221 | "ExecuteTime": {
222 | "end_time": "2023-09-29T10:42:19.483656900Z",
223 | "start_time": "2023-09-29T10:42:19.173714300Z"
224 | }
225 | },
226 | "outputs": [
227 | {
228 | "data": {
229 | "text/plain": [
230 | "[Card(rank='2', suit='spades'),\n",
231 | " Card(rank='3', suit='spades'),\n",
232 | " Card(rank='4', suit='spades')]"
233 | ]
234 | },
235 | "execution_count": 14,
236 | "metadata": {},
237 | "output_type": "execute_result"
238 | }
239 | ],
240 | "source": [
241 | "deck[:3]"
242 | ]
243 | },
244 | {
245 | "cell_type": "code",
246 | "execution_count": 15,
247 | "metadata": {
248 | "ExecuteTime": {
249 | "end_time": "2023-09-29T10:42:19.486654700Z",
250 | "start_time": "2023-09-29T10:42:19.190700Z"
251 | }
252 | },
253 | "outputs": [
254 | {
255 | "data": {
256 | "text/plain": [
257 | "[Card(rank='A', suit='spades'),\n",
258 | " Card(rank='A', suit='diamonds'),\n",
259 | " Card(rank='A', suit='clubs'),\n",
260 | " Card(rank='A', suit='hearts')]"
261 | ]
262 | },
263 | "execution_count": 15,
264 | "metadata": {},
265 | "output_type": "execute_result"
266 | }
267 | ],
268 | "source": [
269 | "deck[12::13]"
270 | ]
271 | },
272 | {
273 | "cell_type": "code",
274 | "execution_count": 16,
275 | "metadata": {
276 | "ExecuteTime": {
277 | "end_time": "2023-09-29T10:42:19.486654700Z",
278 | "start_time": "2023-09-29T10:42:19.206709300Z"
279 | }
280 | },
281 | "outputs": [
282 | {
283 | "name": "stdout",
284 | "output_type": "stream",
285 | "text": [
286 | "Card(rank='2', suit='spades')\n",
287 | "Card(rank='3', suit='spades')\n",
288 | "Card(rank='4', suit='spades')\n",
289 | "Card(rank='5', suit='spades')\n",
290 | "Card(rank='6', suit='spades')\n",
291 | "Card(rank='7', suit='spades')\n",
292 | "Card(rank='8', suit='spades')\n",
293 | "Card(rank='9', suit='spades')\n",
294 | "Card(rank='10', suit='spades')\n",
295 | "Card(rank='J', suit='spades')\n",
296 | "Card(rank='Q', suit='spades')\n",
297 | "Card(rank='K', suit='spades')\n",
298 | "Card(rank='A', suit='spades')\n",
299 | "Card(rank='2', suit='diamonds')\n",
300 | "Card(rank='3', suit='diamonds')\n",
301 | "Card(rank='4', suit='diamonds')\n",
302 | "Card(rank='5', suit='diamonds')\n",
303 | "Card(rank='6', suit='diamonds')\n",
304 | "Card(rank='7', suit='diamonds')\n",
305 | "Card(rank='8', suit='diamonds')\n",
306 | "Card(rank='9', suit='diamonds')\n",
307 | "Card(rank='10', suit='diamonds')\n",
308 | "Card(rank='J', suit='diamonds')\n",
309 | "Card(rank='Q', suit='diamonds')\n",
310 | "Card(rank='K', suit='diamonds')\n",
311 | "Card(rank='A', suit='diamonds')\n",
312 | "Card(rank='2', suit='clubs')\n",
313 | "Card(rank='3', suit='clubs')\n",
314 | "Card(rank='4', suit='clubs')\n",
315 | "Card(rank='5', suit='clubs')\n",
316 | "Card(rank='6', suit='clubs')\n",
317 | "Card(rank='7', suit='clubs')\n",
318 | "Card(rank='8', suit='clubs')\n",
319 | "Card(rank='9', suit='clubs')\n",
320 | "Card(rank='10', suit='clubs')\n",
321 | "Card(rank='J', suit='clubs')\n",
322 | "Card(rank='Q', suit='clubs')\n",
323 | "Card(rank='K', suit='clubs')\n",
324 | "Card(rank='A', suit='clubs')\n",
325 | "Card(rank='2', suit='hearts')\n",
326 | "Card(rank='3', suit='hearts')\n",
327 | "Card(rank='4', suit='hearts')\n",
328 | "Card(rank='5', suit='hearts')\n",
329 | "Card(rank='6', suit='hearts')\n",
330 | "Card(rank='7', suit='hearts')\n",
331 | "Card(rank='8', suit='hearts')\n",
332 | "Card(rank='9', suit='hearts')\n",
333 | "Card(rank='10', suit='hearts')\n",
334 | "Card(rank='J', suit='hearts')\n",
335 | "Card(rank='Q', suit='hearts')\n",
336 | "Card(rank='K', suit='hearts')\n",
337 | "Card(rank='A', suit='hearts')\n"
338 | ]
339 | }
340 | ],
341 | "source": [
342 | "# If not implement __contain__, then for `in` syntax python test item that exist or not in sequential mode: with __getitem__ check each item.\n",
343 | "for card in deck: # first use __iter__ then __getitem__ if not define.\n",
344 | " print(card)"
345 | ]
346 | },
347 | {
348 | "cell_type": "code",
349 | "execution_count": 17,
350 | "metadata": {
351 | "ExecuteTime": {
352 | "end_time": "2023-09-29T10:42:19.488656700Z",
353 | "start_time": "2023-09-29T10:42:19.222035300Z"
354 | }
355 | },
356 | "outputs": [
357 | {
358 | "name": "stdout",
359 | "output_type": "stream",
360 | "text": [
361 | "Card(rank='A', suit='hearts')\n",
362 | "Card(rank='K', suit='hearts')\n",
363 | "Card(rank='Q', suit='hearts')\n",
364 | "Card(rank='J', suit='hearts')\n",
365 | "Card(rank='10', suit='hearts')\n",
366 | "Card(rank='9', suit='hearts')\n",
367 | "Card(rank='8', suit='hearts')\n",
368 | "Card(rank='7', suit='hearts')\n",
369 | "Card(rank='6', suit='hearts')\n",
370 | "Card(rank='5', suit='hearts')\n",
371 | "Card(rank='4', suit='hearts')\n",
372 | "Card(rank='3', suit='hearts')\n",
373 | "Card(rank='2', suit='hearts')\n",
374 | "Card(rank='A', suit='clubs')\n",
375 | "Card(rank='K', suit='clubs')\n",
376 | "Card(rank='Q', suit='clubs')\n",
377 | "Card(rank='J', suit='clubs')\n",
378 | "Card(rank='10', suit='clubs')\n",
379 | "Card(rank='9', suit='clubs')\n",
380 | "Card(rank='8', suit='clubs')\n",
381 | "Card(rank='7', suit='clubs')\n",
382 | "Card(rank='6', suit='clubs')\n",
383 | "Card(rank='5', suit='clubs')\n",
384 | "Card(rank='4', suit='clubs')\n",
385 | "Card(rank='3', suit='clubs')\n",
386 | "Card(rank='2', suit='clubs')\n",
387 | "Card(rank='A', suit='diamonds')\n",
388 | "Card(rank='K', suit='diamonds')\n",
389 | "Card(rank='Q', suit='diamonds')\n",
390 | "Card(rank='J', suit='diamonds')\n",
391 | "Card(rank='10', suit='diamonds')\n",
392 | "Card(rank='9', suit='diamonds')\n",
393 | "Card(rank='8', suit='diamonds')\n",
394 | "Card(rank='7', suit='diamonds')\n",
395 | "Card(rank='6', suit='diamonds')\n",
396 | "Card(rank='5', suit='diamonds')\n",
397 | "Card(rank='4', suit='diamonds')\n",
398 | "Card(rank='3', suit='diamonds')\n",
399 | "Card(rank='2', suit='diamonds')\n",
400 | "Card(rank='A', suit='spades')\n",
401 | "Card(rank='K', suit='spades')\n",
402 | "Card(rank='Q', suit='spades')\n",
403 | "Card(rank='J', suit='spades')\n",
404 | "Card(rank='10', suit='spades')\n",
405 | "Card(rank='9', suit='spades')\n",
406 | "Card(rank='8', suit='spades')\n",
407 | "Card(rank='7', suit='spades')\n",
408 | "Card(rank='6', suit='spades')\n",
409 | "Card(rank='5', suit='spades')\n",
410 | "Card(rank='4', suit='spades')\n",
411 | "Card(rank='3', suit='spades')\n",
412 | "Card(rank='2', suit='spades')\n"
413 | ]
414 | }
415 | ],
416 | "source": [
417 | "for card in reversed(deck):\n",
418 | " print(card)"
419 | ]
420 | },
421 | {
422 | "cell_type": "code",
423 | "execution_count": 18,
424 | "metadata": {
425 | "ExecuteTime": {
426 | "end_time": "2023-09-29T10:42:19.488656700Z",
427 | "start_time": "2023-09-29T10:42:19.238033200Z"
428 | }
429 | },
430 | "outputs": [
431 | {
432 | "data": {
433 | "text/plain": [
434 | "True"
435 | ]
436 | },
437 | "execution_count": 18,
438 | "metadata": {},
439 | "output_type": "execute_result"
440 | }
441 | ],
442 | "source": [
443 | "Card('Q', 'hearts') in deck # if has no __contains__ method, use __getitem__"
444 | ]
445 | },
446 | {
447 | "cell_type": "code",
448 | "execution_count": 19,
449 | "metadata": {
450 | "ExecuteTime": {
451 | "end_time": "2023-09-29T10:42:19.489656800Z",
452 | "start_time": "2023-09-29T10:42:19.254036400Z"
453 | }
454 | },
455 | "outputs": [
456 | {
457 | "data": {
458 | "text/plain": [
459 | "False"
460 | ]
461 | },
462 | "execution_count": 19,
463 | "metadata": {},
464 | "output_type": "execute_result"
465 | }
466 | ],
467 | "source": [
468 | "Card('7', 'beasts') in deck"
469 | ]
470 | },
471 | {
472 | "cell_type": "code",
473 | "execution_count": 20,
474 | "metadata": {
475 | "ExecuteTime": {
476 | "end_time": "2023-09-29T10:42:19.489656800Z",
477 | "start_time": "2023-09-29T10:42:19.273031900Z"
478 | }
479 | },
480 | "outputs": [
481 | {
482 | "name": "stdout",
483 | "output_type": "stream",
484 | "text": [
485 | "Card(rank='2', suit='clubs')\n",
486 | "Card(rank='2', suit='diamonds')\n",
487 | "Card(rank='2', suit='hearts')\n",
488 | "Card(rank='2', suit='spades')\n",
489 | "Card(rank='3', suit='clubs')\n",
490 | "Card(rank='3', suit='diamonds')\n",
491 | "Card(rank='3', suit='hearts')\n",
492 | "Card(rank='3', suit='spades')\n",
493 | "Card(rank='4', suit='clubs')\n",
494 | "Card(rank='4', suit='diamonds')\n",
495 | "Card(rank='4', suit='hearts')\n",
496 | "Card(rank='4', suit='spades')\n",
497 | "Card(rank='5', suit='clubs')\n",
498 | "Card(rank='5', suit='diamonds')\n",
499 | "Card(rank='5', suit='hearts')\n",
500 | "Card(rank='5', suit='spades')\n",
501 | "Card(rank='6', suit='clubs')\n",
502 | "Card(rank='6', suit='diamonds')\n",
503 | "Card(rank='6', suit='hearts')\n",
504 | "Card(rank='6', suit='spades')\n",
505 | "Card(rank='7', suit='clubs')\n",
506 | "Card(rank='7', suit='diamonds')\n",
507 | "Card(rank='7', suit='hearts')\n",
508 | "Card(rank='7', suit='spades')\n",
509 | "Card(rank='8', suit='clubs')\n",
510 | "Card(rank='8', suit='diamonds')\n",
511 | "Card(rank='8', suit='hearts')\n",
512 | "Card(rank='8', suit='spades')\n",
513 | "Card(rank='9', suit='clubs')\n",
514 | "Card(rank='9', suit='diamonds')\n",
515 | "Card(rank='9', suit='hearts')\n",
516 | "Card(rank='9', suit='spades')\n",
517 | "Card(rank='10', suit='clubs')\n",
518 | "Card(rank='10', suit='diamonds')\n",
519 | "Card(rank='10', suit='hearts')\n",
520 | "Card(rank='10', suit='spades')\n",
521 | "Card(rank='J', suit='clubs')\n",
522 | "Card(rank='J', suit='diamonds')\n",
523 | "Card(rank='J', suit='hearts')\n",
524 | "Card(rank='J', suit='spades')\n",
525 | "Card(rank='Q', suit='clubs')\n",
526 | "Card(rank='Q', suit='diamonds')\n",
527 | "Card(rank='Q', suit='hearts')\n",
528 | "Card(rank='Q', suit='spades')\n",
529 | "Card(rank='K', suit='clubs')\n",
530 | "Card(rank='K', suit='diamonds')\n",
531 | "Card(rank='K', suit='hearts')\n",
532 | "Card(rank='K', suit='spades')\n",
533 | "Card(rank='A', suit='clubs')\n",
534 | "Card(rank='A', suit='diamonds')\n",
535 | "Card(rank='A', suit='hearts')\n",
536 | "Card(rank='A', suit='spades')\n"
537 | ]
538 | }
539 | ],
540 | "source": [
541 | "suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)\n",
542 | "\n",
543 | "def spades_high(card):\n",
544 | " rank_value = FrenchDeck.ranks.index(card.rank) # get index of item from the FrenchDeck.ranks list\n",
545 | " return rank_value * len(suit_values) + suit_values[card.suit]\n",
546 | "\n",
547 | "for card in sorted(deck, key=spades_high):\n",
548 | " print(card)"
549 | ]
550 | },
551 | {
552 | "cell_type": "markdown",
553 | "metadata": {},
554 | "source": [
555 | "## Emulating Numeric Types\n"
556 | ]
557 | },
558 | {
559 | "cell_type": "markdown",
560 | "metadata": {},
561 | "source": [
562 | "#### Example 1-2. A simple two-dimensional vector class\n"
563 | ]
564 | },
565 | {
566 | "cell_type": "code",
567 | "execution_count": 21,
568 | "metadata": {
569 | "ExecuteTime": {
570 | "end_time": "2023-09-29T10:42:19.489656800Z",
571 | "start_time": "2023-09-29T10:42:19.295033800Z"
572 | }
573 | },
574 | "outputs": [],
575 | "source": [
576 | "import math\n",
577 | "\n",
578 | "class Vector:\n",
579 | "\n",
580 | " def __init__(self, x=0, y=0):\n",
581 | " self.x = x\n",
582 | " self.y = y\n",
583 | "\n",
584 | " def __repr__(self):\n",
585 | " return 'Vector(%r, %r)' % (self.x, self.y)\n",
586 | "\n",
587 | " def __abs__(self):\n",
588 | " return math.hypot(self.x, self.y)\n",
589 | "\n",
590 | " def __bool__(self):\n",
591 | " return bool(abs(self))\n",
592 | "\n",
593 | " def __add__(self, other):\n",
594 | " x = self.x + other.x\n",
595 | " y = self.y + other.y\n",
596 | " return Vector(x, y)\n",
597 | "\n",
598 | " def __mul__(self, scalar): # if we want to multiply number by a Vector should implement __rmul__\n",
599 | " return Vector(self.x * scalar, self.y * scalar)"
600 | ]
601 | },
602 | {
603 | "cell_type": "code",
604 | "execution_count": 22,
605 | "metadata": {
606 | "ExecuteTime": {
607 | "end_time": "2023-09-29T10:42:19.693693900Z",
608 | "start_time": "2023-09-29T10:42:19.317566300Z"
609 | }
610 | },
611 | "outputs": [
612 | {
613 | "data": {
614 | "text/plain": [
615 | "Vector(4, 5)"
616 | ]
617 | },
618 | "execution_count": 22,
619 | "metadata": {},
620 | "output_type": "execute_result"
621 | }
622 | ],
623 | "source": [
624 | "v1 = Vector(2, 4)\n",
625 | "v2 = Vector(2, 1)\n",
626 | "v1 + v2"
627 | ]
628 | },
629 | {
630 | "cell_type": "code",
631 | "execution_count": 23,
632 | "metadata": {
633 | "ExecuteTime": {
634 | "end_time": "2023-09-29T10:42:19.742373100Z",
635 | "start_time": "2023-09-29T10:42:19.468652800Z"
636 | }
637 | },
638 | "outputs": [
639 | {
640 | "data": {
641 | "text/plain": [
642 | "5.0"
643 | ]
644 | },
645 | "execution_count": 23,
646 | "metadata": {},
647 | "output_type": "execute_result"
648 | }
649 | ],
650 | "source": [
651 | "v = Vector(3, 4)\n",
652 | "abs(v)"
653 | ]
654 | },
655 | {
656 | "cell_type": "code",
657 | "execution_count": 24,
658 | "metadata": {
659 | "ExecuteTime": {
660 | "end_time": "2023-09-29T10:42:19.743372600Z",
661 | "start_time": "2023-09-29T10:42:19.469663Z"
662 | }
663 | },
664 | "outputs": [
665 | {
666 | "data": {
667 | "text/plain": [
668 | "Vector(9, 12)"
669 | ]
670 | },
671 | "execution_count": 24,
672 | "metadata": {},
673 | "output_type": "execute_result"
674 | }
675 | ],
676 | "source": [
677 | "v * 3"
678 | ]
679 | },
680 | {
681 | "cell_type": "code",
682 | "execution_count": 25,
683 | "metadata": {
684 | "ExecuteTime": {
685 | "end_time": "2023-09-29T10:42:19.774429900Z",
686 | "start_time": "2023-09-29T10:42:19.469663Z"
687 | }
688 | },
689 | "outputs": [
690 | {
691 | "data": {
692 | "text/plain": [
693 | "15.0"
694 | ]
695 | },
696 | "execution_count": 25,
697 | "metadata": {},
698 | "output_type": "execute_result"
699 | }
700 | ],
701 | "source": [
702 | "abs(v * 3)"
703 | ]
704 | },
705 | {
706 | "cell_type": "markdown",
707 | "metadata": {},
708 | "source": [
709 | "### `__repr__`\n",
710 | "\n",
711 | "- Called by repr built-in to get the string representation of the object for inspection\n",
712 | "- Called by the interactive console and debugger\n",
713 | "- Called by %r placeholder in classic formatting with the % operator\n",
714 | "- Called by the !r in f-strings or str.format\n",
715 | "- String returned by `__repr__` should be unambiguous and if possible, we can re-create represented object.\n",
716 | "- If you only implement one of these special methods in Python, choose `__repr__`.\n",
717 | "\n",
718 | "### `__str__`\n",
719 | "\n",
720 | "- Called by the str() built-in\n",
721 | "- Called by the print()\n",
722 | "- It should return a string suitable for display to end users.\n",
723 | "- If `__repr__` return user-friendly value, don't need to implement `__str__` because called `__repr__` as a fallback.\n"
724 | ]
725 | },
726 | {
727 | "cell_type": "markdown",
728 | "metadata": {},
729 | "source": [
730 | "# Boolean\n",
731 | "\n",
732 | "- All instances of classes are considered ture, if not implement `__bool__` or `__len__`\n",
733 | "- bool() first calls `__bool__` and if not implemented tries to invoke `__len__`\n"
734 | ]
735 | },
736 | {
737 | "cell_type": "markdown",
738 | "metadata": {},
739 | "source": [
740 | "\n",
741 | "\n",
742 | "- Method names in italic are abstract, so they must be implemented by concrete subclasses\n",
743 | "- Iterable: support unpacking and other forms of iteration\n",
744 | "- Sized: support len built-in function\n",
745 | "- Container: support the `in` operator\n",
746 | "- Sequence: implemented by list, str\n",
747 | " - is reversible\n",
748 | "- Mapping: implemented by dict, collections.defaultdict\n",
749 | "- Set: implemented by frozenset, set\n"
750 | ]
751 | },
752 | {
753 | "cell_type": "markdown",
754 | "metadata": {},
755 | "source": [
756 | "# Special method\n",
757 | "\n",
758 | "### Table1-1\n",
759 | "\n",
760 | "| Category | Method names |\n",
761 | "| :---------------------------- | :------------------------------------------------------------------- |\n",
762 | "| String/bytes representation | `__repr__ __str__ __format__ __bytes__ __fspath__ ` |\n",
763 | "| Conversion to number | `__bool__ __complex__ __int__ __float__ __hash__ __index__ ` |\n",
764 | "| Emulating collections | `__len__ __getitem__ __setitem__ __delitem__ __contains__ ` |\n",
765 | "| Iteration | `__iter__ __aiter__ __next__ __anext__ __reversed__ ` |\n",
766 | "| Callable execution | `__call__ __await__ ` |\n",
767 | "| Context management | `__enter__ __exit__ __aexit__ __aenter__ ` |\n",
768 | "| Instance creation/destruction | `__new__ __init__ __del__ ` |\n",
769 | "| Attribute management | `__getattr__ __getattribute__ __setattr__ __delattr__ __dir__ ` |\n",
770 | "| Attribute descriptors | `__get__ __set__ __delete__ __set_name__ ` |\n",
771 | "| Abstract base classes | `__instancecheck__ __subclasscheck__ ` |\n",
772 | "| Class metaprogramming | `__prepare__ __init_subclass__ __class_getitem__ __mro_entries__` |\n",
773 | "\n",
774 | "### Table1-2\n",
775 | "\n",
776 | "| Operator category | Symbols | Method names |\n",
777 | "| :------------------------------ | :--------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |\n",
778 | "| Unary numeric | `- + abs() ` | `__neg__ __pos__ __abs__ ` |\n",
779 | "| Rich comparison | `< <= == != > >= ` | `__lt__ __le__ __be__ __eq__ __ne__ __gt__ __ge__ ` |\n",
780 | "| Arithmetic | `+ - * / % // % @ divmod() round() ** pow() ` | `__add__ __sub__ __mul__ __truediv__ __floordiv__ __mod__ __matmul__ __divmod__ __round__ __pow__ ` |\n",
781 | "| Reverse arithmetic | `(arithmetic operators with swapped operands)` | `__radd__ __rsub__ __rmul__ __rtruediv__ __rfloordiv__ __rmod__ __rmatmul__ __rdivmod__ __rround__ __rpow__` |\n",
782 | "| Augmented assignment arithmetic | `+= -= *= @= /= //= %= @= **= ` | `__iadd__ __isub__ __imul__ __itruediv__ __ifloordiv__ __imod__ __imatmul__ __ipow__ ` |\n",
783 | "| Bitwise | `& \\| ^ << >> ~ ` | `__and__ __or__ __xor__ __lshift__ __rshift__ __invert__ ` |\n",
784 | "| Reversed bitwise | `(bitwise operators with swapped operands) ` | `__rand__ __ror__ __rxor__ __rlshift__ __rrshift__ __rinvert__ ` |\n",
785 | "| Augmented assignment bitwise | `&= \\|= ^= <<= >>= ` | `__iand__ __ior__ __ixor__ __ilshift__ __irshift__ ` |\n",
786 | "\n",
787 | "- Python calls a reversed operator special method on the second operand when the corresponding special method on the first operand cannot be used.\n"
788 | ]
789 | },
790 | {
791 | "cell_type": "markdown",
792 | "metadata": {},
793 | "source": [
794 | "### Lecturers\n",
795 | "\n",
796 | "1. Reza Hashemian date: 09-29-2023, [LinkedIn](https://www.linkedin.com/in/rezahashemian)\n",
797 | "2.\n",
798 | "\n",
799 | "#### Reviewers\n",
800 | "\n",
801 | "1. Amirhossein Zare date: 09-29-2023, [LinkedIn](https://www.linkedin.com/in/amirhossein-zare-insight)\n",
802 | "2. Alireza Hashemi [Linkedin](https://www.linkedin.com/in/alireza-hashemi-573a3b28b)\n",
803 | "3. S.Khorram, date: 06-10-2023, [LinkedIn](https://www.linkedin.com/in/sara-khorram)\n"
804 | ]
805 | },
806 | {
807 | "cell_type": "code",
808 | "execution_count": null,
809 | "metadata": {},
810 | "outputs": [],
811 | "source": []
812 | }
813 | ],
814 | "metadata": {
815 | "kernelspec": {
816 | "display_name": "Python 3 (ipykernel)",
817 | "language": "python",
818 | "name": "python3"
819 | },
820 | "language_info": {
821 | "codemirror_mode": {
822 | "name": "ipython",
823 | "version": 3
824 | },
825 | "file_extension": ".py",
826 | "mimetype": "text/x-python",
827 | "name": "python",
828 | "nbconvert_exporter": "python",
829 | "pygments_lexer": "ipython3",
830 | "version": "3.10.13"
831 | }
832 | },
833 | "nbformat": 4,
834 | "nbformat_minor": 2
835 | }
836 |
--------------------------------------------------------------------------------
/chapter-03/zen.txt:
--------------------------------------------------------------------------------
1 | The Zen of Python, by Tim Peters
2 |
3 | Beautiful is better than ugly.
4 | Explicit is better than implicit.
5 | Simple is better than complex.
6 | Complex is better than complicated.
7 | Flat is better than nested.
8 | Sparse is better than dense.
9 | Readability counts.
10 | Special cases aren't special enough to break the rules.
11 | Although practicality beats purity.
12 | Errors should never pass silently.
13 | Unless explicitly silenced.
14 | In the face of ambiguity, refuse the temptation to guess.
15 | There should be one-- and preferably only one --obvious way to do it.
16 | Although that way may not be obvious at first unless you're Dutch.
17 | Now is better than never.
18 | Although never is often better than *right* now.
19 | If the implementation is hard to explain, it's a bad idea.
20 | If the implementation is easy to explain, it may be a good idea.
21 | Namespaces are one honking great idea -- let's do more of those!
22 |
--------------------------------------------------------------------------------
/chapter-04/images/example_encoding.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Pyhints/fluent_python/0fd488a3d157ae9372864a675948766e29239a5c/chapter-04/images/example_encoding.jpg
--------------------------------------------------------------------------------
/chapter-04/notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Chapter 4 : Unicode Text Versus Bytes\n"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "## Basic concepts\n"
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "### what is str, character, code-point, encoding, bytes\n"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "Humans use text \n",
29 | "Computers speak bytes\n"
30 | ]
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "metadata": {},
35 | "source": [
36 | "| terms | definition | example |\n",
37 | "| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |\n",
38 | "| code-point | 1 `Character` = 1 `Code-point`
**purpose**: every character → number-id
Fonts work on character to show [Grapheme](https://en.wikipedia.org/wiki/Grapheme) | assing A to a number(code) |\n",
39 | "| code-point in Unicode standard | U+0000 to U+10FFFF (ie 1,114,111 code-points !) | A → U+0041 |\n",
40 | "| encoding | algorithm that converts **code-points** to **byte-sequences** and vice versa
**purpose**: write it down in memory or disk | ascii, UTF-8, UTF-16LE, latin_1, cp1252 |\n",
41 | "| UTF-8 | an encoding algorithm | A(U+0041) → \\x41
€(U+20AC) → \\xe2\\x82\\xac |\n",
42 | "| UTF-16LE | an encoding algorithm | A(U+0041) → \\x41\\x00
€(U+20AC) →\\xac\\x20 |\n"
43 | ]
44 | },
45 | {
46 | "cell_type": "markdown",
47 | "metadata": {},
48 | "source": [
49 | "\n"
50 | ]
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "metadata": {},
55 | "source": [
56 | "| purpose | change | dechange | example : Identity function |\n",
57 | "| -------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |\n",
58 | "| **character ⇔ code-point** | ord(character) -> str | chr(code-point:int) -> int | chr(ord('😸'))
ord(chr(0x1f638)) |\n",
59 | "| **character ⇔ bytes** | my_str.encode(encoding=\"utf8\") | my_bytes.decode(encoding=\"utf8\") | '😸'.encode(encoding=\"utf8\").decode(encoding=\"utf8\")
b'\\xf0\\x9f\\x98\\xb8'.decode(encoding=\"utf8\").encode(encoding=\"utf8\") |\n",
60 | "| **int ⇔ bytes** | my_int.to_bytes(length=1, byteorder=\"big\") | int.from_bytes(my_binary, byteorder=\"big\") | my_int = 65
my_binary = my_int.to_bytes(length=1, byteorder=\"big\")
int.from_bytes(my_binary, byteorder=\"big\") |\n"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 6,
66 | "metadata": {},
67 | "outputs": [
68 | {
69 | "data": {
70 | "text/plain": [
71 | "(65, 'A', 128568, '😸')"
72 | ]
73 | },
74 | "execution_count": 6,
75 | "metadata": {},
76 | "output_type": "execute_result"
77 | }
78 | ],
79 | "source": [
80 | "ord(\"A\"), chr(65), ord(\"😸\"), chr(128568)"
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": 10,
86 | "metadata": {},
87 | "outputs": [
88 | {
89 | "name": "stdout",
90 | "output_type": "stream",
91 | "text": [
92 | "b'\\x124'\n"
93 | ]
94 | },
95 | {
96 | "data": {
97 | "text/plain": [
98 | "(13330, 4660)"
99 | ]
100 | },
101 | "execution_count": 10,
102 | "metadata": {},
103 | "output_type": "execute_result"
104 | }
105 | ],
106 | "source": [
107 | "my_int = 4660\n",
108 | "my_binary = my_int.to_bytes(length=2, byteorder=\"big\")\n",
109 | "print(my_binary)\n",
110 | "wrong_int = int.from_bytes(my_binary, byteorder=\"little\")\n",
111 | "correct_int = int.from_bytes(my_binary, byteorder=\"big\")\n",
112 | "wrong_int, correct_int"
113 | ]
114 | },
115 | {
116 | "cell_type": "markdown",
117 | "metadata": {},
118 | "source": [
119 | "**Note:**\n",
120 | "\n",
121 | "> **'cafe\\u0301'**, **'caf\\N{Latin Small Letter E with Acute}'**, **caf\\u00E9** **'café'** are the same ! \n",
122 | "> **0x41** , **65** both are int type\n",
123 | "\n",
124 | "```python\n",
125 | ">>> for word in 'cafe\\u0301', 'caf\\N{Latin Small Letter E with Acute}', 'caf\\u00E9', 'café':\n",
126 | ">>> print(word)\n",
127 | "café, café, café, café\n",
128 | "```\n"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": 30,
134 | "metadata": {},
135 | "outputs": [
136 | {
137 | "name": "stdout",
138 | "output_type": "stream",
139 | "text": [
140 | "café, café, café, café, "
141 | ]
142 | }
143 | ],
144 | "source": [
145 | "for word in \"cafe\\u0301\", \"caf\\N{LATIN SMALL LETTER E WITH ACUTE}\", \"caf\\u00e9\", \"café\":\n",
146 | " print(word, end=\", \", flush=True)"
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": 31,
152 | "metadata": {},
153 | "outputs": [
154 | {
155 | "name": "stdout",
156 | "output_type": "stream",
157 | "text": [
158 | "original word ==> café \n",
159 | "encoded word ==> b'caf\\xc3\\xa9' \n",
160 | "decoded word ==> café\n"
161 | ]
162 | }
163 | ],
164 | "source": [
165 | "word = \"café\"\n",
166 | "encoded_word = word.encode(\"utf8\")\n",
167 | "decoded_word = encoded_word.decode() # 5 bytes: c, a, f,\\xc3\\xa9\n",
168 | "print(\n",
169 | " f\"original word ==> {word} \\nencoded word ==> {encoded_word} \\ndecoded word ==> {decoded_word}\"\n",
170 | ")"
171 | ]
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "metadata": {},
176 | "source": [
177 | "#### **Question**: encode an integer value as str or raw?\n",
178 | "\n",
179 | "**INT32 range is :** $(-1 \\times 2^{31})$ to $(2^{31}-1)$ \n",
180 | "take an example with max int ie :\n",
181 | "\n",
182 | "```python\n",
183 | ">>> decimal_value = 2 ** 31 - 1\n",
184 | "2147483647\n",
185 | "```\n"
186 | ]
187 | },
188 | {
189 | "cell_type": "code",
190 | "execution_count": 53,
191 | "metadata": {},
192 | "outputs": [
193 | {
194 | "name": "stdout",
195 | "output_type": "stream",
196 | "text": [
197 | "decoding raw decimal ==> 4 bytes\n",
198 | "decoding str-decimal ==> 10 bytes\n"
199 | ]
200 | }
201 | ],
202 | "source": [
203 | "decimal_value = 2**31 - 1 # int32 range is -1 * 2 ** 31 to 2 ** 31\n",
204 | "num_bytes = (\n",
205 | " 4 # int32 ie 4 bytes so Number of bytes for the desired byte representation = 4\n",
206 | ")\n",
207 | "bytes_decimal = decimal_value.to_bytes(num_bytes, \"big\")\n",
208 | "print(f\"{'decoding raw decimal'.ljust(22)}==> {len(bytes_decimal)} bytes\")\n",
209 | "\n",
210 | "bytes_str_decimal = bytes(str(decimal_value), encoding=\"utf-8\")\n",
211 | "print(f\"{'decoding str-decimal'.ljust(22)}==> {len(bytes_str_decimal)} bytes\")"
212 | ]
213 | },
214 | {
215 | "cell_type": "markdown",
216 | "metadata": {},
217 | "source": [
218 | "### Rules when displaying bytes:\n",
219 | "\n",
220 | "- For bytes with decimal codes 32 to 126—from space to ~ (tilde)—the ASCII char‐\n",
221 | " acter itself is used.\n",
222 | "- For bytes corresponding to tab, newline, carriage return, and \\, the escape\n",
223 | " sequences \\t, \\n, \\r, and \\\\ are used.\n",
224 | "- If both string delimiters ' and \" appear in the byte sequence, the whole sequence\n",
225 | " is delimited by ', and any ' inside are escaped as \\'.\n",
226 | " > ```python\n",
227 | " > >>> byte_sequence = b'Hello \"world\"\\'s example'\n",
228 | " > >>> string_representation = byte_sequence.decode('utf-8')\n",
229 | " >\n",
230 | " > >>> print(string_representation)\n",
231 | " > Hello \"world\"'s example\n",
232 | " > ```\n",
233 | "- For other byte values, a hexadecimal escape sequence is used (e.g., \\x00 is the\n",
234 | " null byte)\n"
235 | ]
236 | },
237 | {
238 | "cell_type": "code",
239 | "execution_count": 17,
240 | "metadata": {},
241 | "outputs": [
242 | {
243 | "name": "stdout",
244 | "output_type": "stream",
245 | "text": [
246 | "5 bytes exist in encoded_word\n",
247 | "Based on utf8 ==> every c, a, f corresponds to one byte and é corresponds to two bytes\n",
248 | "c ==> 99------- or 0x63----- or b'c'\n",
249 | "a ==> 97------- or 0x61----- or b'a'\n",
250 | "f ==> 102------ or 0x66----- or b'f'\n",
251 | "é ==> 50089---- or 0xc3a9--- or b'\\xc3\\xa9'\n"
252 | ]
253 | }
254 | ],
255 | "source": [
256 | "word = \"café\"\n",
257 | "encoded_word = word.encode(\"utf8\")\n",
258 | "\n",
259 | "print(f\"{len(encoded_word)} bytes exist in encoded_word\")\n",
260 | "print(\n",
261 | " \"Based on utf8 ==> every c, a, f corresponds to one byte and é corresponds to two bytes\"\n",
262 | ")\n",
263 | "# so print every char with its corresponding bytes\n",
264 | "# encoded_word[0].to_bytes(1, byteorder='big') equals to encoded_word[0:1]\n",
265 | "print(\n",
266 | " f\"c ==> {encoded_word[0]:-<9} or {hex(encoded_word[0]):-<9} or {encoded_word[0:1]}\\\n",
267 | "\\na ==> {encoded_word[1]:-<9} or {hex(encoded_word[1]):-<9} or {encoded_word[1:2]}\\\n",
268 | "\\nf ==> {encoded_word[2]:-<9} or {hex(encoded_word[2]):-<9} or {encoded_word[2:3]}\\\n",
269 | "\\né ==> {int.from_bytes(encoded_word[3:], byteorder='big'):-<9} or \\\n",
270 | "{hex(int.from_bytes(encoded_word[3:], byteorder='big')):-<9} or {encoded_word[3:]}\"\n",
271 | ")"
272 | ]
273 | },
274 | {
275 | "cell_type": "markdown",
276 | "metadata": {},
277 | "source": [
278 | "### bytearray vs bytes\n"
279 | ]
280 | },
281 | {
282 | "cell_type": "markdown",
283 | "metadata": {},
284 | "source": [
285 | "| Type | status |\n",
286 | "| --------- | --------- |\n",
287 | "| bytes | immutable |\n",
288 | "| bytearray | mutable |\n"
289 | ]
290 | },
291 | {
292 | "cell_type": "code",
293 | "execution_count": 18,
294 | "metadata": {},
295 | "outputs": [
296 | {
297 | "name": "stdout",
298 | "output_type": "stream",
299 | "text": [
300 | "b'caf\\xc3\\xa9'\n",
301 | "bytearray(b'caf\\xc3\\xa9')\n"
302 | ]
303 | }
304 | ],
305 | "source": [
306 | "print(cafe := bytes(\"café\", encoding=\"utf_8\"))\n",
307 | "print(cafe2 := bytearray(cafe))"
308 | ]
309 | },
310 | {
311 | "cell_type": "code",
312 | "execution_count": 22,
313 | "metadata": {},
314 | "outputs": [
315 | {
316 | "name": "stdout",
317 | "output_type": "stream",
318 | "text": [
319 | "'bytes' object does not support item assignment\n",
320 | " is mutable\n"
321 | ]
322 | }
323 | ],
324 | "source": [
325 | "def mutable_or_not(cafe):\n",
326 | " try:\n",
327 | " cafe[0] = 12\n",
328 | " print(type(cafe), \"is mutable\")\n",
329 | " except Exception as e:\n",
330 | " print(e)\n",
331 | "\n",
332 | "\n",
333 | "mutable_or_not(cafe)\n",
334 | "mutable_or_not(cafe2)"
335 | ]
336 | },
337 | {
338 | "cell_type": "code",
339 | "execution_count": 32,
340 | "metadata": {},
341 | "outputs": [
342 | {
343 | "data": {
344 | "text/plain": [
345 | "4"
346 | ]
347 | },
348 | "execution_count": 32,
349 | "metadata": {},
350 | "output_type": "execute_result"
351 | }
352 | ],
353 | "source": [
354 | "open(\"test_file\", \"wb\").write(encoded_word)\n",
355 | "open(\"test_file2\", \"w\").write(decoded_word)"
356 | ]
357 | },
358 | {
359 | "cell_type": "code",
360 | "execution_count": 37,
361 | "metadata": {},
362 | "outputs": [
363 | {
364 | "data": {
365 | "text/plain": [
366 | "(False, True)"
367 | ]
368 | },
369 | "execution_count": 37,
370 | "metadata": {},
371 | "output_type": "execute_result"
372 | }
373 | ],
374 | "source": [
375 | "import keyword\n",
376 | "import builtins\n",
377 | "\n",
378 | "keywords = keyword.kwlist\n",
379 | "\"bytearray\" in keywords, \"bytearray\" in dir(builtins)"
380 | ]
381 | },
382 | {
383 | "cell_type": "markdown",
384 | "metadata": {},
385 | "source": [
386 | "#### what is CRLF?\n",
387 | "\n",
388 | "- Windows: Windows-based systems traditionally use the CRLF sequence (CR followed by LF) to represent line endings in text files.\n",
389 | "- Unix-like systems (Linux, macOS, etc.): Unix-like systems typically use the LF character alone to represent line endings.\n"
390 | ]
391 | },
392 | {
393 | "cell_type": "code",
394 | "execution_count": 23,
395 | "metadata": {},
396 | "outputs": [
397 | {
398 | "data": {
399 | "text/plain": [
400 | "b'Hi\\nNEWLINE2'"
401 | ]
402 | },
403 | "execution_count": 23,
404 | "metadata": {},
405 | "output_type": "execute_result"
406 | }
407 | ],
408 | "source": [
409 | "# open(\"test_file1\", 'w').write(\"hi\\nNEWLINE\")\n",
410 | "open(\"test_file2\", \"wb\").write(b\"Hi\\nNEWLINE2\")\n",
411 | "open(\"test_file2\", \"rb\").read()"
412 | ]
413 | },
414 | {
415 | "cell_type": "code",
416 | "execution_count": 24,
417 | "metadata": {},
418 | "outputs": [
419 | {
420 | "data": {
421 | "text/plain": [
422 | "b'Hi\\nNEWLINE'"
423 | ]
424 | },
425 | "execution_count": 24,
426 | "metadata": {},
427 | "output_type": "execute_result"
428 | }
429 | ],
430 | "source": [
431 | "\"Hi\\nNEWLINE\".encode()"
432 | ]
433 | },
434 | {
435 | "cell_type": "markdown",
436 | "metadata": {},
437 | "source": [
438 | "## issues when encodig or decoding\n"
439 | ]
440 | },
441 | {
442 | "cell_type": "markdown",
443 | "metadata": {},
444 | "source": [
445 | "### encoding\n"
446 | ]
447 | },
448 | {
449 | "cell_type": "code",
450 | "execution_count": 5,
451 | "metadata": {},
452 | "outputs": [
453 | {
454 | "ename": "UnicodeEncodeError",
455 | "evalue": "'charmap' codec can't encode character '\\xe3' in position 1: character maps to ",
456 | "output_type": "error",
457 | "traceback": [
458 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
459 | "\u001b[1;31mUnicodeEncodeError\u001b[0m Traceback (most recent call last)",
460 | "\u001b[1;32md:\\FluentPython\\my_present\\fluent_ch4_v0.ipynb Cell 18\u001b[0m line \u001b[0;36m2\n\u001b[0;32m 1\u001b[0m city \u001b[39m=\u001b[39m \u001b[39m'\u001b[39m\u001b[39mSão Paulo\u001b[39m\u001b[39m'\u001b[39m\n\u001b[1;32m----> 2\u001b[0m city\u001b[39m.\u001b[39;49mencode(\u001b[39m'\u001b[39;49m\u001b[39mcp437\u001b[39;49m\u001b[39m'\u001b[39;49m)\n",
461 | "File \u001b[1;32mc:\\Users\\ansar\\.conda\\envs\\fluent\\lib\\encodings\\cp437.py:12\u001b[0m, in \u001b[0;36mCodec.encode\u001b[1;34m(self, input, errors)\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mencode\u001b[39m(\u001b[39mself\u001b[39m,\u001b[39minput\u001b[39m,errors\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39mstrict\u001b[39m\u001b[39m'\u001b[39m):\n\u001b[1;32m---> 12\u001b[0m \u001b[39mreturn\u001b[39;00m codecs\u001b[39m.\u001b[39;49mcharmap_encode(\u001b[39minput\u001b[39;49m,errors,encoding_map)\n",
462 | "\u001b[1;31mUnicodeEncodeError\u001b[0m: 'charmap' codec can't encode character '\\xe3' in position 1: character maps to "
463 | ]
464 | }
465 | ],
466 | "source": [
467 | "city = \"São Paulo\"\n",
468 | "city.encode(\"cp437\")"
469 | ]
470 | },
471 | {
472 | "cell_type": "code",
473 | "execution_count": 6,
474 | "metadata": {},
475 | "outputs": [
476 | {
477 | "data": {
478 | "text/plain": [
479 | "False"
480 | ]
481 | },
482 | "execution_count": 6,
483 | "metadata": {},
484 | "output_type": "execute_result"
485 | }
486 | ],
487 | "source": [
488 | "str.isascii(city)"
489 | ]
490 | },
491 | {
492 | "cell_type": "code",
493 | "execution_count": 8,
494 | "metadata": {},
495 | "outputs": [
496 | {
497 | "data": {
498 | "text/plain": [
499 | "b'So Paulo'"
500 | ]
501 | },
502 | "execution_count": 8,
503 | "metadata": {},
504 | "output_type": "execute_result"
505 | }
506 | ],
507 | "source": [
508 | "city.encode(\"cp437\", errors=\"ignore\")\n",
509 | "# The error='ignore' handler skips characters that cannot be encoded\n",
510 | "# this is usually a very bad idea, leading to silent data los"
511 | ]
512 | },
513 | {
514 | "cell_type": "code",
515 | "execution_count": 139,
516 | "metadata": {},
517 | "outputs": [
518 | {
519 | "data": {
520 | "text/plain": [
521 | "b'S?o Paulo'"
522 | ]
523 | },
524 | "execution_count": 139,
525 | "metadata": {},
526 | "output_type": "execute_result"
527 | }
528 | ],
529 | "source": [
530 | "city.encode(\"cp437\", errors=\"replace\")\n",
531 | "# When encoding, error='replace' substitutes unencodable characters with '?'\n",
532 | "# data is also lost, but users will get a clue that something is amiss."
533 | ]
534 | },
535 | {
536 | "cell_type": "code",
537 | "execution_count": 140,
538 | "metadata": {},
539 | "outputs": [
540 | {
541 | "data": {
542 | "text/plain": [
543 | "b'São Paulo'"
544 | ]
545 | },
546 | "execution_count": 140,
547 | "metadata": {},
548 | "output_type": "execute_result"
549 | }
550 | ],
551 | "source": [
552 | "city.encode(\"cp437\", errors=\"xmlcharrefreplace\")\n",
553 | "# 'xmlcharrefreplace' replaces unencodable characters with an XML entity.\n",
554 | "# If you can’t use UTF, and you can’t afford to lose data, this is the only option."
555 | ]
556 | },
557 | {
558 | "cell_type": "markdown",
559 | "metadata": {},
560 | "source": [
561 | "### decoding\n"
562 | ]
563 | },
564 | {
565 | "cell_type": "markdown",
566 | "metadata": {},
567 | "source": [
568 | "many legacy 8-bit encodings like `cp1252`, `iso8859_1`, `koi8_r` **are able to decode any stream of bytes, including random noise, without reporting errors.**\n"
569 | ]
570 | },
571 | {
572 | "cell_type": "code",
573 | "execution_count": 143,
574 | "metadata": {},
575 | "outputs": [
576 | {
577 | "data": {
578 | "text/plain": [
579 | "'MontrИal'"
580 | ]
581 | },
582 | "execution_count": 143,
583 | "metadata": {},
584 | "output_type": "execute_result"
585 | }
586 | ],
587 | "source": [
588 | "octets = b\"Montr\\xe9al\"\n",
589 | "octets.decode(\"koi8_r\")"
590 | ]
591 | },
592 | {
593 | "cell_type": "code",
594 | "execution_count": 144,
595 | "metadata": {},
596 | "outputs": [
597 | {
598 | "ename": "UnicodeDecodeError",
599 | "evalue": "'utf-8' codec can't decode byte 0xe9 in position 5: invalid continuation byte",
600 | "output_type": "error",
601 | "traceback": [
602 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
603 | "\u001b[1;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)",
604 | "\u001b[1;32md:\\FluentPython\\my_present\\fluent_ch4_v0.ipynb Cell 25\u001b[0m line \u001b[0;36m1\n\u001b[1;32m----> 1\u001b[0m octets\u001b[39m.\u001b[39;49mdecode(\u001b[39m'\u001b[39;49m\u001b[39mutf_8\u001b[39;49m\u001b[39m'\u001b[39;49m)\n",
605 | "\u001b[1;31mUnicodeDecodeError\u001b[0m: 'utf-8' codec can't decode byte 0xe9 in position 5: invalid continuation byte"
606 | ]
607 | }
608 | ],
609 | "source": [
610 | "octets.decode(\"utf_8\")"
611 | ]
612 | },
613 | {
614 | "cell_type": "code",
615 | "execution_count": 145,
616 | "metadata": {},
617 | "outputs": [
618 | {
619 | "data": {
620 | "text/plain": [
621 | "'Montr�al'"
622 | ]
623 | },
624 | "execution_count": 145,
625 | "metadata": {},
626 | "output_type": "execute_result"
627 | }
628 | ],
629 | "source": [
630 | "octets.decode(\"utf_8\", errors=\"replace\")\n",
631 | "# Using 'replace' error handling, the \\xe9 is replaced by “�” (code point\n",
632 | "# U+FFFD), the official Unicode REPLACEMENT CHARACTER intended to represent\n",
633 | "# unknown characters."
634 | ]
635 | },
636 | {
637 | "cell_type": "markdown",
638 | "metadata": {},
639 | "source": [
640 | "### When running a module\n"
641 | ]
642 | },
643 | {
644 | "cell_type": "markdown",
645 | "metadata": {},
646 | "source": [
647 | "If you load a .py module containing non-UTF-8 data and no encoding declaration, you get a message like this:\n",
648 | "\n",
649 | "**SyntaxError:** \n",
650 | "Non-UTF-8 code starting with '\\xe1' in file ola.py on line1, \n",
651 | " but no encoding declared; see https://python.org/dev/peps/pep-0263/\n",
652 | "for details\n",
653 | "\n",
654 | "a likely scenario is opening a .py file created on Windows with cp1252. Note that this error hap‐\n",
655 | "pens even in Python for Windows, because the default encoding for Python 3 source\n",
656 | "is UTF-8 across all platforms\n",
657 | "\n",
658 | "solution: at the beginning:\n",
659 | "\n",
660 | "```python\n",
661 | "# coding: cp1252\n",
662 | "print('Olá, Mundo!')\n",
663 | "```\n"
664 | ]
665 | },
666 | {
667 | "cell_type": "markdown",
668 | "metadata": {},
669 | "source": [
670 | "## How to Discover the Encoding of a Byte Sequence\n"
671 | ]
672 | },
673 | {
674 | "cell_type": "markdown",
675 | "metadata": {},
676 | "source": [
677 | "**Short answer: you can’t**\n",
678 | "\n",
679 | "| Sign in Bytes | Conclusion | Why |\n",
680 | "| ------------------------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------- |\n",
681 | "| contain byte values over 127 | not ASCII | ascii is 0 to 127 |\n",
682 | "| b'\\x00' bytes are common | 32-bit encoding, and not an 8-bit scheme | null characters in plain text are bugs |\n",
683 | "| b'\\x20\\x00' bytes are common | UTF-16LE | more likely to represent the space character (U+0020) in a UTF-16LE |\n",
684 | "| detect BOM at starting
ex: b'\\xef\\xbb\\xbf\\ ... ' | see b'\\xef\\xbb\\xbf\\ so likely UTF-8 file | b'\\xef\\xbb\\xbf' BOM of utf-8-sig
other codec may have BOM too |\n",
685 | "\n",
686 | "**note** : A good library to detect encoding is [chardet](https://pypi.org/project/chardet/)\n"
687 | ]
688 | },
689 | {
690 | "cell_type": "markdown",
691 | "metadata": {},
692 | "source": [
693 | "#### Big endian vs little endian\n"
694 | ]
695 | },
696 | {
697 | "cell_type": "markdown",
698 | "metadata": {},
699 | "source": [
700 | "Understanding endianness is important when working with multi-byte values, such as integers, floating-point numbers, and character encodings like UTF-16. It helps ensure that the bytes are interpreted correctly based on the chosen byte order\n",
701 | "\n",
702 | "_least significant byte(LSB)_ is stored at the lowest memory address, while the _most significant byte (MSB)_ is stored at the highest memory address\n",
703 | "\n",
704 | "**Example:**\n",
705 | "\n",
706 | "> **big-endian (storing 0x1234 or 4660):** \n",
707 | "> |memory address : 0x1000 |memory address : 0x1001 |\n",
708 | "> |---------|---------|\n",
709 | "> |value: 0x12 | value: 0x34 | \n",
710 | ">
\n",
711 | "\n",
712 | "> **little-endian (storing 0x1234 or 4660):** \n",
713 | "> |memory address : 0x1000 |memory address : 0x1001 |\n",
714 | "> |---------|---------|\n",
715 | "> |value: 0x34 | value: 0x12 |\n",
716 | "\n",
717 | "see cpu standard of your system:\n",
718 | "\n",
719 | "```python\n",
720 | "import sys\n",
721 | ">>> sys.byteorder\n",
722 | "'little'\n",
723 | "```\n"
724 | ]
725 | },
726 | {
727 | "cell_type": "markdown",
728 | "metadata": {},
729 | "source": [
730 | "### What is BOM(byte order mark) ?\n",
731 | "\n",
732 | "```python\n",
733 | " u16 = 'El Niño'.encode('utf_16')\n",
734 | ">>> u16\n",
735 | "b'\\xff\\xfeE\\x00l\\x00 \\x00N\\x00i\\x00\\xf1\\x00o\\x00'\n",
736 | "```\n",
737 | "\n",
738 | "**\\xff\\xfe → BOM**\n",
739 | "\n",
740 | "in utf-16le or utf-16le BOM is not generated\n",
741 | "\n",
742 | "example of little-endian and big-endian in utf-16 encoding: \n",
743 | "e(U+0065) → 2 Byte : (LSB)→ 0x65 | (MSB)→ 0x00\n",
744 | "\n",
745 | "- utf-16le :\n",
746 | "\n",
747 | " > Byte 0: 0x65 \n",
748 | " > Byte 1: 0x00
\n",
749 | "\n",
750 | "- utf-16be :\n",
751 | " > Byte 0: 0x00 \n",
752 | " > Byte 1: 0x65\n",
753 | "\n",
754 | "```python\n",
755 | ">>> u16le = 'El Niño'.encode('utf_16le')\n",
756 | ">>> list(u16le)\n",
757 | "[69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111, 0]\n",
758 | ">>> u16be = 'El Niño'.encode('utf_16be')\n",
759 | ">>> list(u16be)\n",
760 | "[0, 69, 0, 108, 0, 32, 0, 78, 0, 105, 0, 241, 0, 111]\n",
761 | "```\n",
762 | "\n",
763 | "**One big advantage of UTF-8 is that it produces the same byte sequence regardless of machine endianness, so no BOM is needed.**\n",
764 | "\n",
765 | "```python\n",
766 | ">>> 'El Niño'.encode('utf_8')\n",
767 | "b'El Ni\\xc3\\xb1o'\n",
768 | ">>> 'El Niño'.encode('utf_8_sig')\n",
769 | "b'\\xef\\xbb\\xbfEl Ni\\xc3\\xb1o'\n",
770 | "```\n",
771 | "\n",
772 | "[codecs documentation](https://docs.python.org/3/library/codecs.html#encodings-and-unicode) says: “In UTF-8, the use of the BOM is discouraged and should generally be avoided.”\n"
773 | ]
774 | },
775 | {
776 | "cell_type": "markdown",
777 | "metadata": {},
778 | "source": [
779 | "## Handling Text Files\n",
780 | "\n",
781 | "recommand specify encoding when write or read txt files\n"
782 | ]
783 | },
784 | {
785 | "cell_type": "code",
786 | "execution_count": 26,
787 | "metadata": {},
788 | "outputs": [
789 | {
790 | "data": {
791 | "text/plain": [
792 | "4"
793 | ]
794 | },
795 | "execution_count": 26,
796 | "metadata": {},
797 | "output_type": "execute_result"
798 | }
799 | ],
800 | "source": [
801 | "open(\"cafe.txt\", \"w\", encoding=\"utf_8\").write(\"café\")"
802 | ]
803 | },
804 | {
805 | "cell_type": "code",
806 | "execution_count": 27,
807 | "metadata": {},
808 | "outputs": [
809 | {
810 | "name": "stdout",
811 | "output_type": "stream",
812 | "text": [
813 | "file content : café \n",
814 | "file size : 5\n",
815 | "file encoding : utf-8\n"
816 | ]
817 | }
818 | ],
819 | "source": [
820 | "f = open(\"cafe.txt\", encoding=\"utf-8\")\n",
821 | "import os\n",
822 | "\n",
823 | "print(\n",
824 | " f\"file content : {f.read()} \\nfile size : {os.stat('cafe.txt').st_size}\\nfile encoding : {f.encoding}\"\n",
825 | ")\n",
826 | "f.close()"
827 | ]
828 | },
829 | {
830 | "cell_type": "markdown",
831 | "metadata": {},
832 | "source": [
833 | "**Note**: If you omit the encoding argument when opening a file, the default is given by :\n"
834 | ]
835 | },
836 | {
837 | "cell_type": "code",
838 | "execution_count": 33,
839 | "metadata": {},
840 | "outputs": [
841 | {
842 | "data": {
843 | "text/plain": [
844 | "'UTF-8'"
845 | ]
846 | },
847 | "execution_count": 33,
848 | "metadata": {},
849 | "output_type": "execute_result"
850 | }
851 | ],
852 | "source": [
853 | "import locale\n",
854 | "\n",
855 | "locale.getpreferredencoding()"
856 | ]
857 | },
858 | {
859 | "cell_type": "markdown",
860 | "metadata": {},
861 | "source": [
862 | "**str ⇔ locale.getpreferredencoding() ⇔ bytes**\n"
863 | ]
864 | },
865 | {
866 | "cell_type": "markdown",
867 | "metadata": {},
868 | "source": [
869 | "```bash\n",
870 | "chcp\n",
871 | "$$ Active code page: 437\n",
872 | "```\n",
873 | "\n",
874 | "**Code page 437**, IBM PC-437 or simply **CP437**, was an early character **encoding used by IBM in its original IBM PC** and compatible systems.\n"
875 | ]
876 | },
877 | {
878 | "cell_type": "code",
879 | "execution_count": 34,
880 | "metadata": {},
881 | "outputs": [
882 | {
883 | "name": "stdout",
884 | "output_type": "stream",
885 | "text": [
886 | " locale.getpreferredencoding() -> 'UTF-8'\n",
887 | " type(my_file) -> \n",
888 | " my_file.encoding -> 'UTF-8'\n",
889 | " sys.stdout.isatty() -> True\n",
890 | " sys.stdout.encoding -> 'utf-8'\n",
891 | " sys.stdin.isatty() -> True\n",
892 | " sys.stdin.encoding -> 'utf-8'\n",
893 | " sys.stderr.isatty() -> True\n",
894 | " sys.stderr.encoding -> 'utf-8'\n",
895 | " sys.getdefaultencoding() -> 'utf-8'\n",
896 | " sys.getfilesystemencoding() -> 'utf-8'\n"
897 | ]
898 | }
899 | ],
900 | "source": [
901 | "!python utils/default_encodings.py # page 134\n",
902 | "# stdout ==> display\n",
903 | "# stdin ==> type input\n",
904 | "# stderr ==> error showing"
905 | ]
906 | },
907 | {
908 | "cell_type": "markdown",
909 | "metadata": {},
910 | "source": [
911 | "It’s weird that chcp and sys.stdout.encoding\n",
912 | "say different things when stdout is writing to the console, but it’s great that now we\n",
913 | "can print Unicode strings without encoding errors on Windows—unless the user\n",
914 | "redirects output to a file That does not mean all your favorite emojis will appear in the console: that also depends on the font the console is using\n"
915 | ]
916 | },
917 | {
918 | "cell_type": "markdown",
919 | "metadata": {},
920 | "source": [
921 | "**Fortunately these codes run well in win11 terminal!** based on fluent python book these codes will not run well on win10 \n",
922 | "if see any problem : console font doesn’t have the glyph to display it\n"
923 | ]
924 | },
925 | {
926 | "cell_type": "code",
927 | "execution_count": 35,
928 | "metadata": {},
929 | "outputs": [
930 | {
931 | "name": "stdout",
932 | "output_type": "stream",
933 | "text": [
934 | "3.10.13 (main, Oct 18 2023, 01:54:08) [GCC 11.3.1 20221121 (Red Hat 11.3.1-4)]\n",
935 | "\n",
936 | "sys.stdout.isatty(): False\n",
937 | "sys.stdout.encoding: UTF-8\n",
938 | "\n",
939 | "Trying to output HORIZONTAL ELLIPSIS:\n",
940 | "…\n",
941 | "Trying to output INFINITY:\n",
942 | "∞\n",
943 | "Trying to output CIRCLED NUMBER FORTY TWO:\n",
944 | "㊷\n"
945 | ]
946 | }
947 | ],
948 | "source": [
949 | "import sys\n",
950 | "from unicodedata import name\n",
951 | "\n",
952 | "print(sys.version)\n",
953 | "print()\n",
954 | "print(\"sys.stdout.isatty():\", sys.stdout.isatty())\n",
955 | "print(\"sys.stdout.encoding:\", sys.stdout.encoding)\n",
956 | "print()\n",
957 | "test_chars = [\n",
958 | " \"\\N{HORIZONTAL ELLIPSIS}\", # exists in cp1252, not in cp437\n",
959 | " \"\\N{INFINITY}\", # exists in cp437, not in cp1252\n",
960 | " \"\\N{CIRCLED NUMBER FORTY TWO}\", # not in cp437 or in cp1252\n",
961 | "]\n",
962 | "for char in test_chars:\n",
963 | " print(f\"Trying to output {name(char)}:\")\n",
964 | " print(char)"
965 | ]
966 | },
967 | {
968 | "cell_type": "markdown",
969 | "metadata": {},
970 | "source": [
971 | "when python version > 3.6 :\n",
972 | "\n",
973 | "> If PYTHONLEGACYWINDOWSSTDIO is not set or is set to an empty string, the encoding for standard I/O is as follows:\n",
974 | ">\n",
975 | "> - For interactive I/O (when running Python interactively in a terminal), the encoding is UTF-8.\n",
976 | "> - If the output/input is redirected to/from a file, the encoding is determined by locale.getpreferredencoding().\n"
977 | ]
978 | },
979 | {
980 | "cell_type": "markdown",
981 | "metadata": {},
982 | "source": [
983 | "## Normalizing unicode\n"
984 | ]
985 | },
986 | {
987 | "cell_type": "markdown",
988 | "metadata": {},
989 | "source": [
990 | "| unicodedata.normalize(normalization*from*, char\\_) | meaning |\n",
991 | "| -------------------------------------------------- | ------------------------------------------------------------------------------------------- |\n",
992 | "| **NFC** | to produce the shortest equivalent string (safe) |\n",
993 | "| NFD | expanding composed characters into base characters and separate combining characters (safe) |\n",
994 | "| NFKC | The goal is to have a standardized form for compatibility characters (search&index) |\n",
995 | "| NFKD | The goal is to have a standardized form for compatibility characters (search&index) |\n",
996 | "\n",
997 | "```python\n",
998 | ">>> from unicodedata import normalize\n",
999 | ">>> s1 = 'café'\n",
1000 | ">>> s2 = 'cafe\\N{COMBINING ACUTE ACCENT}'\n",
1001 | ">>> len(s1), len(s2)\n",
1002 | "(4, 5)\n",
1003 | ">>> len(normalize('NFC', s1)), len(normalize('NFC', s2))\n",
1004 | "(4, 4)\n",
1005 | ">>> len(normalize('NFD', s1)), len(normalize('NFD', s2))\n",
1006 | "(5, 5)\n",
1007 | ">>> normalize('NFC', s1) == normalize('NFC', s2)\n",
1008 | "True\n",
1009 | ">>> normalize('NFD', s1) == normalize('NFD', s2)\n",
1010 | "True\n",
1011 | "```\n",
1012 | "\n",
1013 | "Keyboard drivers usually generate composed characters, so text typed by users will be\n",
1014 | "in NFC by default.\n"
1015 | ]
1016 | },
1017 | {
1018 | "cell_type": "markdown",
1019 | "metadata": {},
1020 | "source": [
1021 | "**Note:** **NFKC or NFKD may lose or distort information**, but they can produce convenient intermediate representations for searching and indexing\n"
1022 | ]
1023 | },
1024 | {
1025 | "cell_type": "code",
1026 | "execution_count": 17,
1027 | "metadata": {},
1028 | "outputs": [
1029 | {
1030 | "name": "stdout",
1031 | "output_type": "stream",
1032 | "text": [
1033 | "½ ==> 1⁄2\n",
1034 | "1\tDIGIT ONE\n",
1035 | "⁄\tFRACTION SLASH\n",
1036 | "2\tDIGIT TWO\n",
1037 | "4² ==> 42\n"
1038 | ]
1039 | }
1040 | ],
1041 | "source": [
1042 | "from unicodedata import normalize, name\n",
1043 | "\n",
1044 | "half = \"\\N{VULGAR FRACTION ONE HALF}\"\n",
1045 | "print(half, \" ==> \", normalize(\"NFKC\", half))\n",
1046 | "\n",
1047 | "for char in normalize(\"NFKC\", half):\n",
1048 | " print(char, name(char), sep=\"\\t\")\n",
1049 | "four_squared = \"4²\"\n",
1050 | "print(four_squared, \" ==> \", normalize(\"NFKC\", four_squared))"
1051 | ]
1052 | },
1053 | {
1054 | "cell_type": "markdown",
1055 | "metadata": {},
1056 | "source": [
1057 | "### case folding\n",
1058 | "\n",
1059 | "case folding is usefull When preparing text for searching or indexing \n",
1060 | "**Case folding is essentially converting all text to lowercase, with some additional**\n",
1061 | "transformations. It is supported by the str.casefold() method \n",
1062 | "There are nearly 300 code points for which str.casefold() and str.lower() return\n",
1063 | "different results. \n",
1064 | "It is **good when case-insensitive comparisons but with some problems** \n",
1065 | "recommand to use **normalize('NFC', str1).casefold()**\n",
1066 | "\n",
1067 | "```python\n",
1068 | ">>> eszett = 'ß'\n",
1069 | ">>> name(eszett)\n",
1070 | "'LATIN SMALL LETTER SHARP S'\n",
1071 | ">>> eszett_cf = eszett.casefold()\n",
1072 | ">>> eszett, eszett_cf\n",
1073 | "('ß', 'ss')\n",
1074 | "```\n"
1075 | ]
1076 | },
1077 | {
1078 | "cell_type": "markdown",
1079 | "metadata": {},
1080 | "source": [
1081 | "| purpose | recommend |\n",
1082 | "| ----------------------------------------------------------- | --------------------------------- |\n",
1083 | "| safe normalization | NFC normalization |\n",
1084 | "| case-insentive search | NFC + casefold() |\n",
1085 | "| decompose character into base character and combining marks | NFD |\n",
1086 | "| check \\_chr\\_\\_ to find out diacritics? | unicodedata.combining(\\_char\\_\\_) |\n"
1087 | ]
1088 | },
1089 | {
1090 | "cell_type": "markdown",
1091 | "metadata": {},
1092 | "source": [
1093 | "### sort str\n",
1094 | "\n",
1095 | "Python sorts sequences of any type by comparing the items in each sequence one by one. \n",
1096 | "For strings, this means comparing the code points. Unfortunately, this produces unacceptable results for anyone who uses non-ASCII characters \n",
1097 | "**pyuca : pure-Python implementation of the Unicode Collation Algorithm (UCA)**\n"
1098 | ]
1099 | },
1100 | {
1101 | "cell_type": "markdown",
1102 | "metadata": {},
1103 | "source": [
1104 | "```python\n",
1105 | "import locale\n",
1106 | "my_locale = locale.setlocale(locale.LC_COLLATE, 'pt_BR.UTF-8')\n",
1107 | "print(my_locale)\n",
1108 | "fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']\n",
1109 | "sorted_fruits = sorted(fruits, key=locale.strxfrm)\n",
1110 | "print(sorted_fruits)\n",
1111 | "\n",
1112 | "```\n",
1113 | "\n",
1114 | "```python\n",
1115 | ">>> import pyuca\n",
1116 | ">>> coll = pyuca.Collator()\n",
1117 | ">>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']\n",
1118 | ">>> sorted_fruits = sorted(fruits, key=coll.sort_key)\n",
1119 | ">>> sorted_fruits\n",
1120 | "['açaí', 'acerola', 'atemoia', 'cajá', 'caju']\n",
1121 | "```\n"
1122 | ]
1123 | },
1124 | {
1125 | "cell_type": "markdown",
1126 | "metadata": {},
1127 | "source": [
1128 | "## The Unicode Database\n"
1129 | ]
1130 | },
1131 | {
1132 | "cell_type": "markdown",
1133 | "metadata": {},
1134 | "source": [
1135 | "you can see more information about characters \n",
1136 | "see this char('😸') info from [compart.com](https://www.compart.com/en/unicode/U+1F638)\n",
1137 | "\n",
1138 | "the Unicode database records whether a character is printable, is a letter, is\n",
1139 | "a decimal digit, or is some other numeric symbol. That’s how the str methods isal\n",
1140 | "pha, isprintable, isdecimal, and isnumeric work. str.casefold also uses infor‐\n",
1141 | "mation from a Unicode table.\n",
1142 | "\n",
1143 | "**tip:** more information on [unicodedata](https://docs.python.org/3/library/unicodedata.html)\n"
1144 | ]
1145 | },
1146 | {
1147 | "cell_type": "code",
1148 | "execution_count": 47,
1149 | "metadata": {},
1150 | "outputs": [
1151 | {
1152 | "name": "stdout",
1153 | "output_type": "stream",
1154 | "text": [
1155 | "The U+ code for ⑦ ----------------- U+2466\n",
1156 | "name of ⑦ char--------------------- CIRCLED DIGIT SEVEN\n",
1157 | "char is decimal ?------------------ False\n",
1158 | "char is digit ?-------------------- False\n",
1159 | "char is number ? ------------------ True\n",
1160 | "equal number of char sign --------- 7.0\n"
1161 | ]
1162 | }
1163 | ],
1164 | "source": [
1165 | "import unicodedata\n",
1166 | "import re\n",
1167 | "\n",
1168 | "re_digit = re.compile(r\"\\d\")\n",
1169 | "# regex is more usefull: https://pypi.org/project/regex/\n",
1170 | "\n",
1171 | "\n",
1172 | "def better_print(input_expalin, value):\n",
1173 | " print(f\"{input_expalin:-<35} {value}\")\n",
1174 | "\n",
1175 | "\n",
1176 | "def info_char(character):\n",
1177 | " code_point = ord(character)\n",
1178 | " unicode_code = f\"U+{code_point:04X}\"\n",
1179 | " better_print(f\"The U+ code for {character} \", unicode_code)\n",
1180 | " better_print(f\"name of {character} char\", unicodedata.name(character))\n",
1181 | " better_print(\"char is decimal ?\", character.isdecimal())\n",
1182 | " better_print(f\"char is digit ?\", bool(re_digit.match(character)))\n",
1183 | " better_print(\"char is number ? \", character.isnumeric())\n",
1184 | " try: # or you could first check it is a number so see its number to avoid error rasing\n",
1185 | " better_print(\"equal number of char sign \", unicodedata.numeric(character))\n",
1186 | " except:\n",
1187 | " print(\"char is not numeric!\")\n",
1188 | "\n",
1189 | "\n",
1190 | "character0 = \"\\N{CIRCLED NUMBER FORTY TWO}\" # or \"㊷\"\n",
1191 | "character1 = \"\\N{GRINNING CAT FACE WITH SMILING EYES}\" # or '😸'\n",
1192 | "character2 = \"\\u2466\" # ⑦\n",
1193 | "character3 = \"5\"\n",
1194 | "info_char(character2)"
1195 | ]
1196 | },
1197 | {
1198 | "cell_type": "markdown",
1199 | "metadata": {},
1200 | "source": [
1201 | "## Dual-Mode str and bytes APIs\n"
1202 | ]
1203 | },
1204 | {
1205 | "cell_type": "markdown",
1206 | "metadata": {},
1207 | "source": [
1208 | "### re api\n",
1209 | "\n",
1210 | "regular expressions is valid on str and bytes, \n",
1211 | "**in bytes api, bytes outside the ASCII range are treated as nondigits and nonword characters.**\n"
1212 | ]
1213 | },
1214 | {
1215 | "cell_type": "code",
1216 | "execution_count": 83,
1217 | "metadata": {},
1218 | "outputs": [
1219 | {
1220 | "name": "stdout",
1221 | "output_type": "stream",
1222 | "text": [
1223 | "Text\n",
1224 | " 'Ramanujan saw ௧௭௨௯ as 1729 = 1³ + 12³ = 9³ + 10³.'\n",
1225 | "Numbers\n",
1226 | " str : ['௧௭௨௯', '1729', '1', '12', '9', '10']\n",
1227 | " bytes: [b'1729', b'1', b'12', b'9', b'10']\n",
1228 | "Words\n",
1229 | " str : ['Ramanujan', 'saw', '௧௭௨௯', 'as', '1729', '1³', '12³', '9³', '10³']\n",
1230 | " bytes: [b'Ramanujan', b'saw', b'as', b'1729', b'1', b'12', b'9', b'10']\n"
1231 | ]
1232 | }
1233 | ],
1234 | "source": [
1235 | "import re\n",
1236 | "\n",
1237 | "re_numbers_str = re.compile(r\"\\d+\")\n",
1238 | "re_words_str = re.compile(r\"\\w+\")\n",
1239 | "re_numbers_bytes = re.compile(rb\"\\d+\")\n",
1240 | "re_words_bytes = re.compile(rb\"\\w+\")\n",
1241 | "text_str = \"Ramanujan saw \\u0be7\\u0bed\\u0be8\\u0bef\" \" as 1729 = 1³ + 12³ = 9³ + 10³.\"\n",
1242 | "text_bytes = text_str.encode(\"utf_8\")\n",
1243 | "print(f\"Text\\n {text_str!r}\")\n",
1244 | "print(\"Numbers\")\n",
1245 | "print(\" str :\", re_numbers_str.findall(text_str))\n",
1246 | "print(\" bytes:\", re_numbers_bytes.findall(text_bytes))\n",
1247 | "print(\"Words\")\n",
1248 | "print(\" str :\", re_words_str.findall(text_str))\n",
1249 | "print(\" bytes:\", re_words_bytes.findall(text_bytes))"
1250 | ]
1251 | },
1252 | {
1253 | "cell_type": "markdown",
1254 | "metadata": {},
1255 | "source": [
1256 | "### os api\n",
1257 | "\n",
1258 | "The GNU/Linux kernel is not Unicode savvy, so in the real world you may find file‐\n",
1259 | "names made of byte sequences that are not valid in any sensible encoding scheme,\n",
1260 | "and cannot be decoded to str, If one such function is called with a str\n",
1261 | "argument, the argument will be automatically converted using the codec named by\n",
1262 | "sys.getfilesystemencoding(), and the OS response will be decoded with the same\n",
1263 | "codec.\n"
1264 | ]
1265 | },
1266 | {
1267 | "cell_type": "code",
1268 | "execution_count": 85,
1269 | "metadata": {},
1270 | "outputs": [
1271 | {
1272 | "data": {
1273 | "text/plain": [
1274 | "(['2',\n",
1275 | " 'cafe.txt',\n",
1276 | " 'ch4_v1.ipynb',\n",
1277 | " 'char_name.jpg',\n",
1278 | " 'dummy',\n",
1279 | " 'example_encoding.jpg',\n",
1280 | " 'test_file',\n",
1281 | " 'test_file2',\n",
1282 | " 'utils',\n",
1283 | " '__pycache__'],\n",
1284 | " [b'2',\n",
1285 | " b'cafe.txt',\n",
1286 | " b'ch4_v1.ipynb',\n",
1287 | " b'char_name.jpg',\n",
1288 | " b'dummy',\n",
1289 | " b'example_encoding.jpg',\n",
1290 | " b'test_file',\n",
1291 | " b'test_file2',\n",
1292 | " b'utils',\n",
1293 | " b'__pycache__'])"
1294 | ]
1295 | },
1296 | "execution_count": 85,
1297 | "metadata": {},
1298 | "output_type": "execute_result"
1299 | }
1300 | ],
1301 | "source": [
1302 | "import os\n",
1303 | "\n",
1304 | "os.listdir(\".\"), os.listdir(b\".\")"
1305 | ]
1306 | },
1307 | {
1308 | "cell_type": "markdown",
1309 | "metadata": {},
1310 | "source": [
1311 | "### Lecturers\n",
1312 | "\n",
1313 | "1. Mohammad Ansarifard, present date : 04-13-2023, `Linkedin` : [linkedin.com/in/mohammad-ansarifard](https://www.linkedin.com/in/mohammad-ansarifard)\n",
1314 | "2. Mehrdad biukian date: 04-10-2023, [LinkedIn](www.linkedin.com/in/mehrdad-biukian-naeini)\n",
1315 | "\n",
1316 | "#### Reviewers\n",
1317 | "\n",
1318 | "1.\n"
1319 | ]
1320 | }
1321 | ],
1322 | "metadata": {
1323 | "kernelspec": {
1324 | "display_name": "fluent",
1325 | "language": "python",
1326 | "name": "python3"
1327 | },
1328 | "language_info": {
1329 | "codemirror_mode": {
1330 | "name": "ipython",
1331 | "version": 3
1332 | },
1333 | "file_extension": ".py",
1334 | "mimetype": "text/x-python",
1335 | "name": "python",
1336 | "nbconvert_exporter": "python",
1337 | "pygments_lexer": "ipython3",
1338 | "version": "3.10.13"
1339 | }
1340 | },
1341 | "nbformat": 4,
1342 | "nbformat_minor": 2
1343 | }
1344 |
--------------------------------------------------------------------------------
/chapter-04/utils/check_terminal.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from unicodedata import name
3 | print(sys.version)
4 | print()
5 | print('sys.stdout.isatty():', sys.stdout.isatty())
6 | print('sys.stdout.encoding:', sys.stdout.encoding)
7 | print()
8 | test_chars = [
9 | '\N{HORIZONTAL ELLIPSIS}', # exists in cp1252, not in cp437
10 | '\N{INFINITY}', # exists in cp437, not in cp1252
11 | '\N{CIRCLED NUMBER FORTY TWO}', # not in cp437 or in cp1252
12 | ]
13 | for char in test_chars:
14 | print(f'Trying to output {name(char)}:')
15 | print(char)
--------------------------------------------------------------------------------
/chapter-04/utils/default_encodings.py:
--------------------------------------------------------------------------------
1 | import locale
2 | import sys
3 |
4 | expressions = """
5 | locale.getpreferredencoding()
6 | type(my_file)
7 | my_file.encoding
8 | sys.stdout.isatty()
9 | sys.stdout.encoding
10 | sys.stdin.isatty()
11 | sys.stdin.encoding
12 | sys.stderr.isatty()
13 | sys.stderr.encoding
14 | sys.getdefaultencoding()
15 | sys.getfilesystemencoding()
16 | """
17 |
18 | my_file = open('dummy', 'w')
19 |
20 | for expression in expressions.split():
21 | value = eval(expression)
22 | print(f'{expression:>30} -> {value!r}')
23 |
--------------------------------------------------------------------------------
/chapter-06/notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "1bfc3fbb",
6 | "metadata": {},
7 | "source": [
8 | "# Object References, Mutability, and Recycling\n",
9 | "\n",
10 | "## Object References and Mutability\n",
11 | "\n",
12 | "In Python, variables are essentially references to objects in memory. When you assign a value to a variable, you're creating a reference to the object. Understanding this reference behavior is essential to grasp how object mutability works.\n",
13 | "\n",
14 | "## Variables Are Not Boxes\n",
15 | "\n",
16 | "Consider variables as labels pointing to objects, rather than boxes storing values. This perspective is vital when dealing with mutable objects like lists and dictionaries.\n",
17 | "\n"
18 | ]
19 | },
20 | {
21 | "cell_type": "markdown",
22 | "id": "e6f8495b",
23 | "metadata": {},
24 | "source": [
25 | "With reference variables, it makes much more sense to say that the variable is assigned to an object, and not the other way around. After all, the object is created before the assignment. \n",
26 | "To understand an assignment in Python, read the righthand side first: that’s where the object is created or retrieved. After that, the variable on the left is bound to the object, like a label stuck to it. Just forget about the boxes."
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": 1,
32 | "id": "d621ce36",
33 | "metadata": {},
34 | "outputs": [
35 | {
36 | "data": {
37 | "text/plain": [
38 | "[1, 2, 3, 4]"
39 | ]
40 | },
41 | "execution_count": 1,
42 | "metadata": {},
43 | "output_type": "execute_result"
44 | }
45 | ],
46 | "source": [
47 | "a = [1, 2, 3]\n",
48 | "b = a # Bind the variable b to the same value that a is referencing\n",
49 | "a.append(4)\n",
50 | "b"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 2,
56 | "id": "761829ec",
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "name": "stdout",
61 | "output_type": "stream",
62 | "text": [
63 | "Gizmo id: 140472343798896\n",
64 | "Gizmo id: 140472342539104\n"
65 | ]
66 | },
67 | {
68 | "ename": "TypeError",
69 | "evalue": "unsupported operand type(s) for *: 'Gizmo' and 'int'",
70 | "output_type": "error",
71 | "traceback": [
72 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
73 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
74 | "\u001b[1;32m/home/killing-joke/Projects/Fluentpython/ch_06_v1/ch-06-06.ipynb Cell 4\u001b[0m line \u001b[0;36m6\n\u001b[1;32m 4\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39mf\u001b[39m\u001b[39m'\u001b[39m\u001b[39mGizmo id: \u001b[39m\u001b[39m{\u001b[39;00m\u001b[39mid\u001b[39m(\u001b[39mself\u001b[39m)\u001b[39m}\u001b[39;00m\u001b[39m'\u001b[39m)\n\u001b[1;32m 5\u001b[0m x \u001b[39m=\u001b[39m Gizmo()\n\u001b[0;32m----> 6\u001b[0m y \u001b[39m=\u001b[39m Gizmo() \u001b[39m*\u001b[39;49m \u001b[39m10\u001b[39;49m\n",
75 | "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for *: 'Gizmo' and 'int'"
76 | ]
77 | }
78 | ],
79 | "source": [
80 | "# Example 6-2. Variables are bound to objects only after the objects are created\n",
81 | "class Gizmo:\n",
82 | " def __init__(self):\n",
83 | " print(f'Gizmo id: {id(self)}')\n",
84 | "x = Gizmo()\n",
85 | "y = Gizmo() * 10"
86 | ]
87 | },
88 | {
89 | "cell_type": "code",
90 | "execution_count": 3,
91 | "id": "a3c80261",
92 | "metadata": {},
93 | "outputs": [
94 | {
95 | "data": {
96 | "text/plain": [
97 | "['Gizmo',\n",
98 | " 'In',\n",
99 | " 'Out',\n",
100 | " '_',\n",
101 | " '_1',\n",
102 | " '__',\n",
103 | " '___',\n",
104 | " '__builtin__',\n",
105 | " '__builtins__',\n",
106 | " '__doc__',\n",
107 | " '__loader__',\n",
108 | " '__name__',\n",
109 | " '__package__',\n",
110 | " '__spec__',\n",
111 | " '__vsc_ipynb_file__',\n",
112 | " '_dh',\n",
113 | " '_i',\n",
114 | " '_i1',\n",
115 | " '_i2',\n",
116 | " '_i3',\n",
117 | " '_ih',\n",
118 | " '_ii',\n",
119 | " '_iii',\n",
120 | " '_oh',\n",
121 | " 'a',\n",
122 | " 'b',\n",
123 | " 'exit',\n",
124 | " 'get_ipython',\n",
125 | " 'open',\n",
126 | " 'quit',\n",
127 | " 'x']"
128 | ]
129 | },
130 | "execution_count": 3,
131 | "metadata": {},
132 | "output_type": "execute_result"
133 | }
134 | ],
135 | "source": [
136 | "dir()"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": 3,
142 | "id": "84ab8500",
143 | "metadata": {},
144 | "outputs": [
145 | {
146 | "data": {
147 | "text/plain": [
148 | "True"
149 | ]
150 | },
151 | "execution_count": 3,
152 | "metadata": {},
153 | "output_type": "execute_result"
154 | }
155 | ],
156 | "source": [
157 | "z = x\n",
158 | "z is x"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "id": "d179ff47",
164 | "metadata": {},
165 | "source": [
166 | "## Identity, Equality, and Aliases\n",
167 | "\n",
168 | "Every object in Python has an identity (unique identifier), which you can access using the id() function. You can check if two variables reference the same object using the is operator, or if their values are equivalent using the == operator. Sometimes, two variables can be aliases, meaning they reference the same object.\n"
169 | ]
170 | },
171 | {
172 | "cell_type": "markdown",
173 | "id": "3379f06f",
174 | "metadata": {},
175 | "source": [
176 | "An object’s identity never changes once it has been created; you may think of it as the object’s address in memory. The is operator compares the identity of two objects; the id() function returns an integer representing its identity. \n",
177 | "\n",
178 | "The real meaning of an object’s ID is implementation dependent. In CPython, id() returns the memory address of the object, but it may be something else in another Python interpreter. The key point is that the ID is guaranteed to be a unique integer label, and it will never change during the life of the object. \n",
179 | "\n",
180 | "the most frequent use for id() is while debugging, when the repr() of two objects look alike, but you need to understand whether two references are aliases or point to separate objects. \n",
181 | "\n",
182 | "The `==` operator compares the values of objects (the data they hold), while is compares their identities. \n",
183 | "\n",
184 | "The is operator is faster than `==` , because it cannot be overloaded, so Python does not have to find and invoke special methods to evaluate it, and computing is as simple as comparing two integer IDs. "
185 | ]
186 | },
187 | {
188 | "cell_type": "code",
189 | "execution_count": 4,
190 | "id": "05427b32",
191 | "metadata": {},
192 | "outputs": [
193 | {
194 | "data": {
195 | "text/plain": [
196 | "True"
197 | ]
198 | },
199 | "execution_count": 4,
200 | "metadata": {},
201 | "output_type": "execute_result"
202 | }
203 | ],
204 | "source": [
205 | "# Example 6-3. charles and lewis refer to the same object\n",
206 | "charles = {'name': 'Charles L. Dodgson', 'born': 1832}\n",
207 | "lewis = charles\n",
208 | "lewis is charles"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": 5,
214 | "id": "f2a7cefd",
215 | "metadata": {},
216 | "outputs": [
217 | {
218 | "data": {
219 | "text/plain": [
220 | "(140288030783744, 140288030783744)"
221 | ]
222 | },
223 | "execution_count": 5,
224 | "metadata": {},
225 | "output_type": "execute_result"
226 | }
227 | ],
228 | "source": [
229 | "id(charles), id(lewis)\n"
230 | ]
231 | },
232 | {
233 | "cell_type": "code",
234 | "execution_count": 6,
235 | "id": "6348d3e4",
236 | "metadata": {},
237 | "outputs": [
238 | {
239 | "data": {
240 | "text/plain": [
241 | "{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}"
242 | ]
243 | },
244 | "execution_count": 6,
245 | "metadata": {},
246 | "output_type": "execute_result"
247 | }
248 | ],
249 | "source": [
250 | "lewis['balance'] = 950\n",
251 | "charles"
252 | ]
253 | },
254 | {
255 | "cell_type": "markdown",
256 | "id": "7fdcd9c6",
257 | "metadata": {},
258 | "source": [
259 | "### Choosing Between == and is\n",
260 | "\n",
261 | "Each object has its own `identity`, `type` and `value`. Only the identity does not change once it is created. The is operator compares the identity of two objects. (Use the id() function)\n",
262 | "\n",
263 | "`is` is faster than `==` because is only compares the ids of two Objects with singletons like `None`. `a == b` is equivalent to the abbreviation of `a.__eq__(b)`. The` __eq__` method is inherited from objects that compare ids, but most build-in data types override the `__eq__` method to match their equality behavior.\n",
264 | "\n",
265 | "\n",
266 | "Let's make it clear:\n",
267 | "\n",
268 | "* `==` compares values of objects\n",
269 | "* `is` compares identity of Objects\n"
270 | ]
271 | },
272 | {
273 | "cell_type": "code",
274 | "execution_count": 4,
275 | "id": "0a5d28d3",
276 | "metadata": {},
277 | "outputs": [
278 | {
279 | "data": {
280 | "text/plain": [
281 | "False"
282 | ]
283 | },
284 | "execution_count": 4,
285 | "metadata": {},
286 | "output_type": "execute_result"
287 | }
288 | ],
289 | "source": [
290 | "x is None"
291 | ]
292 | },
293 | {
294 | "cell_type": "code",
295 | "execution_count": 5,
296 | "id": "d3addc0c",
297 | "metadata": {},
298 | "outputs": [
299 | {
300 | "data": {
301 | "text/plain": [
302 | "True"
303 | ]
304 | },
305 | "execution_count": 5,
306 | "metadata": {},
307 | "output_type": "execute_result"
308 | }
309 | ],
310 | "source": [
311 | "x is not None"
312 | ]
313 | },
314 | {
315 | "cell_type": "markdown",
316 | "id": "115eb9ae",
317 | "metadata": {},
318 | "source": [
319 | "## The Relative Immutability of Tuples"
320 | ]
321 | },
322 | {
323 | "cell_type": "markdown",
324 | "id": "bebea1d8",
325 | "metadata": {},
326 | "source": [
327 | "When we use mutable data types in a tuple, the mutable data type can change, but the reference to the tuple remains the same while the value of the list within it changes."
328 | ]
329 | },
330 | {
331 | "cell_type": "code",
332 | "execution_count": 8,
333 | "id": "37d2cddb",
334 | "metadata": {},
335 | "outputs": [
336 | {
337 | "data": {
338 | "text/plain": [
339 | "True"
340 | ]
341 | },
342 | "execution_count": 8,
343 | "metadata": {},
344 | "output_type": "execute_result"
345 | }
346 | ],
347 | "source": [
348 | "t1 = (1, 2, [30, 40])\n",
349 | "t2 = (1, 2, [30, 40])\n",
350 | "t1 == t2"
351 | ]
352 | },
353 | {
354 | "cell_type": "code",
355 | "execution_count": 9,
356 | "id": "98d22f84",
357 | "metadata": {},
358 | "outputs": [
359 | {
360 | "data": {
361 | "text/plain": [
362 | "140288030783232"
363 | ]
364 | },
365 | "execution_count": 9,
366 | "metadata": {},
367 | "output_type": "execute_result"
368 | }
369 | ],
370 | "source": [
371 | "id(t1[-1])"
372 | ]
373 | },
374 | {
375 | "cell_type": "code",
376 | "execution_count": 10,
377 | "id": "d7612a38",
378 | "metadata": {},
379 | "outputs": [
380 | {
381 | "data": {
382 | "text/plain": [
383 | "(1, 2, [30, 40, 99])"
384 | ]
385 | },
386 | "execution_count": 10,
387 | "metadata": {},
388 | "output_type": "execute_result"
389 | }
390 | ],
391 | "source": [
392 | "t1[-1].append(99)\n",
393 | "t1"
394 | ]
395 | },
396 | {
397 | "cell_type": "code",
398 | "execution_count": 11,
399 | "id": "32399b5f",
400 | "metadata": {},
401 | "outputs": [
402 | {
403 | "data": {
404 | "text/plain": [
405 | "140288030783232"
406 | ]
407 | },
408 | "execution_count": 11,
409 | "metadata": {},
410 | "output_type": "execute_result"
411 | }
412 | ],
413 | "source": [
414 | "id(t1[-1])"
415 | ]
416 | },
417 | {
418 | "cell_type": "code",
419 | "execution_count": 12,
420 | "id": "0015793d",
421 | "metadata": {},
422 | "outputs": [
423 | {
424 | "data": {
425 | "text/plain": [
426 | "False"
427 | ]
428 | },
429 | "execution_count": 12,
430 | "metadata": {},
431 | "output_type": "execute_result"
432 | }
433 | ],
434 | "source": [
435 | "t1 == t2"
436 | ]
437 | },
438 | {
439 | "cell_type": "markdown",
440 | "id": "ec870ebb",
441 | "metadata": {},
442 | "source": [
443 | "## Copies Are Shallow by Default\n",
444 | "\n",
445 | "When you create a copy of a mutable object using the assignment operator or a copy method like copy(), you get a shallow copy. This means that the top-level object is duplicated, but the inner objects are still references. For deep copies, you can use the copy module's deepcopy() function.\n"
446 | ]
447 | },
448 | {
449 | "cell_type": "code",
450 | "execution_count": 6,
451 | "id": "8345b909",
452 | "metadata": {},
453 | "outputs": [],
454 | "source": [
455 | "l1 = [3, [55, 44], (7, 8, 9)]\n",
456 | "l2 = list(l1) # or l2 = l1[:]\n"
457 | ]
458 | },
459 | {
460 | "cell_type": "code",
461 | "execution_count": 7,
462 | "id": "ac7697c6",
463 | "metadata": {},
464 | "outputs": [
465 | {
466 | "data": {
467 | "text/plain": [
468 | "[3, [55, 44], (7, 8, 9)]"
469 | ]
470 | },
471 | "execution_count": 7,
472 | "metadata": {},
473 | "output_type": "execute_result"
474 | }
475 | ],
476 | "source": [
477 | "l2"
478 | ]
479 | },
480 | {
481 | "cell_type": "code",
482 | "execution_count": 8,
483 | "id": "73288b34",
484 | "metadata": {},
485 | "outputs": [
486 | {
487 | "data": {
488 | "text/plain": [
489 | "True"
490 | ]
491 | },
492 | "execution_count": 8,
493 | "metadata": {},
494 | "output_type": "execute_result"
495 | }
496 | ],
497 | "source": [
498 | "l2 == l1"
499 | ]
500 | },
501 | {
502 | "cell_type": "code",
503 | "execution_count": 9,
504 | "id": "ce3242ad",
505 | "metadata": {},
506 | "outputs": [
507 | {
508 | "data": {
509 | "text/plain": [
510 | "False"
511 | ]
512 | },
513 | "execution_count": 9,
514 | "metadata": {},
515 | "output_type": "execute_result"
516 | }
517 | ],
518 | "source": [
519 | "l2 is l1"
520 | ]
521 | },
522 | {
523 | "cell_type": "code",
524 | "execution_count": 17,
525 | "id": "c4571331",
526 | "metadata": {},
527 | "outputs": [
528 | {
529 | "name": "stdout",
530 | "output_type": "stream",
531 | "text": [
532 | "l1 : [3, [66, 44], (7, 8, 9), 100]\n",
533 | "l2 : [3, [66, 44], (7, 8, 9)]\n",
534 | "\n",
535 | "l1 : [3, [66, 44, 33, 22], (7, 8, 9), 100]\n",
536 | "l2 : [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]\n",
537 | "\n"
538 | ]
539 | }
540 | ],
541 | "source": [
542 | "#example 6-6\n",
543 | "l1 = [3, [66, 55, 44], (7, 8, 9)]\n",
544 | "l2 = list(l1)\n",
545 | "\n",
546 | "l1.append(100)\n",
547 | "l1[1].remove(55)\n",
548 | "\n",
549 | "print(f'l1 : {l1}')\n",
550 | "print(f'l2 : {l2}\\n')\n",
551 | "\n",
552 | "l2[1] += [33, 22] # Changes the list in place so this change happens in l1 too.\n",
553 | "l2[2] += (10, 11) # Creates a new tuple with new id so this change only happen in l2.\n",
554 | "\n",
555 | "print(f'l1 : {l1}')\n",
556 | "print(f'l2 : {l2}\\n')"
557 | ]
558 | },
559 | {
560 | "cell_type": "markdown",
561 | "id": "b7684bee",
562 | "metadata": {},
563 | "source": [
564 | "### Deep and Shallow Copies of Arbitrary Objects"
565 | ]
566 | },
567 | {
568 | "cell_type": "markdown",
569 | "id": "34df02fa",
570 | "metadata": {},
571 | "source": [
572 | "Note that making deep copies is not a simple matter in the general case. Objects may have cyclic references that would cause a naive algorithm to enter an infinite loop. The deepcopy function remembers the objects already copied to handle cyclic references gracefully."
573 | ]
574 | },
575 | {
576 | "cell_type": "code",
577 | "execution_count": 11,
578 | "id": "801eb443",
579 | "metadata": {},
580 | "outputs": [],
581 | "source": [
582 | "# Example 6-8. Bus picks up and drops off passengers\n",
583 | "class Bus:\n",
584 | " def __init__(self, passengers=None):\n",
585 | " if passengers is None:\n",
586 | " self.passengers = []\n",
587 | " else:\n",
588 | " self.passengers = list(passengers)\n",
589 | " \n",
590 | " def pick(self, name):\n",
591 | " self.passengers.append(name)\n",
592 | " \n",
593 | " def drop(self, name):\n",
594 | " self.passengers.remove(name)"
595 | ]
596 | },
597 | {
598 | "cell_type": "code",
599 | "execution_count": 12,
600 | "id": "5c128570",
601 | "metadata": {},
602 | "outputs": [],
603 | "source": [
604 | "import copy\n",
605 | "bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])\n",
606 | "bus2 = copy.copy(bus1) # bus2 is a shallow copy of bus1\n",
607 | "bus3 = copy.deepcopy(bus1)\n",
608 | "bus1.drop('Bill')\n"
609 | ]
610 | },
611 | {
612 | "cell_type": "code",
613 | "execution_count": 13,
614 | "id": "2ee44ed8",
615 | "metadata": {},
616 | "outputs": [
617 | {
618 | "data": {
619 | "text/plain": [
620 | "['Alice', 'Claire', 'David']"
621 | ]
622 | },
623 | "execution_count": 13,
624 | "metadata": {},
625 | "output_type": "execute_result"
626 | }
627 | ],
628 | "source": [
629 | "bus2.passengers"
630 | ]
631 | },
632 | {
633 | "cell_type": "code",
634 | "execution_count": 14,
635 | "id": "5de69f8f",
636 | "metadata": {},
637 | "outputs": [
638 | {
639 | "data": {
640 | "text/plain": [
641 | "['Alice', 'Bill', 'Claire', 'David']"
642 | ]
643 | },
644 | "execution_count": 14,
645 | "metadata": {},
646 | "output_type": "execute_result"
647 | }
648 | ],
649 | "source": [
650 | "bus3.passengers"
651 | ]
652 | },
653 | {
654 | "cell_type": "markdown",
655 | "id": "3543ab22",
656 | "metadata": {},
657 | "source": [
658 | "#### Cyclic references"
659 | ]
660 | },
661 | {
662 | "cell_type": "code",
663 | "execution_count": 15,
664 | "id": "93bf0a06",
665 | "metadata": {},
666 | "outputs": [],
667 | "source": [
668 | "# Example 6-10. Cyclic references: b refers to a, and then is appended to a; deepcopy still manages to copy a\n",
669 | "a = [10, 20]\n",
670 | "b = [a, 30]\n",
671 | "a.append(b)"
672 | ]
673 | },
674 | {
675 | "cell_type": "code",
676 | "execution_count": 16,
677 | "id": "cd788198",
678 | "metadata": {},
679 | "outputs": [
680 | {
681 | "data": {
682 | "text/plain": [
683 | "[10, 20, [[...], 30]]"
684 | ]
685 | },
686 | "execution_count": 16,
687 | "metadata": {},
688 | "output_type": "execute_result"
689 | }
690 | ],
691 | "source": [
692 | "a"
693 | ]
694 | },
695 | {
696 | "cell_type": "code",
697 | "execution_count": 17,
698 | "id": "6c1da922",
699 | "metadata": {},
700 | "outputs": [],
701 | "source": [
702 | "from copy import deepcopy\n",
703 | "c = deepcopy(a)"
704 | ]
705 | },
706 | {
707 | "cell_type": "code",
708 | "execution_count": 18,
709 | "id": "d53ad202",
710 | "metadata": {},
711 | "outputs": [
712 | {
713 | "data": {
714 | "text/plain": [
715 | "[10, 20, [[...], 30]]"
716 | ]
717 | },
718 | "execution_count": 18,
719 | "metadata": {},
720 | "output_type": "execute_result"
721 | }
722 | ],
723 | "source": [
724 | "c"
725 | ]
726 | },
727 | {
728 | "cell_type": "markdown",
729 | "id": "1ae98879",
730 | "metadata": {},
731 | "source": [
732 | "To customize the behavior of `copy()` or `deepcopy()` we must implement `__copy__()` or `__deepcopy__()`"
733 | ]
734 | },
735 | {
736 | "cell_type": "markdown",
737 | "id": "7a742c6f",
738 | "metadata": {},
739 | "source": [
740 | "## Function Parameters as References\n",
741 | "\n",
742 | "Understanding how function parameters work as references is crucial for writing correct and efficient code.\n"
743 | ]
744 | },
745 | {
746 | "cell_type": "markdown",
747 | "id": "769f22d3",
748 | "metadata": {},
749 | "source": [
750 | "Python call functions with call by sharing method."
751 | ]
752 | },
753 | {
754 | "cell_type": "markdown",
755 | "id": "1c256089",
756 | "metadata": {},
757 | "source": [
758 | "Call by sharing means that each formal parameter of the function gets a copy of each reference in the arguments. In other words, the parameters inside the function become aliases of the actual arguments. \n",
759 | "The result of this scheme is that a function may change any mutable object passed as a parameter, but it cannot change the identity of those objects"
760 | ]
761 | },
762 | {
763 | "cell_type": "code",
764 | "execution_count": 26,
765 | "id": "a22924d7",
766 | "metadata": {},
767 | "outputs": [],
768 | "source": [
769 | "# Example 6-11. A function may change any mutable object it receives\n",
770 | "def f(a, b):\n",
771 | " a += b\n",
772 | " return a\n",
773 | "x = 1\n",
774 | "y = 2\n"
775 | ]
776 | },
777 | {
778 | "cell_type": "code",
779 | "execution_count": 27,
780 | "id": "d115f1ef",
781 | "metadata": {},
782 | "outputs": [
783 | {
784 | "data": {
785 | "text/plain": [
786 | "3"
787 | ]
788 | },
789 | "execution_count": 27,
790 | "metadata": {},
791 | "output_type": "execute_result"
792 | }
793 | ],
794 | "source": [
795 | "f(x, y)"
796 | ]
797 | },
798 | {
799 | "cell_type": "code",
800 | "execution_count": 28,
801 | "id": "5c002068",
802 | "metadata": {},
803 | "outputs": [
804 | {
805 | "data": {
806 | "text/plain": [
807 | "(1, 2)"
808 | ]
809 | },
810 | "execution_count": 28,
811 | "metadata": {},
812 | "output_type": "execute_result"
813 | }
814 | ],
815 | "source": [
816 | "x, y"
817 | ]
818 | },
819 | {
820 | "cell_type": "code",
821 | "execution_count": 29,
822 | "id": "98c5d70f",
823 | "metadata": {},
824 | "outputs": [],
825 | "source": [
826 | "a = [1, 2]\n",
827 | "b = [3, 4]"
828 | ]
829 | },
830 | {
831 | "cell_type": "code",
832 | "execution_count": 30,
833 | "id": "813bce22",
834 | "metadata": {},
835 | "outputs": [
836 | {
837 | "data": {
838 | "text/plain": [
839 | "[1, 2, 3, 4]"
840 | ]
841 | },
842 | "execution_count": 30,
843 | "metadata": {},
844 | "output_type": "execute_result"
845 | }
846 | ],
847 | "source": [
848 | "f(a, b)"
849 | ]
850 | },
851 | {
852 | "cell_type": "code",
853 | "execution_count": 31,
854 | "id": "3fb03526",
855 | "metadata": {},
856 | "outputs": [
857 | {
858 | "data": {
859 | "text/plain": [
860 | "([1, 2, 3, 4], [3, 4])"
861 | ]
862 | },
863 | "execution_count": 31,
864 | "metadata": {},
865 | "output_type": "execute_result"
866 | }
867 | ],
868 | "source": [
869 | "a, b"
870 | ]
871 | },
872 | {
873 | "cell_type": "code",
874 | "execution_count": 32,
875 | "id": "985214d2",
876 | "metadata": {},
877 | "outputs": [],
878 | "source": [
879 | "t = (10, 20)\n",
880 | "u = (30, 40)"
881 | ]
882 | },
883 | {
884 | "cell_type": "code",
885 | "execution_count": 33,
886 | "id": "34354156",
887 | "metadata": {},
888 | "outputs": [
889 | {
890 | "data": {
891 | "text/plain": [
892 | "(10, 20, 30, 40)"
893 | ]
894 | },
895 | "execution_count": 33,
896 | "metadata": {},
897 | "output_type": "execute_result"
898 | }
899 | ],
900 | "source": [
901 | "f(t, u)"
902 | ]
903 | },
904 | {
905 | "cell_type": "code",
906 | "execution_count": 34,
907 | "id": "f6e2e809",
908 | "metadata": {},
909 | "outputs": [
910 | {
911 | "data": {
912 | "text/plain": [
913 | "((10, 20), (30, 40))"
914 | ]
915 | },
916 | "execution_count": 34,
917 | "metadata": {},
918 | "output_type": "execute_result"
919 | }
920 | ],
921 | "source": [
922 | "t, u"
923 | ]
924 | },
925 | {
926 | "cell_type": "markdown",
927 | "id": "4f40fd48",
928 | "metadata": {},
929 | "source": [
930 | "## Mutable Types as Parameter Defaults: Bad Idea\n",
931 | "\n",
932 | "When defining a function with mutable default arguments, such as lists or dictionaries, you should be cautious. The default value is evaluated only once, when the function is defined. This can lead to unexpected behavior, especially when multiple calls to the function share the same default object.\n"
933 | ]
934 | },
935 | {
936 | "cell_type": "code",
937 | "execution_count": 19,
938 | "id": "a3dd4302",
939 | "metadata": {},
940 | "outputs": [],
941 | "source": [
942 | "class HauntedBus:\n",
943 | " \"\"\"A bus model haunted by ghost passengers\"\"\"\n",
944 | "\n",
945 | " def __init__(self, passengers=[]):\n",
946 | " self.passengers = passengers\n",
947 | "\n",
948 | " def pick(self, name):\n",
949 | " self.passengers.append(name)\n",
950 | "\n",
951 | " def drop(self, name):\n",
952 | " self.passengers.remove(name)"
953 | ]
954 | },
955 | {
956 | "cell_type": "code",
957 | "execution_count": 36,
958 | "id": "6d257984",
959 | "metadata": {},
960 | "outputs": [],
961 | "source": [
962 | "bus1 = HauntedBus(['Alice', 'Bill'])"
963 | ]
964 | },
965 | {
966 | "cell_type": "code",
967 | "execution_count": 37,
968 | "id": "19e58490",
969 | "metadata": {},
970 | "outputs": [
971 | {
972 | "data": {
973 | "text/plain": [
974 | "['Alice', 'Bill']"
975 | ]
976 | },
977 | "execution_count": 37,
978 | "metadata": {},
979 | "output_type": "execute_result"
980 | }
981 | ],
982 | "source": [
983 | "bus1.passengers"
984 | ]
985 | },
986 | {
987 | "cell_type": "code",
988 | "execution_count": 38,
989 | "id": "4d4e6838",
990 | "metadata": {},
991 | "outputs": [],
992 | "source": [
993 | "bus1.pick('Charlie')\n",
994 | "bus1.drop('Alice')"
995 | ]
996 | },
997 | {
998 | "cell_type": "code",
999 | "execution_count": 39,
1000 | "id": "ffeff443",
1001 | "metadata": {},
1002 | "outputs": [
1003 | {
1004 | "data": {
1005 | "text/plain": [
1006 | "['Bill', 'Charlie']"
1007 | ]
1008 | },
1009 | "execution_count": 39,
1010 | "metadata": {},
1011 | "output_type": "execute_result"
1012 | }
1013 | ],
1014 | "source": [
1015 | "bus1.passengers"
1016 | ]
1017 | },
1018 | {
1019 | "cell_type": "code",
1020 | "execution_count": 40,
1021 | "id": "4be7d422",
1022 | "metadata": {},
1023 | "outputs": [],
1024 | "source": [
1025 | "bus2 = HauntedBus()\n",
1026 | "bus2.pick('Carrie')"
1027 | ]
1028 | },
1029 | {
1030 | "cell_type": "code",
1031 | "execution_count": 41,
1032 | "id": "a54d36e4",
1033 | "metadata": {},
1034 | "outputs": [
1035 | {
1036 | "data": {
1037 | "text/plain": [
1038 | "['Carrie']"
1039 | ]
1040 | },
1041 | "execution_count": 41,
1042 | "metadata": {},
1043 | "output_type": "execute_result"
1044 | }
1045 | ],
1046 | "source": [
1047 | "bus2.passengers"
1048 | ]
1049 | },
1050 | {
1051 | "cell_type": "code",
1052 | "execution_count": 42,
1053 | "id": "5850feec",
1054 | "metadata": {},
1055 | "outputs": [],
1056 | "source": [
1057 | "bus3 = HauntedBus()"
1058 | ]
1059 | },
1060 | {
1061 | "cell_type": "code",
1062 | "execution_count": 43,
1063 | "id": "5927b03f",
1064 | "metadata": {},
1065 | "outputs": [
1066 | {
1067 | "data": {
1068 | "text/plain": [
1069 | "['Carrie']"
1070 | ]
1071 | },
1072 | "execution_count": 43,
1073 | "metadata": {},
1074 | "output_type": "execute_result"
1075 | }
1076 | ],
1077 | "source": [
1078 | "bus3.passengers"
1079 | ]
1080 | },
1081 | {
1082 | "cell_type": "code",
1083 | "execution_count": 44,
1084 | "id": "994d794a",
1085 | "metadata": {},
1086 | "outputs": [],
1087 | "source": [
1088 | "bus3.pick('Dave')"
1089 | ]
1090 | },
1091 | {
1092 | "cell_type": "code",
1093 | "execution_count": 45,
1094 | "id": "5a44be15",
1095 | "metadata": {},
1096 | "outputs": [
1097 | {
1098 | "data": {
1099 | "text/plain": [
1100 | "['Carrie', 'Dave']"
1101 | ]
1102 | },
1103 | "execution_count": 45,
1104 | "metadata": {},
1105 | "output_type": "execute_result"
1106 | }
1107 | ],
1108 | "source": [
1109 | "bus2.passengers"
1110 | ]
1111 | },
1112 | {
1113 | "cell_type": "code",
1114 | "execution_count": 46,
1115 | "id": "f5d2f759",
1116 | "metadata": {},
1117 | "outputs": [
1118 | {
1119 | "data": {
1120 | "text/plain": [
1121 | "True"
1122 | ]
1123 | },
1124 | "execution_count": 46,
1125 | "metadata": {},
1126 | "output_type": "execute_result"
1127 | }
1128 | ],
1129 | "source": [
1130 | "bus2.passengers is bus3.passengers"
1131 | ]
1132 | },
1133 | {
1134 | "cell_type": "code",
1135 | "execution_count": 47,
1136 | "id": "11d4891e",
1137 | "metadata": {},
1138 | "outputs": [
1139 | {
1140 | "data": {
1141 | "text/plain": [
1142 | "['Bill', 'Charlie']"
1143 | ]
1144 | },
1145 | "execution_count": 47,
1146 | "metadata": {},
1147 | "output_type": "execute_result"
1148 | }
1149 | ],
1150 | "source": [
1151 | "bus1.passengers"
1152 | ]
1153 | },
1154 | {
1155 | "cell_type": "code",
1156 | "execution_count": 48,
1157 | "id": "70ebd01b",
1158 | "metadata": {},
1159 | "outputs": [
1160 | {
1161 | "data": {
1162 | "text/plain": [
1163 | "['__annotations__',\n",
1164 | " '__builtins__',\n",
1165 | " '__call__',\n",
1166 | " '__class__',\n",
1167 | " '__closure__',\n",
1168 | " '__code__',\n",
1169 | " '__defaults__',\n",
1170 | " '__delattr__',\n",
1171 | " '__dict__',\n",
1172 | " '__dir__',\n",
1173 | " '__doc__',\n",
1174 | " '__eq__',\n",
1175 | " '__format__',\n",
1176 | " '__ge__',\n",
1177 | " '__get__',\n",
1178 | " '__getattribute__',\n",
1179 | " '__globals__',\n",
1180 | " '__gt__',\n",
1181 | " '__hash__',\n",
1182 | " '__init__',\n",
1183 | " '__init_subclass__',\n",
1184 | " '__kwdefaults__',\n",
1185 | " '__le__',\n",
1186 | " '__lt__',\n",
1187 | " '__module__',\n",
1188 | " '__name__',\n",
1189 | " '__ne__',\n",
1190 | " '__new__',\n",
1191 | " '__qualname__',\n",
1192 | " '__reduce__',\n",
1193 | " '__reduce_ex__',\n",
1194 | " '__repr__',\n",
1195 | " '__setattr__',\n",
1196 | " '__sizeof__',\n",
1197 | " '__str__',\n",
1198 | " '__subclasshook__']"
1199 | ]
1200 | },
1201 | "execution_count": 48,
1202 | "metadata": {},
1203 | "output_type": "execute_result"
1204 | }
1205 | ],
1206 | "source": [
1207 | "dir(HauntedBus.__init__)"
1208 | ]
1209 | },
1210 | {
1211 | "cell_type": "code",
1212 | "execution_count": 49,
1213 | "id": "beb99a2b",
1214 | "metadata": {},
1215 | "outputs": [
1216 | {
1217 | "data": {
1218 | "text/plain": [
1219 | "(['Carrie', 'Dave'],)"
1220 | ]
1221 | },
1222 | "execution_count": 49,
1223 | "metadata": {},
1224 | "output_type": "execute_result"
1225 | }
1226 | ],
1227 | "source": [
1228 | "HauntedBus.__init__.__defaults__"
1229 | ]
1230 | },
1231 | {
1232 | "cell_type": "code",
1233 | "execution_count": 50,
1234 | "id": "aedfe3c5",
1235 | "metadata": {},
1236 | "outputs": [
1237 | {
1238 | "data": {
1239 | "text/plain": [
1240 | "True"
1241 | ]
1242 | },
1243 | "execution_count": 50,
1244 | "metadata": {},
1245 | "output_type": "execute_result"
1246 | }
1247 | ],
1248 | "source": [
1249 | "HauntedBus.__init__.__defaults__[0] is bus2.passengers"
1250 | ]
1251 | },
1252 | {
1253 | "cell_type": "code",
1254 | "execution_count": 20,
1255 | "id": "0f8d9e18",
1256 | "metadata": {},
1257 | "outputs": [
1258 | {
1259 | "data": {
1260 | "text/plain": [
1261 | "mappingproxy({'__module__': '__main__',\n",
1262 | " '__doc__': 'A bus model haunted by ghost passengers',\n",
1263 | " '__init__': ,\n",
1264 | " 'pick': ,\n",
1265 | " 'drop': ,\n",
1266 | " '__dict__': ,\n",
1267 | " '__weakref__': })"
1268 | ]
1269 | },
1270 | "execution_count": 20,
1271 | "metadata": {},
1272 | "output_type": "execute_result"
1273 | }
1274 | ],
1275 | "source": [
1276 | "HauntedBus.__dict__"
1277 | ]
1278 | },
1279 | {
1280 | "cell_type": "markdown",
1281 | "id": "856c9d82",
1282 | "metadata": {},
1283 | "source": [
1284 | "## Defensive Programming with Mutable Parameters\n",
1285 | "\n",
1286 | "When a function receives a mutable parameter, you should consider whether the caller expects the argument to be changed or not. This is about aligning the expectations of the coder of the function and the caller.\n"
1287 | ]
1288 | },
1289 | {
1290 | "cell_type": "code",
1291 | "execution_count": 51,
1292 | "id": "d61beb83",
1293 | "metadata": {},
1294 | "outputs": [],
1295 | "source": [
1296 | "class TwilightBus:\n",
1297 | " \"\"\"A bus model that makes passengers vanish\"\"\"\n",
1298 | "\n",
1299 | " def __init__(self, passengers=None):\n",
1300 | " if passengers is None:\n",
1301 | " self.passengers = []\n",
1302 | " else:\n",
1303 | " self.passengers = passengers\n",
1304 | "\n",
1305 | " def pick(self, name):\n",
1306 | " self.passengers.append(name)\n",
1307 | "\n",
1308 | " def drop(self, name):\n",
1309 | " self.passengers.remove(name)"
1310 | ]
1311 | },
1312 | {
1313 | "cell_type": "code",
1314 | "execution_count": 52,
1315 | "id": "e668ff0f",
1316 | "metadata": {},
1317 | "outputs": [
1318 | {
1319 | "data": {
1320 | "text/plain": [
1321 | "['Sue', 'Maya', 'Diana']"
1322 | ]
1323 | },
1324 | "execution_count": 52,
1325 | "metadata": {},
1326 | "output_type": "execute_result"
1327 | }
1328 | ],
1329 | "source": [
1330 | "basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']\n",
1331 | "bus = TwilightBus(basketball_team)\n",
1332 | "bus.drop('Tina')\n",
1333 | "bus.drop('Pat')\n",
1334 | "basketball_team"
1335 | ]
1336 | },
1337 | {
1338 | "cell_type": "code",
1339 | "execution_count": 53,
1340 | "id": "20a34fcc",
1341 | "metadata": {},
1342 | "outputs": [],
1343 | "source": [
1344 | "class TwilightBus:\n",
1345 | " \"\"\"A bus model that makes passengers vanish\"\"\"\n",
1346 | "\n",
1347 | " def __init__(self, passengers=None):\n",
1348 | " if passengers is None:\n",
1349 | " self.passengers = []\n",
1350 | " else:\n",
1351 | " self.passengers = list(passengers)\n",
1352 | "\n",
1353 | " def pick(self, name):\n",
1354 | " self.passengers.append(name)\n",
1355 | "\n",
1356 | " def drop(self, name):\n",
1357 | " self.passengers.remove(name)"
1358 | ]
1359 | },
1360 | {
1361 | "cell_type": "code",
1362 | "execution_count": 54,
1363 | "id": "7c5b2669",
1364 | "metadata": {},
1365 | "outputs": [
1366 | {
1367 | "data": {
1368 | "text/plain": [
1369 | "['Sue', 'Tina', 'Maya', 'Diana', 'Pat']"
1370 | ]
1371 | },
1372 | "execution_count": 54,
1373 | "metadata": {},
1374 | "output_type": "execute_result"
1375 | }
1376 | ],
1377 | "source": [
1378 | "basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']\n",
1379 | "bus = TwilightBus(basketball_team)\n",
1380 | "bus.drop('Tina')\n",
1381 | "bus.drop('Pat')\n",
1382 | "basketball_team"
1383 | ]
1384 | },
1385 | {
1386 | "cell_type": "markdown",
1387 | "id": "235201e6",
1388 | "metadata": {},
1389 | "source": [
1390 | "## del and Garbage Collection\n",
1391 | "\n",
1392 | "The del statement in Python doesn't delete objects. Instead, it deletes references to objects. When the number of references to an object reaches zero, the object may be garbage collected. The __del__ special method, when defined, allows an object to release external resources before being destroyed.\n"
1393 | ]
1394 | },
1395 | {
1396 | "cell_type": "code",
1397 | "execution_count": 21,
1398 | "id": "b8ec4a76",
1399 | "metadata": {},
1400 | "outputs": [
1401 | {
1402 | "data": {
1403 | "text/plain": [
1404 | "[1, 2]"
1405 | ]
1406 | },
1407 | "execution_count": 21,
1408 | "metadata": {},
1409 | "output_type": "execute_result"
1410 | }
1411 | ],
1412 | "source": [
1413 | "a = [1, 2]\n",
1414 | "b = a\n",
1415 | "del a\n",
1416 | "b"
1417 | ]
1418 | },
1419 | {
1420 | "cell_type": "code",
1421 | "execution_count": 22,
1422 | "id": "ae3a5cf5",
1423 | "metadata": {},
1424 | "outputs": [],
1425 | "source": [
1426 | "b = [3] #Rebinding b to a different object removes the last remaining reference to [1, 2]."
1427 | ]
1428 | },
1429 | {
1430 | "cell_type": "markdown",
1431 | "id": "6dd70e83",
1432 | "metadata": {},
1433 | "source": [
1434 | "**Note**\n",
1435 | "* There is a `__del__` special method, but it does not cause the disposal\n",
1436 | "of the instance, and **should not be called by your code**.\n",
1437 | "* `__del__` is invoked by the Python interpreter when the instance is\n",
1438 | "about to be destroyed to give it a chance to release external\n",
1439 | "resources. \n",
1440 | "* You will seldom need to implement `__del__` in your\n",
1441 | "own code.\n",
1442 | "* The proper use of `__del__` is rather tricky. See the\n",
1443 | "`__del__` special method documentation in the “Data Model” chapter\n",
1444 | "of The Python Language Reference"
1445 | ]
1446 | },
1447 | {
1448 | "cell_type": "code",
1449 | "execution_count": 23,
1450 | "id": "393af146",
1451 | "metadata": {},
1452 | "outputs": [
1453 | {
1454 | "data": {
1455 | "text/plain": [
1456 | "True"
1457 | ]
1458 | },
1459 | "execution_count": 23,
1460 | "metadata": {},
1461 | "output_type": "execute_result"
1462 | }
1463 | ],
1464 | "source": [
1465 | "# Example 6-16. Watching the end of an object when no more references point to it\n",
1466 | "import weakref\n",
1467 | "s1 = {1, 2, 3}\n",
1468 | "s2 = s1\n",
1469 | "def bye():\n",
1470 | " print('...like tears in the rain.')\n",
1471 | "\n",
1472 | "ender = weakref.finalize(s1, bye)\n",
1473 | "ender.alive"
1474 | ]
1475 | },
1476 | {
1477 | "cell_type": "markdown",
1478 | "id": "8de4cc96",
1479 | "metadata": {},
1480 | "source": [
1481 | "**Weak reference**\n",
1482 | "\n",
1483 | "* `finalize` method holds a `weak reference` to `{1, 2, 3}`.\n",
1484 | "* Weak reference to an object does not increase its reference count.\n",
1485 | "* It does not prevent the target object from being garbage collected.\n",
1486 | "* Weak references are useful in caching applications. "
1487 | ]
1488 | },
1489 | {
1490 | "cell_type": "code",
1491 | "execution_count": 24,
1492 | "id": "8360c5a0",
1493 | "metadata": {},
1494 | "outputs": [
1495 | {
1496 | "data": {
1497 | "text/plain": [
1498 | "True"
1499 | ]
1500 | },
1501 | "execution_count": 24,
1502 | "metadata": {},
1503 | "output_type": "execute_result"
1504 | }
1505 | ],
1506 | "source": [
1507 | "del s1\n",
1508 | "ender.alive"
1509 | ]
1510 | },
1511 | {
1512 | "cell_type": "code",
1513 | "execution_count": 25,
1514 | "id": "605d5e06",
1515 | "metadata": {},
1516 | "outputs": [
1517 | {
1518 | "name": "stdout",
1519 | "output_type": "stream",
1520 | "text": [
1521 | "...like tears in the rain.\n"
1522 | ]
1523 | }
1524 | ],
1525 | "source": [
1526 | "#Rebinding the last reference, s2, makes {1, 2, 3} unreachable.It is destroyed,the bye callback is invoked.\n",
1527 | "s2 = 'spam' "
1528 | ]
1529 | },
1530 | {
1531 | "cell_type": "code",
1532 | "execution_count": 26,
1533 | "id": "20e90ec4",
1534 | "metadata": {},
1535 | "outputs": [
1536 | {
1537 | "data": {
1538 | "text/plain": [
1539 | "False"
1540 | ]
1541 | },
1542 | "execution_count": 26,
1543 | "metadata": {},
1544 | "output_type": "execute_result"
1545 | }
1546 | ],
1547 | "source": [
1548 | "ender.alive"
1549 | ]
1550 | },
1551 | {
1552 | "cell_type": "code",
1553 | "execution_count": 61,
1554 | "id": "84daba32",
1555 | "metadata": {},
1556 | "outputs": [],
1557 | "source": [
1558 | "class Cheese:\n",
1559 | "\n",
1560 | " def __init__(self, kind):\n",
1561 | " self.kind = kind\n",
1562 | "\n",
1563 | " def __repr__(self):\n",
1564 | " return f'Cheese({self.kind!r})'"
1565 | ]
1566 | },
1567 | {
1568 | "cell_type": "code",
1569 | "execution_count": 62,
1570 | "id": "834d234d",
1571 | "metadata": {},
1572 | "outputs": [],
1573 | "source": [
1574 | "import weakref\n",
1575 | "stock = weakref.WeakValueDictionary()\n",
1576 | "catalog = [Cheese('Red Leicester'), Cheese('Tilsit'),\n",
1577 | " Cheese('Brie'), Cheese('Parmesan')]\n",
1578 | "\n",
1579 | "for cheese in catalog:\n",
1580 | " stock[cheese.kind] = cheese\n"
1581 | ]
1582 | },
1583 | {
1584 | "cell_type": "code",
1585 | "execution_count": 63,
1586 | "id": "474731a8",
1587 | "metadata": {},
1588 | "outputs": [
1589 | {
1590 | "data": {
1591 | "text/plain": [
1592 | "['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']"
1593 | ]
1594 | },
1595 | "execution_count": 63,
1596 | "metadata": {},
1597 | "output_type": "execute_result"
1598 | }
1599 | ],
1600 | "source": [
1601 | "sorted(stock.keys())"
1602 | ]
1603 | },
1604 | {
1605 | "cell_type": "code",
1606 | "execution_count": 64,
1607 | "id": "04722ae5",
1608 | "metadata": {},
1609 | "outputs": [
1610 | {
1611 | "data": {
1612 | "text/plain": [
1613 | "['Parmesan']"
1614 | ]
1615 | },
1616 | "execution_count": 64,
1617 | "metadata": {},
1618 | "output_type": "execute_result"
1619 | }
1620 | ],
1621 | "source": [
1622 | "del catalog\n",
1623 | "sorted(stock.keys())"
1624 | ]
1625 | },
1626 | {
1627 | "cell_type": "code",
1628 | "execution_count": 65,
1629 | "id": "428dc212",
1630 | "metadata": {},
1631 | "outputs": [
1632 | {
1633 | "data": {
1634 | "text/plain": [
1635 | "[]"
1636 | ]
1637 | },
1638 | "execution_count": 65,
1639 | "metadata": {},
1640 | "output_type": "execute_result"
1641 | }
1642 | ],
1643 | "source": [
1644 | "del cheese\n",
1645 | "sorted(stock.keys())"
1646 | ]
1647 | },
1648 | {
1649 | "cell_type": "markdown",
1650 | "id": "ba6beae7",
1651 | "metadata": {},
1652 | "source": [
1653 | "## Tricks Python Plays with Immutables\n",
1654 | "\n",
1655 | "Python has some unique behaviors when it comes to immutables like tuples, strings, bytes, and frozensets. For instance, creating a new tuple from another doesn't create a copy but returns a reference to the same object. String literals may also create shared objects as an optimization technique called interning.\n"
1656 | ]
1657 | },
1658 | {
1659 | "cell_type": "code",
1660 | "execution_count": 27,
1661 | "id": "99b303a1",
1662 | "metadata": {},
1663 | "outputs": [],
1664 | "source": [
1665 | "t1 = (1, 2, 3)\n",
1666 | "t2 = tuple(t1)"
1667 | ]
1668 | },
1669 | {
1670 | "cell_type": "code",
1671 | "execution_count": 28,
1672 | "id": "aa7d3248",
1673 | "metadata": {},
1674 | "outputs": [
1675 | {
1676 | "data": {
1677 | "text/plain": [
1678 | "True"
1679 | ]
1680 | },
1681 | "execution_count": 28,
1682 | "metadata": {},
1683 | "output_type": "execute_result"
1684 | }
1685 | ],
1686 | "source": [
1687 | "t2 is t1"
1688 | ]
1689 | },
1690 | {
1691 | "cell_type": "code",
1692 | "execution_count": 29,
1693 | "id": "089920dc",
1694 | "metadata": {},
1695 | "outputs": [
1696 | {
1697 | "data": {
1698 | "text/plain": [
1699 | "True"
1700 | ]
1701 | },
1702 | "execution_count": 29,
1703 | "metadata": {},
1704 | "output_type": "execute_result"
1705 | }
1706 | ],
1707 | "source": [
1708 | "t3 = t1[:]\n",
1709 | "t3 is t1"
1710 | ]
1711 | },
1712 | {
1713 | "cell_type": "code",
1714 | "execution_count": 30,
1715 | "id": "8e179981",
1716 | "metadata": {},
1717 | "outputs": [
1718 | {
1719 | "data": {
1720 | "text/plain": [
1721 | "False"
1722 | ]
1723 | },
1724 | "execution_count": 30,
1725 | "metadata": {},
1726 | "output_type": "execute_result"
1727 | }
1728 | ],
1729 | "source": [
1730 | "# Example 6-18. String literals may create shared objects\n",
1731 | "t1 = (1, 2, 3)\n",
1732 | "t3 = (1, 2, 3)\n",
1733 | "t3 is t1"
1734 | ]
1735 | },
1736 | {
1737 | "cell_type": "code",
1738 | "execution_count": 31,
1739 | "id": "c1433c67",
1740 | "metadata": {},
1741 | "outputs": [
1742 | {
1743 | "data": {
1744 | "text/plain": [
1745 | "True"
1746 | ]
1747 | },
1748 | "execution_count": 31,
1749 | "metadata": {},
1750 | "output_type": "execute_result"
1751 | }
1752 | ],
1753 | "source": [
1754 | "# Surprise: a and b refer to the same str!\n",
1755 | "s1 = 'ABC'\n",
1756 | "s2 = 'ABC'\n",
1757 | "s2 is s1 "
1758 | ]
1759 | },
1760 | {
1761 | "cell_type": "markdown",
1762 | "id": "cdb1463e",
1763 | "metadata": {},
1764 | "source": [
1765 | "\n",
1766 | "Remember to use == to compare strings or integers for equality instead of is. Interning is an optimization for internal use of the Python interpreter and should not impact your code.\n",
1767 | "\n",
1768 | "Understanding object references, mutability, and recycling is crucial for writing clean and efficient Python code. These concepts enable you to work with Python's rich data structures effectively while avoiding common pitfalls and unexpected behavior.\n"
1769 | ]
1770 | },
1771 | {
1772 | "cell_type": "markdown",
1773 | "id": "a4d33b63",
1774 | "metadata": {},
1775 | "source": [
1776 | "# Lecturers\n",
1777 | "\n",
1778 | "1. Mohammad Rajabi ..., [Linkedin](https://www.linkedin.com/in/mohammad-rajabi-me/)\n",
1779 | "2. Pooya Fekri, [Linkedin](https://www.linkedin.com/in/pooyafekri)\n",
1780 | "\n",
1781 | "\n",
1782 | "present date : 2023-10-27"
1783 | ]
1784 | },
1785 | {
1786 | "cell_type": "markdown",
1787 | "id": "eb00051f",
1788 | "metadata": {},
1789 | "source": [
1790 | "# Reviewers\n",
1791 | "\n",
1792 | "1. Hosein Toodehroosta, review date: 2023-10-26, [LinkedIn](https://www.linkedin.com/in/hossein-toodehroosta/)\n",
1793 | "2. Zohreh Alizadeh, review date: 2023-10-27, [LinkedIn](https://ir.linkedin.com/in/zohreh-bayramalizadeh/)\n",
1794 | "3. Mehran Faraji, review date: 2023-10-27, [LinkedIn](https://www.linkedin.com/in/mehranfaraji/)\n",
1795 | "4. Mahya Asgarian, review date: 2023-10-27, [LinkedIn](https://www.linkedin.com/in/mahya-asgarian-9a7b13249/)\n",
1796 | "5. Saeed Hemmati, review date: 2023-10-27, [LinkedIn](https://www.linkedin.com/in/saeed-hemati/)"
1797 | ]
1798 | }
1799 | ],
1800 | "metadata": {
1801 | "kernelspec": {
1802 | "display_name": "Python 3 (ipykernel)",
1803 | "language": "python",
1804 | "name": "python3"
1805 | },
1806 | "language_info": {
1807 | "codemirror_mode": {
1808 | "name": "ipython",
1809 | "version": 3
1810 | },
1811 | "file_extension": ".py",
1812 | "mimetype": "text/x-python",
1813 | "name": "python",
1814 | "nbconvert_exporter": "python",
1815 | "pygments_lexer": "ipython3",
1816 | "version": "3.10.12"
1817 | }
1818 | },
1819 | "nbformat": 4,
1820 | "nbformat_minor": 5
1821 | }
1822 |
--------------------------------------------------------------------------------
/chapter-07/notebook.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{"id":"kBBG-D_gtmEx"},"source":["## Functions as First-Class Objects"]},{"cell_type":"markdown","metadata":{"id":"F0cbdXiottTQ"},"source":["### Introduction"]},{"cell_type":"markdown","metadata":{"id":"31vsax1Dt0Ln"},"source":["\n","\n","Functions in Python are first-class objects:\n","\n","\n","* Created at runtime\n","* Assigned to a variable or element in a data structure\n","* Passed as an argument to a function\n","* Returned as the result of a function\n","* Examples of first-class objects in Python : `int`, `str`, `dict`\n"]},{"cell_type":"code","execution_count":1,"metadata":{"id":"x4DgIjmKta7j","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1699182491609,"user_tz":-210,"elapsed":10,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}},"outputId":"b764c423-9bc3-4eb1-d142-ff9e765639f4"},"outputs":[{"output_type":"stream","name":"stdout","text":["1405006117752879898543142606244511569936384000000000\n"," return n! \n","\n"]}],"source":["# Example 7-1\n","def factorial(n): # Create at runtime\n"," \"\"\" return n! \"\"\"\n"," return 1 if n < 2 else n * factorial(n -1) # Returned as the result of function\n","\n","print(factorial(42))\n","print(factorial.__doc__)\n","print(type(factorial))"]},{"cell_type":"code","execution_count":2,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"j-1scYImvgF0","outputId":"a35efea3-fa26-433b-c0ac-f8f21b0af455","executionInfo":{"status":"ok","timestamp":1699182492105,"user_tz":-210,"elapsed":20,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["\n","120\n","[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]\n"]}],"source":["fact = factorial # Assigned to variable\n","print(fact)\n","print(fact(5))\n","print(list(map(factorial, range(11)))) # Passed as an argument to a function"]},{"cell_type":"markdown","metadata":{"id":"buERQEZwxCFw"},"source":["### Higher-Order Functions"]},{"cell_type":"markdown","metadata":{"id":"zH-lumjXxG0G"},"source":["\n","\n","* A function that takes a function as an arguement or returns as the result.\n","* Examples : `map`, `filter`, `reduce`, `apply`, `sorted`\n","* `listcomp`, `genexp` are alternatives of `map`, `filter`\n","* `map`, `filter` return generators\n","* `reduce` must be imported from `functools`\n","\n"]},{"cell_type":"markdown","metadata":{"id":"JKf2u6NrYNO1"},"source":["The apply function was deprecated in Python 2.3 and removed in Python 3 because it’s no longer necessary. \n","If you need to call a function with a dynamic set of arguments, you can write fn(*args, **kwargs) instead of apply(fn, args, kwargs)."]},{"cell_type":"code","execution_count":3,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"g62rtvEDwfhB","outputId":"f36019e5-778e-40d9-a540-e8c61ffe2446","executionInfo":{"status":"ok","timestamp":1699182492106,"user_tz":-210,"elapsed":19,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"execute_result","data":{"text/plain":["['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']"]},"metadata":{},"execution_count":3}],"source":["# Example 7-3\n","fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']\n","sorted(fruits, key=len) # Any one-argument function can be used as the key"]},{"cell_type":"code","source":["# Example 7-4\n","def reverse(word):\n"," return word[::-1]\n","\n","sorted(fruits, key=reverse)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"4zImPyZVdhol","executionInfo":{"status":"ok","timestamp":1699182492106,"user_tz":-210,"elapsed":17,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}},"outputId":"f3629a2c-6bf7-4ebe-aaa3-d9a8fe7016e8"},"execution_count":4,"outputs":[{"output_type":"execute_result","data":{"text/plain":["['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']"]},"metadata":{},"execution_count":4}]},{"cell_type":"code","execution_count":5,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"k8ah9FGUx85y","outputId":"d650c02e-7489-4e91-ce7c-a0474ed8fa91","executionInfo":{"status":"ok","timestamp":1699182492106,"user_tz":-210,"elapsed":16,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["[1, 1, 2, 6, 24]\n","[1, 1, 2, 6, 24]\n"]}],"source":["print(list(map(factorial, range(5))))\n","print([factorial(n) for n in range(5)])"]},{"cell_type":"code","execution_count":6,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"VWrMb9wK5PXM","outputId":"6de27621-a82c-4d25-c397-f26eb807bf4f","executionInfo":{"status":"ok","timestamp":1699182492106,"user_tz":-210,"elapsed":15,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["[1, 6]\n","[1, 6]\n"]}],"source":["print(list(map(factorial, filter(lambda n : n % 2, range(5)))))\n","print([factorial(n) for n in range(5) if n % 2])"]},{"cell_type":"code","execution_count":7,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"XZd6VBaJ6Zn_","outputId":"e8c9f4b7-3820-4baf-e75f-b7458e55c176","executionInfo":{"status":"ok","timestamp":1699182492107,"user_tz":-210,"elapsed":15,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["reduce : 4950\n","without reduce : 4950\n"]}],"source":["from functools import reduce\n","from operator import add\n","\n","print(f'reduce : {reduce(add, range(100))}')\n","print(f'without reduce : {sum(range(100))}')"]},{"cell_type":"markdown","metadata":{"id":"02ms35dt7h1I"},"source":["* Common idea of `reduce` and `sum` is to apply some operation to successive items in a series, accumulatiing previous results, thus reducing a series of values to a single value.\n","* Other reducing built-in functions : `all`, `any`\n","* `all(iterable)` ----> Returns `True` if there are no falsy elements in the iterable. ----> `all([])` returns `True`.\n","* `any(iterable)` ----> Returns `True` if any element of the `iterable` is truthy. ----> `any([])` returns `False`."]},{"cell_type":"markdown","metadata":{"id":"bVzWwhwQ8stS"},"source":["#### Anonymous Functions"]},{"cell_type":"markdown","metadata":{"id":"R9xIuZ2y8zHv"},"source":["\n","\n","* Created by `lambda` keyword in a Python expression\n","* The body can not contain other python statements such as `while`, `try`, `=`\n","* `:=` can be used (better to replace `lambda` function with regular function).\n","* Useful in the context of an argument list for a higher-order function.\n","\n"]},{"cell_type":"code","execution_count":8,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"4rUjAlvi7Lt4","outputId":"d93dfd73-8de6-4d47-be38-2257762dc85e","executionInfo":{"status":"ok","timestamp":1699182492107,"user_tz":-210,"elapsed":15,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"execute_result","data":{"text/plain":["['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']"]},"metadata":{},"execution_count":8}],"source":["sorted(fruits, key=lambda word : word[::-1])"]},{"cell_type":"markdown","metadata":{"id":"BFIkmE1x-Mzc"},"source":["#### Nine Flavors of Callable Objects"]},{"cell_type":"markdown","metadata":{"id":"NZK4lSh9-UNG"},"source":["\n","\n","* Use `callable()` to determine whether an object is callable.\n","* Callable types:\n"," * User-defined functions : Created with `def`, `lambda`\n"," * Built-in functions : `len`, `time.strftime`, `type`\n"," * Built-in methods : `dict.get`, `list.append`\n"," * Methods : Functions defined in the body of a class\n"," * Classes : `__new__` method create an instance, `__init__` initialize it. Calling a class is like calling function.\n"," * Class instances : if class has `__call__`\n"," * Generator functions : Functions use `yield` in body\n"," * Native coroutine functions : Functions defined with `async def`, return a coroutine object.\n"," * Asynchronous generator functions : Functions defined with `async def`, have `yield` in body, return asynchronous generator, use with `async for`.\n","\n","* Native coroutine and asynchronous generator functions return objects that only work with the help of an asynchronous programming framework like `asyncio`.\n","\n"]},{"cell_type":"markdown","metadata":{"id":"fnkdfbrnBj9s"},"source":["#### User-Defined Callable Types"]},{"cell_type":"markdown","metadata":{"id":"5_5YXXwDBvW9"},"source":["\n","\n","* Arbitrary Python objects can behave like functions by implementing `__call__` instance method.\n","* Another good use case for `__call__` is implementing decorators.\n","\n"]},{"cell_type":"code","execution_count":9,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"gWIuHRn99lUp","outputId":"f6367437-8e55-4612-c5d9-68b802281316","executionInfo":{"status":"ok","timestamp":1699182492107,"user_tz":-210,"elapsed":14,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["1\n","2\n"]}],"source":["# Example 7-8\n","import random\n","\n","class BingoCage:\n","\n"," def __init__(self, items):\n"," self._items = list(items)\n"," random.shuffle(self._items)\n","\n"," def pick(self):\n"," try:\n"," return self._items.pop()\n"," except IndexError:\n"," raise LookupError('pick from empty BingoCage')\n","\n"," def __call__(self): # Shortcut to bingo.pick() : bingo()\n"," return self.pick()\n","\n","\n","bingo = BingoCage(range(3))\n","print(bingo.pick())\n","print(bingo())"]},{"cell_type":"markdown","metadata":{"id":"1bLSV0XLDFvD"},"source":["### From Positional to Keyword-Only Parameters"]},{"cell_type":"markdown","metadata":{"id":"ozAqadAnDM3C"},"source":["\n","\n","* `*`, `**` to unpack iterables and mappings into seperate arguments when calling a function\n","* To specify keyword-only arguments when defining a function, name them after the argument prefixed with `*`.\n","* Keyword-only arguments do not need to have a default value.\n","* If don't want to support variable positional arguments but still want keyword-only arguments, put `*` by itself in the signature.\n","\n"]},{"cell_type":"code","execution_count":10,"metadata":{"id":"qoHx9r_HCuLi","executionInfo":{"status":"ok","timestamp":1699182492107,"user_tz":-210,"elapsed":13,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[],"source":["# Example 7-9\n","def tag(name, *content, class_=None, **attrs):\n"," \"\"\"Generate one or more HTML tags\"\"\"\n"," if class_ is not None:\n"," attrs['class'] = class_\n"," attr_pairs = (f' {attr}=\"{value}\"' for attr, value\n"," in sorted(attrs.items()))\n"," attr_str = ''.join(attr_pairs)\n"," if content:\n"," elements = (f'<{name}{attr_str}>{c}{name}>'\n"," for c in content)\n"," return '\\n'.join(elements)\n"," else:\n"," return f'<{name}{attr_str} />'"]},{"cell_type":"code","execution_count":11,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"qcGbtAg7EMjj","outputId":"af52d1ed-cdd9-43aa-ba32-b772e92e83b0","executionInfo":{"status":"ok","timestamp":1699182492107,"user_tz":-210,"elapsed":12,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["
\n","-----------------------------\n","hello
\n","-----------------------------\n","hello
\n","world
\n","-----------------------------\n","hello
\n","-----------------------------\n","\n","\n","-----------------------------\n","
\n","-----------------------------\n","
\n"]}],"source":["print(tag('br'))\n","print('-----------------------------')\n","print(tag('p', 'hello')) # Any number of arguments after the first are captured by *content as a tuple\n","print('-----------------------------')\n","print(tag('p', 'hello', 'world'))\n","print('-----------------------------')\n","print(tag('p', 'hello', id=33)) # Keyword arguments not explicitly named in tag signature are captured by **attrs as dict\n","print('-----------------------------')\n","print(tag('p', 'hello', 'world', class_='sidebar')) # class_ paramter can only be passed as a keyword argument\n","print('-----------------------------')\n","print(tag(content='testing', name='img')) # first positional argument can also be passed as a keyword\n","print('-----------------------------')\n","my_tag = {'name' : 'img', 'title': 'Sunset Boulevard',\n"," 'src' : 'sunset.jpg', 'class' : 'framed'\n","}\n","\n","print(tag(**my_tag)) # passes all my_tag items as seperate arguments which are then bound to named parameters"]},{"cell_type":"markdown","metadata":{"id":"2RRazJVTGZiD"},"source":["#### Positional-Only Parameters"]},{"cell_type":"markdown","metadata":{"id":"4i64200UGeXd"},"source":["\n","\n","* To define a function requiring positional-only paramters, use `/` in parameter list.\n","* All arguments to the left of the `/` are positional-only. After `/` other arguments work as usual.\n","* Example : built-in `divmod` can only be called with positional parameters\n"]},{"cell_type":"code","execution_count":12,"metadata":{"id":"b2FmHEobFIFH","executionInfo":{"status":"ok","timestamp":1699182492107,"user_tz":-210,"elapsed":13,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[],"source":["# divmod(a=10, b=20) ----> TypeError\n","\n","def divmod(a, b, /):\n"," return (a // b, a % b)"]},{"cell_type":"markdown","metadata":{"id":"x6puWZoAHv_3"},"source":["### Packages for Functional Programming"]},{"cell_type":"markdown","metadata":{"id":"dnWr_V86H1u2"},"source":["\n","\n","* Two useful packages : `operator`, `functools`\n","* `operator` : Provides function equivalents for dozens of operators.\n","* `functools` : Provides higher-order functions\n"]},{"cell_type":"markdown","metadata":{"id":"CR0x7zGiIm6G"},"source":["#### The operator Module"]},{"cell_type":"markdown","metadata":{"id":"tJ9cEuHdJzUp"},"source":["\n","\n","* Arithmetic operators : `add`, `mul`\n","* `itemgetter` : Creates a function that given a collection, returns the item at index `i`, supports any class that implements `__getitem__`.\n","\n","* `attrgetter` : Creates a function to extract object attributes by name, can navigate through nested objects if contains `.` .\n","* `methodcaller` : It's similar to `itemgetter` and `attrgetter` in that it creates a function on the fly. The function it creates calls a method by name on the object given as argument.\n","\n"]},{"cell_type":"code","execution_count":13,"metadata":{"id":"ydQDZvE9HLcg","executionInfo":{"status":"ok","timestamp":1699182492108,"user_tz":-210,"elapsed":12,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[],"source":["# Example 7-11 , 7-12\n","from functools import reduce\n","from operator import mul\n","\n","\n","def factorial_no_operator(n):\n"," return reduce(lambda a, b : a * b, range(1, n + 1))\n","\n","def factorial_with_operator(n):\n"," return reduce(mul, range(1, n + 1))"]},{"cell_type":"code","execution_count":14,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"MDkDB_b3KjvW","outputId":"38e01757-0148-4a41-860c-27ac911802fe","executionInfo":{"status":"ok","timestamp":1699182492108,"user_tz":-210,"elapsed":12,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["('São Paulo', 'BR', 19.649, (-23.547778, -46.635833))\n","('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))\n","('Tokyo', 'JP', 36.933, (35.689722, 139.691667))\n","('Mexico City', 'MX', 20.142, (19.433333, -99.133333))\n","('New York-Newark', 'US', 20.104, (40.808611, -74.020386))\n","\n","-----------------------------------\n","\n","('JP', 'Tokyo')\n","('IN', 'Delhi NCR')\n","('MX', 'Mexico City')\n","('US', 'New York-Newark')\n","('BR', 'São Paulo')\n"]}],"source":["# Example 7-13\n","from operator import itemgetter\n","\n","\n","metro_data = [\n"," ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),\n"," ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),\n"," ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),\n"," ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),\n"," ('São Paulo', 'BR', 19.649, (-23.547778, -46.635833)),\n","]\n","\n","for city in sorted(metro_data, key=itemgetter(1)): # sort by index 1 which is country code\n"," print(city)\n","\n","print('\\n-----------------------------------\\n')\n","\n","cc_name = itemgetter(1, 0) # passing multiple index creates a function that return tuple with extracted values\n","for city in metro_data:\n"," print(cc_name(city))"]},{"cell_type":"code","execution_count":15,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"UVbHRbPPLemD","outputId":"f567d40d-90f8-403d-f0f3-ee67d9fd9bcc","executionInfo":{"status":"ok","timestamp":1699182492108,"user_tz":-210,"elapsed":11,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLon(lat=35.689722, lon=139.691667))\n","35.689722\n","\n","-----------------------------------\n","\n","('São Paulo', -23.547778)\n","('Mexico City', 19.433333)\n","('Delhi NCR', 28.613889)\n","('Tokyo', 35.689722)\n","('New York-Newark', 40.808611)\n"]}],"source":["# Example 7-14\n","from collections import namedtuple\n","from operator import attrgetter\n","\n","\n","LatLon = namedtuple('LatLon', 'lat lon')\n","Metropolis = namedtuple('Metropolis', 'name cc pop coord')\n","\n","metro_areas = [Metropolis(name, cc, pop, LatLon(lat, lon)) for name, cc, pop, (lat, lon) in metro_data]\n","\n","print(metro_areas[0])\n","print(metro_areas[0].coord.lat)\n","\n","print('\\n-----------------------------------\\n')\n","\n","name_lat = attrgetter('name', 'coord.lat') # retrieve name and coord.lat nested attribute\n","for city in sorted(metro_areas, key=attrgetter('coord.lat')): # sort list of cities by latitude\n"," print(name_lat(city)) # show only name and lat"]},{"cell_type":"code","execution_count":16,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"TWwYXSXWNieG","outputId":"312560b5-0418-432a-eefc-d264fbc1a9b3","executionInfo":{"status":"ok","timestamp":1699182492109,"user_tz":-210,"elapsed":12,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["THE TIME HAS COME\n","The-time-has-come\n"]}],"source":["# Example 7-15\n","from operator import methodcaller\n","\n","\n","s = 'The time has come'\n","upcase = methodcaller('upper')\n","print(upcase(s))\n","\n","hyphenate = methodcaller('replace', ' ', '-')\n","print(hyphenate(s))"]},{"cell_type":"markdown","metadata":{"id":"rQXl1BhJOykw"},"source":["#### Freezing Arguments with functools.partial"]},{"cell_type":"markdown","metadata":{"id":"I_Uc_XamO5El"},"source":["\n","\n","* `partial` : given a callable it produces a new callable with some of the arguments the original callable bound to predetermined values. It takes a callable as first argument, followed by an arbitrary number of positional and keyword arguments to bind, returns `functools.partial` object, has attributes providing access to original function and fixed arguments.\n","* `partialmethod` : same as `partial` but is designed to work with methods.\n","\n"]},{"cell_type":"code","execution_count":17,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"DRUY-5sfOdef","outputId":"1446c6a4-e501-48e0-8808-fabce347d15d","executionInfo":{"status":"ok","timestamp":1699182492109,"user_tz":-210,"elapsed":11,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"execute_result","data":{"text/plain":["[3, 6, 9, 12, 15, 18, 21, 24, 27]"]},"metadata":{},"execution_count":17}],"source":["# Example 7-16\n","from functools import partial\n","\n","\n","triple = partial(mul, 3) # binding the first positional argument of mul to 3\n","list(map(triple, range(1, 10)))"]},{"cell_type":"code","execution_count":18,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Yq5Ybd5-Pn9U","outputId":"7b881f97-378c-4713-aa0a-04b5b0b22315","executionInfo":{"status":"ok","timestamp":1699182492109,"user_tz":-210,"elapsed":10,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["café café\n","False\n","True\n"]}],"source":["# Example 7-17\n","import unicodedata\n","\n","nfc = partial(unicodedata.normalize, 'NFC')\n","\n","s1 = 'café'\n","s2 = 'cafe\\u0301'\n","\n","print(s1, s2)\n","\n","print(s1 == s2)\n","\n","print(nfc(s1) == nfc(s2))"]},{"cell_type":"code","execution_count":19,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"cBDOV_HGQRWH","outputId":"aa749917-026c-4152-ebd9-a1fe8c27cfa1","executionInfo":{"status":"ok","timestamp":1699182492110,"user_tz":-210,"elapsed":11,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"outputs":[{"output_type":"stream","name":"stdout","text":["
\n","\n","-------------------------------------\n","\n","functools.partial(, 'img', class_='pic-frame')\n","\n","-------------------------------------\n","\n","dir picture : ['__call__', '__class__', '__class_getitem__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__vectorcalloffset__', 'args', 'func', 'keywords']\n","\n","-------------------------------------\n","\n","function : \n","\n","-------------------------------------\n","\n","keyword arg : {'class_': 'pic-frame'}\n","\n","-------------------------------------\n","\n","args : ('img',)\n"]}],"source":["# Example 7-18\n","picture = partial(tag, 'img', class_='pic-frame')\n","print(picture(src='wumpus.jpg'))\n","print('\\n-------------------------------------\\n')\n","print(picture)\n","print('\\n-------------------------------------\\n')\n","print(f'dir picture : {dir(picture)}')\n","print('\\n-------------------------------------\\n')\n","print(f'function : {picture.func}')\n","print('\\n-------------------------------------\\n')\n","print(f'keyword arg : {picture.keywords}')\n","print('\\n-------------------------------------\\n')\n","print(f'args : {picture.args}')"]},{"cell_type":"markdown","metadata":{"id":"oczWUPDiSYP4"},"source":["### Lecturers"]},{"cell_type":"markdown","metadata":{"id":"8TdcZrcXSbbr"},"source":["\n","\n","* Reihane Heidari : [Linkedin](https://www.linkedin.com/in/reihane-heidari/)\n","* Saeed Hemmati : [Linkedin](https://www.linkedin.com/in/saeed-hemati/)\n","\n","\n","Presentation Date : 11-03-2023\n"]},{"cell_type":"markdown","metadata":{"id":"ckU3jbunYNPC"},"source":["### Reviewers"]},{"cell_type":"markdown","source":["1. Hosein Toodehroosta, review date: 11-02-2023, [LinkedIn](https://www.linkedin.com/in/hossein-toodehroosta-4b34b9191?utm_source=share&utm_campaign=share_via&utm_content=profile&utm_medium=android_app)\n","2. Mahya Asgarian, review date : 11-02-2023, [Linkedin](https://www.linkedin.com/in/mahya-asgarian-9a7b13249/)"],"metadata":{"id":"G6hJAdig4NvU"}},{"cell_type":"code","source":[],"metadata":{"id":"ZJ99JuXJfq6y","executionInfo":{"status":"ok","timestamp":1699182492110,"user_tz":-210,"elapsed":10,"user":{"displayName":"Saeed Hemati","userId":"17952675333562969500"}}},"execution_count":19,"outputs":[]}],"metadata":{"colab":{"provenance":[],"toc_visible":true},"kernelspec":{"display_name":"Python 3 (ipykernel)","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.9"}},"nbformat":4,"nbformat_minor":0}
--------------------------------------------------------------------------------
/chapter-10/notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# **CHAPTER 10
Design Patterns with First-Class Functions**"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "- In software engineering, a design pattern is a **general recipe for solving a common\n",
15 | "design problem**. You don’t need to know design patterns to follow this chapter.\n",
16 | "\n",
17 | "- Although design patterns are language independent, that does not mean every pattern\n",
18 | "applies to every language. Therefore implementation language determines which patterns are relevant.\n",
19 | "- In particular, in the context of languages with first-class functions, Norvig (“Design Patterns in Dynamic Languages”, Peter Norvig, 1996 presentation) suggests rethinking the classic patterns known as **Strategy**, **Command**, **Template Method**, and **Visitor**.\n",
20 | "\n",
21 | "- The **goal of this chapter** is to show how—in some cases—functions can do the same work as classes, with code that is more readable and concise."
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "# Strategy pattern"
29 | ]
30 | },
31 | {
32 | "cell_type": "markdown",
33 | "metadata": {},
34 | "source": [
35 | "\n",
36 | "\n",
37 | "Define a family of algorithms, encapsulate each one, and make them interchangeable.\n",
38 | "Strategy lets the algorithm vary independently from clients that use it.\n",
39 | "\n",
40 | "- *Context*: Provides a service by delegating some computation to interchangeable components\n",
41 | "that implement alternative algorithms. In the ecommerce example, the\n",
42 | "context is an Order, which is configured to apply a promotional discount according\n",
43 | "to one of several algorithms.\n",
44 | "\n",
45 | "- *Strategy*:The interface common to the components that implement the different algorithms.\n",
46 | "In our example, this role is played by an abstract class called Promotion.\n",
47 | "\n",
48 | "- *Concrete strategy*: One of the concrete subclasses of Strategy. FidelityPromo, BulkPromo, and LargeOrderPromo are the three concrete strategies implemented.\n"
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": 1,
54 | "metadata": {},
55 | "outputs": [],
56 | "source": [
57 | "# Example 10.1 Implementation of the Order class with pluggable discount strategies\n",
58 | "from abc import ABC, abstractmethod\n",
59 | "from collections.abc import Sequence\n",
60 | "from decimal import Decimal\n",
61 | "from typing import NamedTuple, Optional\n",
62 | "\n",
63 | "\n",
64 | "class Customer(NamedTuple):\n",
65 | " name: str\n",
66 | " fidelity: int\n",
67 | "\n",
68 | "\n",
69 | "class LineItem(NamedTuple):\n",
70 | " product: str\n",
71 | " quantity: int\n",
72 | " price: Decimal\n",
73 | "\n",
74 | " def total(self) -> Decimal:\n",
75 | " return self.price * self.quantity\n",
76 | "\n",
77 | "\n",
78 | "class Order(NamedTuple): # the Context\n",
79 | " customer: Customer\n",
80 | " cart: Sequence[LineItem]\n",
81 | " promotion: Optional['Promotion'] = None\n",
82 | "\n",
83 | " def total(self) -> Decimal:\n",
84 | " totals = (item.total() for item in self.cart)\n",
85 | " return sum(totals, start=Decimal(0))\n",
86 | "\n",
87 | " def due(self) -> Decimal:\n",
88 | " if self.promotion is None:\n",
89 | " discount = Decimal(0)\n",
90 | " else:\n",
91 | " discount = self.promotion.discount(self)\n",
92 | " return self.total() - discount\n",
93 | "\n",
94 | " def __repr__(self):\n",
95 | " return f''\n",
96 | "\n",
97 | "\n",
98 | "class Promotion(ABC): # the Strategy: an abstract base class\n",
99 | " @abstractmethod\n",
100 | " def discount(self, order: Order) -> Decimal:\n",
101 | " \"\"\"Return discount as a positive dollar amount\"\"\"\n",
102 | "\n",
103 | "\n",
104 | "class FidelityPromo(Promotion): # first Concrete Strategy\n",
105 | " \"\"\"5% discount for customers with 1000 or more fidelity points\"\"\"\n",
106 | "\n",
107 | " def discount(self, order: Order) -> Decimal:\n",
108 | " rate = Decimal('0.05')\n",
109 | " if order.customer.fidelity >= 1000:\n",
110 | " return order.total() * rate\n",
111 | " return Decimal(0)\n",
112 | "\n",
113 | "\n",
114 | "class BulkItemPromo(Promotion): # second Concrete Strategy\n",
115 | " \"\"\"10% discount for each LineItem with 20 or more units\"\"\"\n",
116 | "\n",
117 | " def discount(self, order: Order) -> Decimal:\n",
118 | " discount = Decimal(0)\n",
119 | " for item in order.cart:\n",
120 | " if item.quantity >= 20:\n",
121 | " discount += item.total() * Decimal('0.1')\n",
122 | " return discount\n",
123 | "\n",
124 | "\n",
125 | "class LargeOrderPromo(Promotion): # third Concrete Strategy\n",
126 | " \"\"\"7% discount for orders with 10 or more distinct items\"\"\"\n",
127 | "\n",
128 | " def discount(self, order: Order) -> Decimal:\n",
129 | " distinct_items = {item.product for item in order.cart}\n",
130 | " if len(distinct_items) >= 10:\n",
131 | " return order.total() * Decimal('0.07')\n",
132 | " return Decimal(0)"
133 | ]
134 | },
135 | {
136 | "cell_type": "code",
137 | "execution_count": 2,
138 | "metadata": {},
139 | "outputs": [],
140 | "source": [
141 | "# Two customers: joe has 0 fidelity points, ann has 1,100.\n",
142 | "joe = Customer('John Doe', 0)\n",
143 | "ann = Customer('Ann Smith', 1100)\n",
144 | "cart = (LineItem('banana', 4, Decimal('.5')),\n",
145 | " LineItem('apple', 10, Decimal('1.5')),\n",
146 | " LineItem('watermelon', 5, Decimal(5)))"
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": 3,
152 | "metadata": {},
153 | "outputs": [
154 | {
155 | "data": {
156 | "text/plain": [
157 | ""
158 | ]
159 | },
160 | "execution_count": 3,
161 | "metadata": {},
162 | "output_type": "execute_result"
163 | }
164 | ],
165 | "source": [
166 | "# The FidelityPromo promotion gives no discount to joe.\n",
167 | "Order(joe, cart, FidelityPromo())"
168 | ]
169 | },
170 | {
171 | "cell_type": "code",
172 | "execution_count": 4,
173 | "metadata": {},
174 | "outputs": [
175 | {
176 | "data": {
177 | "text/plain": [
178 | ""
179 | ]
180 | },
181 | "execution_count": 4,
182 | "metadata": {},
183 | "output_type": "execute_result"
184 | }
185 | ],
186 | "source": [
187 | "# ann gets a 5% discount because she has at least 1,000 points.\n",
188 | "Order(ann, cart, FidelityPromo())"
189 | ]
190 | },
191 | {
192 | "cell_type": "markdown",
193 | "id": "dfc02cba",
194 | "metadata": {},
195 | "source": [
196 | "# Function-Oriented Strategy"
197 | ]
198 | },
199 | {
200 | "cell_type": "markdown",
201 | "metadata": {},
202 | "source": [
203 | "- Since each concrete strategy is a class with **no attribute** and **single method**, they look a lot like plain functions.\n",
204 | "\n",
205 | "- Replacing the concrete strategies with simple functions \n",
206 | "and removing the Promo abstract class is a way to refactor Example 10-1. Only small adjustments are needed in the Order class."
207 | ]
208 | },
209 | {
210 | "cell_type": "code",
211 | "execution_count": 7,
212 | "id": "b0153e22",
213 | "metadata": {},
214 | "outputs": [],
215 | "source": [
216 | "#Example 10.3 Order class with discount strategies implemented as functions\n",
217 | "\n",
218 | "from collections.abc import Sequence\n",
219 | "from dataclasses import dataclass\n",
220 | "from decimal import Decimal\n",
221 | "from typing import Optional, Callable, NamedTuple\n",
222 | "\n",
223 | "\n",
224 | "class Customer(NamedTuple):\n",
225 | " name: str\n",
226 | " fidelity: int\n",
227 | "\n",
228 | "\n",
229 | "class LineItem(NamedTuple):\n",
230 | " product: str\n",
231 | " quantity: int\n",
232 | " price: Decimal\n",
233 | "\n",
234 | " def total(self):\n",
235 | " return self.price * self.quantity\n",
236 | "\n",
237 | "@dataclass(frozen=True)\n",
238 | "class Order: # the Context\n",
239 | " customer: Customer\n",
240 | " cart: Sequence[LineItem]\n",
241 | " promotion: Optional[Callable[['Order'], Decimal]] = None # <1> promotion may be None, or it may be a callable\n",
242 | "\n",
243 | " def total(self) -> Decimal:\n",
244 | " totals = (item.total() for item in self.cart)\n",
245 | " return sum(totals, start=Decimal(0))\n",
246 | "\n",
247 | " def due(self) -> Decimal:\n",
248 | " if self.promotion is None:\n",
249 | " discount = Decimal(0)\n",
250 | " else:\n",
251 | " discount = self.promotion(self) # <2> promotion is not a method. It’s an instance\n",
252 | " return self.total() - discount\n",
253 | "\n",
254 | " def __repr__(self):\n",
255 | " return f''\n",
256 | "\n",
257 | "\n",
258 | "def fidelity_promo(order: Order) -> Decimal: # <4> Each strategy is a function.\n",
259 | " \"\"\"5% discount for customers with 1000 or more fidelity points\"\"\"\n",
260 | " if order.customer.fidelity >= 1000:\n",
261 | " return order.total() * Decimal('0.05')\n",
262 | " return Decimal(0)\n",
263 | "\n",
264 | "\n",
265 | "def bulk_item_promo(order: Order) -> Decimal:\n",
266 | " \"\"\"10% discount for each LineItem with 20 or more units\"\"\"\n",
267 | " discount = Decimal(0)\n",
268 | " for item in order.cart:\n",
269 | " if item.quantity >= 20:\n",
270 | " discount += item.total() * Decimal('0.1')\n",
271 | " return discount\n",
272 | "\n",
273 | "\n",
274 | "def large_order_promo(order: Order) -> Decimal:\n",
275 | " \"\"\"7% discount for orders with 10 or more distinct items\"\"\"\n",
276 | " distinct_items = {item.product for item in order.cart}\n",
277 | " if len(distinct_items) >= 10:\n",
278 | " return order.total() * Decimal('0.07')\n",
279 | " return Decimal(0)"
280 | ]
281 | },
282 | {
283 | "cell_type": "code",
284 | "execution_count": 8,
285 | "metadata": {},
286 | "outputs": [],
287 | "source": [
288 | "joe = Customer('John Doe', 0)\n",
289 | "ann = Customer('Ann Smith', 1100)\n",
290 | "cart = [LineItem('banana', 4, Decimal('.5')),\n",
291 | " LineItem('apple', 10, Decimal('1.5')),\n",
292 | " LineItem('watermelon', 5, Decimal(5))]\n",
293 | "banana_cart = [LineItem('banana', 30, Decimal('.5')),\n",
294 | " LineItem('apple', 10, Decimal('1.5'))]"
295 | ]
296 | },
297 | {
298 | "cell_type": "code",
299 | "execution_count": 9,
300 | "metadata": {},
301 | "outputs": [
302 | {
303 | "data": {
304 | "text/plain": [
305 | ""
306 | ]
307 | },
308 | "execution_count": 9,
309 | "metadata": {},
310 | "output_type": "execute_result"
311 | }
312 | ],
313 | "source": [
314 | "Order(ann, cart, fidelity_promo)"
315 | ]
316 | },
317 | {
318 | "cell_type": "code",
319 | "execution_count": 10,
320 | "metadata": {},
321 | "outputs": [
322 | {
323 | "data": {
324 | "text/plain": [
325 | ""
326 | ]
327 | },
328 | "execution_count": 10,
329 | "metadata": {},
330 | "output_type": "execute_result"
331 | }
332 | ],
333 | "source": [
334 | "\n",
335 | "Order(joe, banana_cart, bulk_item_promo)"
336 | ]
337 | },
338 | {
339 | "cell_type": "markdown",
340 | "metadata": {},
341 | "source": [
342 | "Same test fixtures as Example 10-1.\n",
343 | "To apply a discount strategy to an Order, just pass the promotion function as an\n",
344 | "argument.\n",
345 | "A different promotion function is used here and in the next test."
346 | ]
347 | },
348 | {
349 | "cell_type": "markdown",
350 | "metadata": {},
351 | "source": [
352 | "$\\color{orange}{Note:}$
\n",
353 | "\n",
354 | "- Often concrete strategies have no internal state; they only deal with data from the context. If that is the case, then by all means use plain old functions instead of coding single-method classes implementing a single-method interface declared in yet another class. A function is more lightweight than an instance of a user-defined class, and there is no need for Flyweight because each strategy function is created just once per Python process when it loads the module. A plain function is also “a shared\n",
355 | "object that can be used in multiple contexts simultaneously."
356 | ]
357 | },
358 | {
359 | "cell_type": "markdown",
360 | "metadata": {},
361 | "source": [
362 | "# Choosing the Best Strategy: Simple Approach"
363 | ]
364 | },
365 | {
366 | "cell_type": "markdown",
367 | "metadata": {},
368 | "source": [
369 | "Suppose you want to create a “metastrategy” that selects the best available discount for a given Order.\n",
370 | "\n",
371 | "In the following examples (6-7-8-9) we study additional refactorings that implement this requirement using a variety of approaches that leverage functions and modules as objects."
372 | ]
373 | },
374 | {
375 | "cell_type": "code",
376 | "execution_count": 8,
377 | "metadata": {},
378 | "outputs": [],
379 | "source": [
380 | "# Example 10-6. best_promo finds the maximum discount iterating over a list of functions\n",
381 | "from decimal import Decimal\n",
382 | "\n",
383 | "promos = [fidelity_promo, bulk_item_promo, large_order_promo]\n",
384 | "\n",
385 | "\n",
386 | "def best_promo(order: Order) -> Decimal:\n",
387 | " \"\"\"Compute the best discount available\"\"\"\n",
388 | " return max(promo(order) for promo in promos)"
389 | ]
390 | },
391 | {
392 | "cell_type": "code",
393 | "execution_count": 9,
394 | "metadata": {},
395 | "outputs": [],
396 | "source": [
397 | "joe = Customer('John Doe', 0)\n",
398 | "ann = Customer('Ann Smith', 1100)\n",
399 | "cart = [LineItem('banana', 4, Decimal('.5')),\n",
400 | " LineItem('apple', 10, Decimal('1.5')),\n",
401 | " LineItem('watermelon', 5, Decimal(5))]\n",
402 | "banana_cart = [LineItem('banana', 30, Decimal('.5')),\n",
403 | " LineItem('apple', 10, Decimal('1.5'))]\n",
404 | "long_cart = [LineItem(str(item_code), 1, Decimal(1))\n",
405 | " for item_code in range(10)]"
406 | ]
407 | },
408 | {
409 | "cell_type": "code",
410 | "execution_count": 10,
411 | "metadata": {},
412 | "outputs": [
413 | {
414 | "data": {
415 | "text/plain": [
416 | ""
417 | ]
418 | },
419 | "execution_count": 10,
420 | "metadata": {},
421 | "output_type": "execute_result"
422 | }
423 | ],
424 | "source": [
425 | "Order(joe, long_cart, best_promo)"
426 | ]
427 | },
428 | {
429 | "cell_type": "code",
430 | "execution_count": 11,
431 | "metadata": {},
432 | "outputs": [
433 | {
434 | "data": {
435 | "text/plain": [
436 | ""
437 | ]
438 | },
439 | "execution_count": 11,
440 | "metadata": {},
441 | "output_type": "execute_result"
442 | }
443 | ],
444 | "source": [
445 | "Order(joe, banana_cart, best_promo)"
446 | ]
447 | },
448 | {
449 | "cell_type": "code",
450 | "execution_count": 12,
451 | "metadata": {},
452 | "outputs": [
453 | {
454 | "data": {
455 | "text/plain": [
456 | ""
457 | ]
458 | },
459 | "execution_count": 12,
460 | "metadata": {},
461 | "output_type": "execute_result"
462 | }
463 | ],
464 | "source": [
465 | "Order(ann, cart, best_promo)"
466 | ]
467 | },
468 | {
469 | "cell_type": "markdown",
470 | "metadata": {},
471 | "source": [
472 | "$\\color{orange}{Note:}$
\n",
473 | "our *main issue with Example 10-6* is the repetition of the function names\n",
474 | "in their definitions and then in the promos list used by the best_promo function to\n",
475 | "determine the highest discount applicable. The repetition is problematic because\n",
476 | "someone may add a new promotional strategy function and forget to manually add it\n",
477 | "to the promos list—in which case, best_promo will silently ignore the new strategy,\n",
478 | "introducing a **subtle bug** in the system (Example 10-9 solves this problem).
\n",
479 | "\n",
480 | "\n",
481 | "Read on for a couple of solutions to this issue: \n",
482 | "- Finding Strategies in a Module\n",
483 | "- Decorator-Enhanced Strategy Pattern"
484 | ]
485 | },
486 | {
487 | "cell_type": "markdown",
488 | "metadata": {},
489 | "source": [
490 | "# Finding Strategies in a Module"
491 | ]
492 | },
493 | {
494 | "cell_type": "markdown",
495 | "metadata": {},
496 | "source": [
497 | "## globals()"
498 | ]
499 | },
500 | {
501 | "cell_type": "markdown",
502 | "metadata": {},
503 | "source": []
504 | },
505 | {
506 | "cell_type": "code",
507 | "execution_count": 1,
508 | "metadata": {},
509 | "outputs": [
510 | {
511 | "data": {
512 | "text/plain": [
513 | "dict_items([('__name__', '__main__'), ('__doc__', 'Automatically created module for IPython interactive environment'), ('__package__', None), ('__loader__', None), ('__spec__', None), ('__builtin__', ), ('__builtins__', ), ('_ih', ['', 'globals().items()']), ('_oh', {}), ('_dh', [WindowsPath('c:/Users/amirh/Desktop/Pytorch RL Udemy course/Fluent Python Class codes/Ch 10'), WindowsPath('c:/Users/amirh/Desktop/Pytorch RL Udemy course/Fluent Python Class codes/Ch 10')]), ('In', ['', 'globals().items()']), ('Out', {}), ('get_ipython', >), ('exit', ), ('quit', ), ('open', ), ('_', ''), ('__', ''), ('___', ''), ('__vsc_ipynb_file__', 'c:\\\\Users\\\\amirh\\\\Desktop\\\\Pytorch RL Udemy course\\\\Fluent Python Class codes\\\\Ch 10\\\\ch_10_V2.ipynb'), ('_i', ''), ('_ii', ''), ('_iii', ''), ('_i1', 'globals().items()')])"
514 | ]
515 | },
516 | "execution_count": 1,
517 | "metadata": {},
518 | "output_type": "execute_result"
519 | }
520 | ],
521 | "source": [
522 | "globals().items()"
523 | ]
524 | },
525 | {
526 | "cell_type": "code",
527 | "execution_count": 11,
528 | "metadata": {},
529 | "outputs": [],
530 | "source": [
531 | "# Example 10-7. The promos list is built by introspection of the module global namespace\n",
532 | "from decimal import Decimal\n",
533 | "\n",
534 | "promos = [promo for name, promo in globals().items()\n",
535 | " if name.endswith('_promo') and\n",
536 | " name != 'best_promo'\n",
537 | "]\n",
538 | "\n",
539 | "\n",
540 | "def best_promo(order: Order) -> Decimal:\n",
541 | " \"\"\"Compute the best discount available\"\"\"\n",
542 | " return max(promo(order) for promo in promos)"
543 | ]
544 | },
545 | {
546 | "cell_type": "code",
547 | "execution_count": 13,
548 | "metadata": {},
549 | "outputs": [],
550 | "source": [
551 | "joe = Customer('John Doe', 0)\n",
552 | "ann = Customer('Ann Smith', 1100)\n",
553 | "cart = [LineItem('banana', 4, Decimal('.5')),\n",
554 | " LineItem('apple', 10, Decimal('1.5')),\n",
555 | " LineItem('watermelon', 5, Decimal(5))]\n",
556 | "banana_cart = [LineItem('banana', 30, Decimal('.5')),\n",
557 | " LineItem('apple', 10, Decimal('1.5'))]\n",
558 | "long_cart = [LineItem(str(item_code), 1, Decimal(1))\n",
559 | " for item_code in range(10)]"
560 | ]
561 | },
562 | {
563 | "cell_type": "code",
564 | "execution_count": 14,
565 | "metadata": {},
566 | "outputs": [
567 | {
568 | "data": {
569 | "text/plain": [
570 | ""
571 | ]
572 | },
573 | "execution_count": 14,
574 | "metadata": {},
575 | "output_type": "execute_result"
576 | }
577 | ],
578 | "source": [
579 | "Order(joe, long_cart, best_promo)"
580 | ]
581 | },
582 | {
583 | "cell_type": "code",
584 | "execution_count": 15,
585 | "metadata": {},
586 | "outputs": [
587 | {
588 | "data": {
589 | "text/plain": [
590 | ""
591 | ]
592 | },
593 | "execution_count": 15,
594 | "metadata": {},
595 | "output_type": "execute_result"
596 | }
597 | ],
598 | "source": [
599 | "Order(joe, banana_cart, best_promo)"
600 | ]
601 | },
602 | {
603 | "cell_type": "code",
604 | "execution_count": 16,
605 | "metadata": {},
606 | "outputs": [
607 | {
608 | "data": {
609 | "text/plain": [
610 | ""
611 | ]
612 | },
613 | "execution_count": 16,
614 | "metadata": {},
615 | "output_type": "execute_result"
616 | }
617 | ],
618 | "source": [
619 | "Order(ann, cart, best_promo)"
620 | ]
621 | },
622 | {
623 | "cell_type": "markdown",
624 | "metadata": {},
625 | "source": [
626 | "## inspect"
627 | ]
628 | },
629 | {
630 | "cell_type": "markdown",
631 | "metadata": {},
632 | "source": [
633 | "Another way of collecting the available promotions would be to create a module and\n",
634 | "put all the strategy functions there, except for best_promo.\n",
635 | "\n",
636 | "The function inspect.getmembers returns the attributes of an object—in this case,\n",
637 | "the promotions module—optionally filtered by a predicate (a boolean function). We\n",
638 | "use inspect.isfunction to get only the functions from the module."
639 | ]
640 | },
641 | {
642 | "cell_type": "code",
643 | "execution_count": null,
644 | "metadata": {},
645 | "outputs": [],
646 | "source": [
647 | "# Example 10-8. The promos list is built by introspection of a new promotions module\n",
648 | "from decimal import Decimal\n",
649 | "import inspect\n",
650 | "# promos list is built by introspection of a new promotions module\n",
651 | "from strategy import Order\n",
652 | "import promotions\n",
653 | "\n",
654 | "promos = [func for _, func in inspect.getmembers(promotions, inspect.isfunction)]\n",
655 | "\n",
656 | "\n",
657 | "def best_promo(order: Order) -> Decimal:\n",
658 | " \"\"\"Compute the best discount available\"\"\"\n",
659 | " return max(promo(order) for promo in promos)"
660 | ]
661 | },
662 | {
663 | "cell_type": "code",
664 | "execution_count": null,
665 | "metadata": {},
666 | "outputs": [],
667 | "source": [
668 | "joe = Customer('John Doe', 0)\n",
669 | "ann = Customer('Ann Smith', 1100)\n",
670 | "cart = [LineItem('banana', 4, Decimal('.5')),\n",
671 | " LineItem('apple', 10, Decimal('1.5')),\n",
672 | " LineItem('watermelon', 5, Decimal(5))]\n",
673 | "banana_cart = [LineItem('banana', 30, Decimal('.5')),\n",
674 | " LineItem('apple', 10, Decimal('1.5'))]\n",
675 | "long_cart = [LineItem(str(item_code), 1, Decimal(1))\n",
676 | " for item_code in range(10)]"
677 | ]
678 | },
679 | {
680 | "cell_type": "markdown",
681 | "metadata": {},
682 | "source": [
683 | "- Example 10-8 works regardless of the names given to the functions (Despite Example 10-7); all that matters is that the promotions module contains only functions that calculate discounts given\n",
684 | "orders.\n",
685 | "\n",
686 | "- Of course, this is an implicit assumption of the code. If someone were to create\n",
687 | "a function with a different signature in the promotions module, then best_promo\n",
688 | "would break while trying to apply it to an order.\n",
689 | "\n",
690 | "- We could add more stringent tests to filter the functions, by inspecting their arguments\n",
691 | "for instance. The point of Example 10-8 is not to offer a complete solution, but\n",
692 | "to highlight one possible use of module introspection.\n",
693 | "\n",
694 | "- A more explicit alternative to dynamically collecting promotional discount functions\n",
695 | "would be to use a simple decorator (Example 10-9)."
696 | ]
697 | },
698 | {
699 | "cell_type": "markdown",
700 | "metadata": {},
701 | "source": [
702 | "# Decorator-Enhanced Strategy Pattern"
703 | ]
704 | },
705 | {
706 | "cell_type": "markdown",
707 | "metadata": {},
708 | "source": [
709 | "A more explicit alternative to dynamically collecting promotional discount functions\n",
710 | "would be to use a simple.\n",
711 | "\n",
712 | "This solution has several advantages over the others presented before:\n",
713 | "\n",
714 | "- The promotion strategy functions don’t have to use special names—no need for\n",
715 | "the _promo suffix.\n",
716 | "\n",
717 | "- The @promotion decorator highlights the purpose of the decorated function, and\n",
718 | "also makes it easy to temporarily disable a promotion: just comment out the\n",
719 | "decorator.\n",
720 | "\n",
721 | "- Promotional discount strategies may be defined in other modules, anywhere in\n",
722 | "the system, as long as the @promotion decorator is applied to them."
723 | ]
724 | },
725 | {
726 | "cell_type": "code",
727 | "execution_count": 17,
728 | "metadata": {},
729 | "outputs": [],
730 | "source": [
731 | "# Example 10-9. The promos list is filled by the Promotion decorator\n",
732 | "\n",
733 | "from decimal import Decimal\n",
734 | "from typing import Callable\n",
735 | "\n",
736 | "\n",
737 | "Promotion = Callable[[Order], Decimal]\n",
738 | "\n",
739 | "promos: list[Promotion] = []\n",
740 | "\n",
741 | "\n",
742 | "def promotion(promo: Promotion) -> Promotion:\n",
743 | " promos.append(promo)\n",
744 | " return promo\n",
745 | "\n",
746 | "\n",
747 | "def best_promo(order: Order) -> Decimal:\n",
748 | " \"\"\"Compute the best discount available\"\"\"\n",
749 | " return max(promo(order) for promo in promos)\n",
750 | "\n",
751 | "\n",
752 | "@promotion\n",
753 | "def fidelity(order: Order) -> Decimal:\n",
754 | " \"\"\"5% discount for customers with 1000 or more fidelity points\"\"\"\n",
755 | " if order.customer.fidelity >= 1000:\n",
756 | " return order.total() * Decimal('0.05')\n",
757 | " return Decimal(0)\n",
758 | "\n",
759 | "\n",
760 | "@promotion\n",
761 | "def bulk_item(order: Order) -> Decimal:\n",
762 | " \"\"\"10% discount for each LineItem with 20 or more units\"\"\"\n",
763 | " discount = Decimal(0)\n",
764 | " for item in order.cart:\n",
765 | " if item.quantity >= 20:\n",
766 | " discount += item.total() * Decimal('0.1')\n",
767 | " return discount\n",
768 | "\n",
769 | "\n",
770 | "@promotion\n",
771 | "def large_order(order: Order) -> Decimal:\n",
772 | " \"\"\"7% discount for orders with 10 or more distinct items\"\"\"\n",
773 | " distinct_items = {item.product for item in order.cart}\n",
774 | " if len(distinct_items) >= 10:\n",
775 | " return order.total() * Decimal('0.07')\n",
776 | " return Decimal(0)"
777 | ]
778 | },
779 | {
780 | "cell_type": "code",
781 | "execution_count": 18,
782 | "metadata": {},
783 | "outputs": [],
784 | "source": [
785 | "joe = Customer('John Doe', 0)\n",
786 | "ann = Customer('Ann Smith', 1100)\n",
787 | "cart = [LineItem('banana', 4, Decimal('.5')),\n",
788 | " LineItem('apple', 10, Decimal('1.5')),\n",
789 | " LineItem('watermelon', 5, Decimal(5))]\n",
790 | "\n",
791 | "banana_cart = [LineItem('banana', 30, Decimal('.5')),\n",
792 | " LineItem('apple', 10, Decimal('1.5'))]\n",
793 | "long_cart = [LineItem(str(item_code), 1, Decimal(1))\n",
794 | " for item_code in range(10)]"
795 | ]
796 | },
797 | {
798 | "cell_type": "markdown",
799 | "metadata": {},
800 | "source": [
801 | "# The Command Pattern"
802 | ]
803 | },
804 | {
805 | "cell_type": "markdown",
806 | "metadata": {},
807 | "source": [
808 | "- **Command** is another design pattern that can be simplified by the use of functions\n",
809 | "passed as arguments.\n",
810 | "\n",
811 | "- The **goal of Command** is to decouple an object that invokes an operation (the\n",
812 | "invoker) from the provider object that implements it (the receiver).\n",
813 | "\n",
814 | "- Quoting from Design Patterns book, “Commands are an object-oriented replacement for\n",
815 | "callbacks.” The question is: do we need an object-oriented replacement for callbacks?\n",
816 | "Sometimes yes, but not always."
817 | ]
818 | },
819 | {
820 | "cell_type": "code",
821 | "execution_count": null,
822 | "metadata": {},
823 | "outputs": [],
824 | "source": [
825 | "\n",
826 | "class MacroCommand:\n",
827 | " \"\"\"A command that executes a list of commands\"\"\"\n",
828 | " def __init__(self, commands):\n",
829 | " self.commands = list(commands)\n",
830 | "\n",
831 | " def __call__(self):\n",
832 | " for command in self.commands:\n",
833 | " command()\n"
834 | ]
835 | },
836 | {
837 | "cell_type": "markdown",
838 | "metadata": {},
839 | "source": [
840 | "- More advanced uses of the Command pattern—to support undo, for example—may\n",
841 | "require more than a simple callback function. Even then, Python provides a couple of\n",
842 | "alternatives that deserve consideration:\n",
843 | "\n",
844 | " - A callable instance like MacroCommand in Example 10-10 can keep whatever state\n",
845 | "is necessary, and provide extra methods in addition to `__call__`.\n",
846 | "\n",
847 | " - A closure can be used to hold the internal state of a function between calls.\n",
848 | "\n",
849 | "- At a high level, the approach here was similar to the one we applied to Strategy: replacing\n",
850 | "with callables the instances of a participant class that implemented a singlemethod\n",
851 | "interface. After all, every Python callable implements a single-method\n",
852 | "interface, and that method is named `__call__`."
853 | ]
854 | },
855 | {
856 | "cell_type": "markdown",
857 | "metadata": {},
858 | "source": [
859 | "# Lecturers\n",
860 | "\n",
861 | "1. Amirhossein Nourian [LinkedIn](https://www.linkedin.com/in/amirhnrn/)\n",
862 | "2. Mehrna Faraji [LinkedIn](https://www.linkedin.com/in/mehranfaraji/)\n",
863 | "\n",
864 | "\n",
865 | "present date : 2023-11-24"
866 | ]
867 | },
868 | {
869 | "cell_type": "markdown",
870 | "id": "3fbee50e",
871 | "metadata": {},
872 | "source": [
873 | "# Reviewers"
874 | ]
875 | },
876 | {
877 | "cell_type": "markdown",
878 | "id": "d403fb4b",
879 | "metadata": {},
880 | "source": [
881 | "1. Mahya Asgarian, review date: 2023-11-24 [LinkedIn](https://www.linkedin.com/in/mahya-asgarian-9a7b13249)"
882 | ]
883 | }
884 | ],
885 | "metadata": {
886 | "kernelspec": {
887 | "display_name": "Python 3 (ipykernel)",
888 | "language": "python",
889 | "name": "python3"
890 | },
891 | "language_info": {
892 | "codemirror_mode": {
893 | "name": "ipython",
894 | "version": 3
895 | },
896 | "file_extension": ".py",
897 | "mimetype": "text/x-python",
898 | "name": "python",
899 | "nbconvert_exporter": "python",
900 | "pygments_lexer": "ipython3",
901 | "version": "3.10.9"
902 | }
903 | },
904 | "nbformat": 4,
905 | "nbformat_minor": 5
906 | }
907 |
--------------------------------------------------------------------------------
/chapter-12/notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "id": "WEmUmFiMIh_B"
7 | },
8 | "source": [
9 | "## Special Methods for Sequences"
10 | ]
11 | },
12 | {
13 | "cell_type": "markdown",
14 | "metadata": {
15 | "id": "Xnazq8qbKjxm"
16 | },
17 | "source": [
18 | "### Vector: A User-Defined Sequence Type"
19 | ]
20 | },
21 | {
22 | "cell_type": "markdown",
23 | "metadata": {
24 | "id": "p4mQPgy6I4ru"
25 | },
26 | "source": [
27 | "* Goal: Create a class to represent multidimensional Vector\n",
28 | "* `Vector` will behave like standard Python immutable flat sequence\n",
29 | " * Baisc sequence protocol: `__len__` and `__getitem__`\n",
30 | " * Safe representation of instances with many items\n",
31 | " * Proper slicing support, producing new vector instances\n",
32 | " * Aggregate hashing, taking into account every contained element value\n",
33 | " * Custom formatting language extension\n",
34 | " * Dynamic attribute access with `__getattr__`\n",
35 | "* Strategy: Composition instead of inheritance ---> store components in an array of floats"
36 | ]
37 | },
38 | {
39 | "cell_type": "markdown",
40 | "metadata": {
41 | "id": "0M-42EJXKZAx"
42 | },
43 | "source": [
44 | "### Vector Take #1: Vector2d Compatible"
45 | ]
46 | },
47 | {
48 | "cell_type": "markdown",
49 | "metadata": {
50 | "id": "YtOAxbkJKqP9"
51 | },
52 | "source": [
53 | "* Constructor: Take data as an iterable argument in the constructor, i.e:\n",
54 | "```\n",
55 | "Vector([1, 2, 3])\n",
56 | "```\n",
57 | " * Could have subclassed `Vector` from `Vector2d` but incompatible constructors make subclassing not advisable.\n",
58 | "\n",
59 | "* Use `reprlib` module to produce limited-length representation\n",
60 | " * Because of its role in debugging, calling `repr()` on an object should never raise exception"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 4,
66 | "metadata": {
67 | "id": "79RHuFk8IYNE"
68 | },
69 | "outputs": [],
70 | "source": [
71 | "from array import array\n",
72 | "import reprlib\n",
73 | "import math\n",
74 | "import operator\n",
75 | "\n",
76 | "\n",
77 | "class Vector1:\n",
78 | " typecode = 'd'\n",
79 | "\n",
80 | " def __init__(self, components):\n",
81 | " self._components = array(self.typecode, components) # self._components: protected attribute, array with Vector components\n",
82 | "\n",
83 | " def __iter__(self):\n",
84 | " return iter(self._components) # iterator over self._components\n",
85 | "\n",
86 | " def __repr__(self):\n",
87 | " components = reprlib.repr(self._components)\n",
88 | " components = components[components.find('['):-1]\n",
89 | " return f'Vector({components})'\n",
90 | "\n",
91 | " def __str__(self):\n",
92 | " return str(tuple(self))\n",
93 | "\n",
94 | " def __bytes__(self):\n",
95 | " return (bytes([ord(self.typecode)]) +\n",
96 | " bytes(self._components))\n",
97 | "\n",
98 | " def __eq__(self, other):\n",
99 | " return tuple(self) == tuple(other)\n",
100 | "\n",
101 | " def __abs__(self):\n",
102 | " return math.hypot(*self) # since python 3.8: math.hypot accecpts N-dimensional points\n",
103 | "\n",
104 | " def __bool__(self):\n",
105 | " return bool(abs(self))\n",
106 | "\n",
107 | " @classmethod\n",
108 | " def frombytes(cls, octets):\n",
109 | " typecode = chr(octets[0])\n",
110 | " memv = memoryview(octets[1:]).cast(typecode)\n",
111 | " return cls(memv) # Pass memoryview withount unpacking with * to constructor"
112 | ]
113 | },
114 | {
115 | "cell_type": "markdown",
116 | "metadata": {
117 | "id": "MaF_2c2XRTBF"
118 | },
119 | "source": [
120 | "### Protocols and Duck Typing"
121 | ]
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "metadata": {
126 | "id": "F2ptbfEsRZQK"
127 | },
128 | "source": [
129 | "* Don't need to inherit from any special class to create a fully functional sequence type in Python.\n",
130 | "* Just need to implement the methods that fulfill the sequence *protocol*.\n",
131 | "* Protocol is an informal interface defined only in documentation and not in code.\n",
132 | "* Sequence protocol in Python: just implement `__len__` and `__getitem__` methods.\n",
133 | "* All that matters is that it provides the necessary methods ----> This became know as *duck typing*\n",
134 | "* Because protocols are informal and unenforced, can often get away with implementing just part of a protocl if know the specific context where a class will be used, e.g to support iteration only `__getitem__` is needed, no need to `__len__`.\n",
135 | "* Two kinds of protoclos: *static protocol* and *dynamic protocol*\n",
136 | "* One key difference between them is that static protocol implementations must provide all methods defined in the protocol class (More about protocols in Chapter 13)."
137 | ]
138 | },
139 | {
140 | "cell_type": "markdown",
141 | "metadata": {
142 | "id": "c-jBlqkPVQSD"
143 | },
144 | "source": [
145 | "### Vector Take #2: A sliceable Sequence"
146 | ]
147 | },
148 | {
149 | "cell_type": "markdown",
150 | "metadata": {
151 | "id": "MQsObv6jVcEd"
152 | },
153 | "source": [
154 | "* Need `__len__` and `__getitem__` in `Vector` to support sequence protocol.\n",
155 | "* Slice of a `Vector` should be a `Vector` instance ----> Need to analyze the arguments get in `__getitem__`."
156 | ]
157 | },
158 | {
159 | "cell_type": "code",
160 | "execution_count": 5,
161 | "metadata": {
162 | "colab": {
163 | "base_uri": "https://localhost:8080/"
164 | },
165 | "id": "XDJBszmKNvxk",
166 | "outputId": "4584731f-252c-4304-b272-adef73057af2"
167 | },
168 | "outputs": [
169 | {
170 | "name": "stdout",
171 | "output_type": "stream",
172 | "text": [
173 | "1\n",
174 | "slice(1, 4, None)\n",
175 | "slice(1, 4, 2)\n",
176 | "(slice(1, 4, 2), 9)\n",
177 | "(slice(1, 4, 2), slice(7, 9, None))\n"
178 | ]
179 | }
180 | ],
181 | "source": [
182 | "# example 12-4\n",
183 | "class MySeq:\n",
184 | " def __getitem__(self, index):\n",
185 | " return index\n",
186 | "\n",
187 | "s = MySeq()\n",
188 | "print(s[1])\n",
189 | "print(s[1:4])\n",
190 | "print(s[1:4:2])\n",
191 | "print(s[1:4:2, 9]) # presence of commas inside [] means __getitem__ receives a tuple\n",
192 | "print(s[1:4:2, 7:9])"
193 | ]
194 | },
195 | {
196 | "cell_type": "code",
197 | "execution_count": 6,
198 | "metadata": {
199 | "colab": {
200 | "base_uri": "https://localhost:8080/"
201 | },
202 | "id": "dM8AMO8YW97t",
203 | "outputId": "8d66df92-9fbd-4265-a499-02461276780c"
204 | },
205 | "outputs": [
206 | {
207 | "data": {
208 | "text/plain": [
209 | "['__class__',\n",
210 | " '__delattr__',\n",
211 | " '__dir__',\n",
212 | " '__doc__',\n",
213 | " '__eq__',\n",
214 | " '__format__',\n",
215 | " '__ge__',\n",
216 | " '__getattribute__',\n",
217 | " '__getstate__',\n",
218 | " '__gt__',\n",
219 | " '__hash__',\n",
220 | " '__init__',\n",
221 | " '__init_subclass__',\n",
222 | " '__le__',\n",
223 | " '__lt__',\n",
224 | " '__ne__',\n",
225 | " '__new__',\n",
226 | " '__reduce__',\n",
227 | " '__reduce_ex__',\n",
228 | " '__repr__',\n",
229 | " '__setattr__',\n",
230 | " '__sizeof__',\n",
231 | " '__str__',\n",
232 | " '__subclasshook__',\n",
233 | " 'indices',\n",
234 | " 'start',\n",
235 | " 'step',\n",
236 | " 'stop']"
237 | ]
238 | },
239 | "execution_count": 6,
240 | "metadata": {},
241 | "output_type": "execute_result"
242 | }
243 | ],
244 | "source": [
245 | "dir(slice) # start, stop, step attributes, indices mthod"
246 | ]
247 | },
248 | {
249 | "cell_type": "code",
250 | "execution_count": 7,
251 | "metadata": {
252 | "colab": {
253 | "base_uri": "https://localhost:8080/"
254 | },
255 | "id": "GRoAVuo0XcOM",
256 | "outputId": "b5efad63-937f-4a3b-9d64-182d879a1bc0"
257 | },
258 | "outputs": [
259 | {
260 | "name": "stdout",
261 | "output_type": "stream",
262 | "text": [
263 | "Help on method_descriptor:\n",
264 | "\n",
265 | "indices(...)\n",
266 | " S.indices(len) -> (start, stop, stride)\n",
267 | " \n",
268 | " Assuming a sequence of length len, calculate the start and stop\n",
269 | " indices, and the stride length of the extended slice described by\n",
270 | " S. Out of bounds indices are clipped in a manner consistent with the\n",
271 | " handling of normal slices.\n",
272 | "\n"
273 | ]
274 | }
275 | ],
276 | "source": [
277 | "help(slice.indices)"
278 | ]
279 | },
280 | {
281 | "cell_type": "markdown",
282 | "metadata": {
283 | "id": "nKYTQ9xiX1Mg"
284 | },
285 | "source": [
286 | "* `indices` method gracefully handle missing or negative indices and slices that are longer than the original sequence.\n",
287 | "* This method produces normalized tuples of nonnegative start, stop and stride integers tailored to a sequence of the given length"
288 | ]
289 | },
290 | {
291 | "cell_type": "code",
292 | "execution_count": 8,
293 | "metadata": {},
294 | "outputs": [
295 | {
296 | "name": "stdout",
297 | "output_type": "stream",
298 | "text": [
299 | "(0, 5, 2)\n",
300 | "(2, 5, 1)\n"
301 | ]
302 | }
303 | ],
304 | "source": [
305 | "print(slice(None, 10, 2).indices(5)) # The length of the sequence is 5.\n",
306 | "print(slice(-3, None, None).indices(5))"
307 | ]
308 | },
309 | {
310 | "cell_type": "markdown",
311 | "metadata": {
312 | "id": "hDTpUBNpYt24"
313 | },
314 | "source": [
315 | "* `Vector` won't need the `slice.indices()` method because `array` in constructor will handle it."
316 | ]
317 | },
318 | {
319 | "cell_type": "code",
320 | "execution_count": 9,
321 | "metadata": {
322 | "id": "RCWzRohwXvI1"
323 | },
324 | "outputs": [],
325 | "source": [
326 | "class Vector2(Vector1):\n",
327 | " def __len__(self):\n",
328 | " return len(self._components)\n",
329 | "\n",
330 | " def __getitem__(self, key):\n",
331 | " if isinstance(key, slice):\n",
332 | " cls = type(self)\n",
333 | " return cls(self._components[key]) # invoke the class to build another Vector instance from a slice of the _components array\n",
334 | " index = operator.index(key)\n",
335 | " return self._components[index] # return specific item from _components"
336 | ]
337 | },
338 | {
339 | "cell_type": "markdown",
340 | "metadata": {
341 | "id": "eXKucmACfhEr"
342 | },
343 | "source": [
344 | "* `operator.index()` ----> calls the `__index__` special method.\n",
345 | "* `operator.index()` and `__index__` defined in PEP 357 to allow any of numerous types of integers in `Numpy` to be used as indexes and slice arguments.\n",
346 | "* Key difference between `int()` and `operator.index()` is that the former is intended for this specific purpose, e.g , `int(3.14)` returns 3 and `operator.index(3.14)` raise `TypeError`.\n"
347 | ]
348 | },
349 | {
350 | "cell_type": "code",
351 | "execution_count": 10,
352 | "metadata": {
353 | "colab": {
354 | "base_uri": "https://localhost:8080/"
355 | },
356 | "id": "OZ8-N7E3ZhZX",
357 | "outputId": "60a89d2b-83cc-400c-a47d-af632ccb8131"
358 | },
359 | "outputs": [
360 | {
361 | "data": {
362 | "text/plain": [
363 | "6.0"
364 | ]
365 | },
366 | "execution_count": 10,
367 | "metadata": {},
368 | "output_type": "execute_result"
369 | }
370 | ],
371 | "source": [
372 | "# example 12-7\n",
373 | "v = Vector2(range(7))\n",
374 | "v[-1]"
375 | ]
376 | },
377 | {
378 | "cell_type": "code",
379 | "execution_count": 11,
380 | "metadata": {
381 | "colab": {
382 | "base_uri": "https://localhost:8080/"
383 | },
384 | "id": "F_-EgtQRhZNR",
385 | "outputId": "14139937-9baf-4e67-ac87-8216c9b495f2"
386 | },
387 | "outputs": [
388 | {
389 | "data": {
390 | "text/plain": [
391 | "Vector([1.0, 2.0, 3.0])"
392 | ]
393 | },
394 | "execution_count": 11,
395 | "metadata": {},
396 | "output_type": "execute_result"
397 | }
398 | ],
399 | "source": [
400 | "v[1:4]"
401 | ]
402 | },
403 | {
404 | "cell_type": "code",
405 | "execution_count": 12,
406 | "metadata": {
407 | "colab": {
408 | "base_uri": "https://localhost:8080/"
409 | },
410 | "id": "N_ODF61rh_e_",
411 | "outputId": "27efabae-2d0c-48c1-ca21-6b8a3d31d376"
412 | },
413 | "outputs": [
414 | {
415 | "data": {
416 | "text/plain": [
417 | "Vector([6.0])"
418 | ]
419 | },
420 | "execution_count": 12,
421 | "metadata": {},
422 | "output_type": "execute_result"
423 | }
424 | ],
425 | "source": [
426 | "v[-1:] # slice of len = 1 creates a Vector"
427 | ]
428 | },
429 | {
430 | "cell_type": "code",
431 | "execution_count": 13,
432 | "metadata": {
433 | "colab": {
434 | "base_uri": "https://localhost:8080/",
435 | "height": 279
436 | },
437 | "id": "-YdLmHd-iBMW",
438 | "outputId": "8861ad1f-1309-4057-c289-ebbc4e42853d"
439 | },
440 | "outputs": [
441 | {
442 | "ename": "TypeError",
443 | "evalue": "'tuple' object cannot be interpreted as an integer",
444 | "output_type": "error",
445 | "traceback": [
446 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
447 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
448 | "\u001b[1;32m/home/mehdi/Downloads/Telegram Desktop/ch-12-v3.ipynb Cell 22\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m v[\u001b[39m1\u001b[39;49m,\u001b[39m2\u001b[39;49m]\n",
449 | "\u001b[1;32m/home/mehdi/Downloads/Telegram Desktop/ch-12-v3.ipynb Cell 22\u001b[0m line \u001b[0;36m9\n\u001b[1;32m 7\u001b[0m \u001b[39mcls\u001b[39m \u001b[39m=\u001b[39m \u001b[39mtype\u001b[39m(\u001b[39mself\u001b[39m)\n\u001b[1;32m 8\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mcls\u001b[39m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_components[key]) \u001b[39m# invoke the class to build another Vector instance from a slice of the _components array\u001b[39;00m\n\u001b[0;32m----> 9\u001b[0m index \u001b[39m=\u001b[39m operator\u001b[39m.\u001b[39;49mindex(key)\n\u001b[1;32m 10\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_components[index]\n",
450 | "\u001b[0;31mTypeError\u001b[0m: 'tuple' object cannot be interpreted as an integer"
451 | ]
452 | }
453 | ],
454 | "source": [
455 | "v[1,2]"
456 | ]
457 | },
458 | {
459 | "cell_type": "markdown",
460 | "metadata": {
461 | "id": "tL9gVkaliMhf"
462 | },
463 | "source": [
464 | "### Vector Take #3: Dynamic Atrribute Access"
465 | ]
466 | },
467 | {
468 | "cell_type": "markdown",
469 | "metadata": {
470 | "id": "iPzXC6yoiUcc"
471 | },
472 | "source": [
473 | "* Want to access the first few components with shortcut letters like x, y, z instead of `v[0]`, `v[1]`, `v[2]`.\n",
474 | "* Can provide read-only access to x, y using `@property`.\n",
475 | "* `__getattr__` special method is better.\n",
476 | "* `__getattr__` is invoked by the interpreter when attribute lookup fails.\n",
477 | "* Given expression `obj.x`, Python checks if the `obj` intance has an attribute named `x`.\n",
478 | " * if not, search goes to the class (`obj.__class__`) and then up to the inheritance graph.\n",
479 | " * if not found, `__getattr__` method defined in class of `obj` is called with `self` and name of attribute as string."
480 | ]
481 | },
482 | {
483 | "cell_type": "code",
484 | "execution_count": 14,
485 | "metadata": {
486 | "colab": {
487 | "base_uri": "https://localhost:8080/"
488 | },
489 | "id": "VR8nI9ABiDFK",
490 | "outputId": "dcef21e6-fde7-4245-cda0-c16a332ddfa0"
491 | },
492 | "outputs": [
493 | {
494 | "name": "stdout",
495 | "output_type": "stream",
496 | "text": [
497 | "0.0\n",
498 | "10\n"
499 | ]
500 | },
501 | {
502 | "data": {
503 | "text/plain": [
504 | "Vector([0.0, 1.0, 2.0, 3.0, 4.0])"
505 | ]
506 | },
507 | "execution_count": 14,
508 | "metadata": {},
509 | "output_type": "execute_result"
510 | }
511 | ],
512 | "source": [
513 | "class vector3(Vector2):\n",
514 | " __match_args__ = ('x', 'y', 'z', 't') # allow positional pattern matching on dynamic attributes supported by __getattr__\n",
515 | "\n",
516 | " def __getattr__(self, name):\n",
517 | " cls = type(self) # <2>\n",
518 | " try:\n",
519 | " pos = cls.__match_args__.index(name) # get position of name in __match_args__\n",
520 | " except ValueError: # if name not found\n",
521 | " pos = -1\n",
522 | " if 0 <= pos < len(self._components): # <5>\n",
523 | " return self._components[pos]\n",
524 | " msg = f'{cls.__name__!r} object has no attribute {name!r}' # <6>\n",
525 | " raise AttributeError(msg)\n",
526 | "\n",
527 | "v3 = vector3(range(5))\n",
528 | "print(v3.x)\n",
529 | "v3.x = 10 # Assigning new value to x should raise an exception\n",
530 | "print(v3.x)\n",
531 | "v3 # vector components did not change"
532 | ]
533 | },
534 | {
535 | "cell_type": "code",
536 | "execution_count": 15,
537 | "metadata": {},
538 | "outputs": [
539 | {
540 | "name": "stdout",
541 | "output_type": "stream",
542 | "text": [
543 | "{'_components': array('d', [0.0, 1.0, 2.0, 3.0, 4.0]), 'x': 10}\n"
544 | ]
545 | }
546 | ],
547 | "source": [
548 | "print(v3.__dict__)"
549 | ]
550 | },
551 | {
552 | "cell_type": "markdown",
553 | "metadata": {
554 | "id": "4ydXlaKvln60"
555 | },
556 | "source": [
557 | "* After assinging value to attribute `x`, `v3` object now has `x` attribute so `__getattr__` will no longer be called to retrieve `v3.x`.\n",
558 | "* The interpreter just return the value 10 that is bound to `v3.x`.\n",
559 | "* `__getattr__` pays no attention to instance attributes other than `self._components` from where it retrieves the values of the virtual attributes listed in `__match_args__`.\n",
560 | "* Need to customize the logic for setting attribute in order to avoid inconsistency by implementing `__setattr__`.\n",
561 | "* When implementing `__getattr__`, very often need to code `__setattr__` to avoid inconsistent behavior in objects.\n",
562 | "* If want to allow changing components, can implement:\n",
563 | " * `__setitem__` to enable `v[0] = 1.1`\n",
564 | " * `__setattr__` to make `v.x = 1.1` work.\n",
565 | "* `Vector` is immutable so no need to allow changing components."
566 | ]
567 | },
568 | {
569 | "cell_type": "code",
570 | "execution_count": null,
571 | "metadata": {
572 | "id": "7Kh_jTlLlTCy"
573 | },
574 | "outputs": [],
575 | "source": [
576 | "class Vector3(Vector2):\n",
577 | " __match_args__ = ('x', 'y', 'z', 't')\n",
578 | "\n",
579 | " def __getattr__(self, name):\n",
580 | " cls = type(self)\n",
581 | " try:\n",
582 | " pos = cls.__match_args__.index(name)\n",
583 | " except ValueError:\n",
584 | " pos = -1\n",
585 | " if 0 <= pos < len(self._components):\n",
586 | " return self._components[pos]\n",
587 | " msg = f'{cls.__name__!r} object has no attribute {name!r}'\n",
588 | " raise AttributeError(msg)\n",
589 | "\n",
590 | " def __setattr__(self, name, value):\n",
591 | " cls = type(self)\n",
592 | " if len(name) == 1:\n",
593 | " if name in cls.__match_args__:\n",
594 | " error = 'readonly attribute {attr_name!r}'\n",
595 | " elif name.islower():\n",
596 | " error = \"can't set attributes 'a' to 'z' in {cls_name!r}\"\n",
597 | " else:\n",
598 | " error = ''\n",
599 | " if error:\n",
600 | " msg = error.format(cls_name=cls.__name__, attr_name=name)\n",
601 | " raise AttributeError(msg)\n",
602 | " super().__setattr__(name, value) # call __setattr__ on superclass for standard behavior"
603 | ]
604 | },
605 | {
606 | "cell_type": "markdown",
607 | "metadata": {
608 | "id": "dnFVgeVovKUD"
609 | },
610 | "source": [
611 | "* Can use `__slots__` at class level to prevent setting new instance attributes.\n",
612 | "* Using `__slots__` just to prevent instance attribute creation is not recommended.\n",
613 | "* `__slots__` should be used only to save memory."
614 | ]
615 | },
616 | {
617 | "cell_type": "markdown",
618 | "metadata": {
619 | "id": "-vL14Wdoyi_e"
620 | },
621 | "source": [
622 | "### Vector Take #4: Hashing and a Faster =="
623 | ]
624 | },
625 | {
626 | "cell_type": "markdown",
627 | "metadata": {
628 | "id": "N-ety69VyrBk"
629 | },
630 | "source": [
631 | "* `__hash__` and `__eq__` will make `Vector` instances hashable.\n",
632 | "* Can't use tuple to compute hash like in `Vector2d` because `Vector` instances may be dealing with thousands of components.\n",
633 | "* Use `^` (xor) operator to the hashes of every component in succession ----> `functools.reduce` will be used.\n",
634 | "* Key idea of `reduce()` is to reduce a series of values to a single value:\n",
635 | " * First argument to `reduce()` is a two-argument function.\n",
636 | " * Second argument is an iterable.\n",
637 | " * Third argument is initializer. `initializer` is the value returned if the sequence is empty and is used as the first argument in reducing loop, so it should be the identity value of the operation.\n",
638 | " * When using `reduce`, it's good practice to provide `initializer` to prevent `TypeError` exception.\n",
639 | " * When calling `reduce(fn, list)`, `fn` will be applied to the first pair of elements of the iterable ----> `fn(list[0], list[1])` ----> producing first result `r1`.\n",
640 | " * Then `fn` is applied to `r1` and next element ----> `fn(r1, list[2])` ----> producing second result `r2`.\n",
641 | " * `fn(r2, list[3])` ----> producing `r3`\n",
642 | " * until last element when a single result `rN` is returned.\n",
643 | "* For `^` operator can use `operator.xor`.\n",
644 | "* `operator` module provides functionality of all python infix operators in function form ----> less need to `lambda`.\n",
645 | "* Can use `map-reduce` computation ----> The mapping step produces one hash for each component, reduce step aggregates all hashes with the xor operator.\n",
646 | "* In Python3, `map` is lazy; it creates a generator that yields the results on demand, thus saving memory.\n",
647 | "* `__eq__` method for `Vector` instances that may have thousands of components is inefficient. It builds two tuples copying the entire contents of the operands just to use `__eq__` of tuple. Also it considers `Vector([1,2])` equal to `(1, 2)` which may be a problem"
648 | ]
649 | },
650 | {
651 | "cell_type": "code",
652 | "execution_count": null,
653 | "metadata": {
654 | "id": "IXVM1t6OvGXj"
655 | },
656 | "outputs": [],
657 | "source": [
658 | "import functools\n",
659 | "\n",
660 | "class Vector4(Vector3):\n",
661 | " def __eq__(self, other):\n",
662 | " return (len(self) == len(other) and # Checking len, because zip stops producing values without warning as soon as one of the inputs is exhausted.\n",
663 | " all(a == b for a, b in zip(self, other))) # zip produces a generator of tuples made from the items in each iterable.\n",
664 | "\n",
665 | " def __hash__(self):\n",
666 | " hashes = (hash(x) for x in self)\n",
667 | " return functools.reduce(operator.xor, hashes, 0)"
668 | ]
669 | },
670 | {
671 | "cell_type": "markdown",
672 | "metadata": {
673 | "id": "pRvMH1ON7tyI"
674 | },
675 | "source": [
676 | "* `all` function used in `__eq__` can produce same aggregate computation of `for` loop in one line. If all comparisons between corresponding components in the operands are `True`, result is `True`. As soon as one comparison is `False`, `all` returns `False`."
677 | ]
678 | },
679 | {
680 | "cell_type": "markdown",
681 | "metadata": {
682 | "id": "budh0jxr8LlG"
683 | },
684 | "source": [
685 | "### Vector Take #5: Formatting"
686 | ]
687 | },
688 | {
689 | "cell_type": "markdown",
690 | "metadata": {
691 | "id": "CA913dAO8PyZ"
692 | },
693 | "source": [
694 | "* `Vector` will use spherical coordinates.\n",
695 | "* Change custom format suffix from `'p'` to `'h'`.\n",
696 | "* `'h'` code will produce a display like ``, `r` is the magnitude, and the remaining numbers are the angular components a1, a2, a3.\n",
697 | "* `angle(n)` method compute one of the angular coordinates.\n",
698 | "* `angles()` return an iterable of all angular coordinates."
699 | ]
700 | },
701 | {
702 | "cell_type": "code",
703 | "execution_count": 16,
704 | "metadata": {
705 | "id": "ZXFmIQbj8GGy"
706 | },
707 | "outputs": [],
708 | "source": [
709 | "import itertools\n",
710 | "\n",
711 | "class Vector:\n",
712 | " typecode = 'd'\n",
713 | "\n",
714 | " def __init__(self, components):\n",
715 | " self._components = array(self.typecode, components)\n",
716 | "\n",
717 | " def __iter__(self):\n",
718 | " return iter(self._components)\n",
719 | "\n",
720 | " def __repr__(self):\n",
721 | " components = reprlib.repr(self._components)\n",
722 | " components = components[components.find('['):-1]\n",
723 | " return f'Vector({components})'\n",
724 | "\n",
725 | " def __str__(self):\n",
726 | " return str(tuple(self))\n",
727 | "\n",
728 | " def __bytes__(self):\n",
729 | " return (bytes([ord(self.typecode)]) +\n",
730 | " bytes(self._components))\n",
731 | "\n",
732 | " def __eq__(self, other):\n",
733 | " return (len(self) == len(other) and\n",
734 | " all(a == b for a, b in zip(self, other)))\n",
735 | "\n",
736 | " def __hash__(self):\n",
737 | " hashes = (hash(x) for x in self)\n",
738 | " return functools.reduce(operator.xor, hashes, 0)\n",
739 | "\n",
740 | " def __abs__(self):\n",
741 | " return math.hypot(*self)\n",
742 | "\n",
743 | " def __bool__(self):\n",
744 | " return bool(abs(self))\n",
745 | "\n",
746 | " def __len__(self):\n",
747 | " return len(self._components)\n",
748 | "\n",
749 | " def __getitem__(self, key):\n",
750 | " if isinstance(key, slice):\n",
751 | " cls = type(self)\n",
752 | " return cls(self._components[key])\n",
753 | " index = operator.index(key)\n",
754 | " return self._components[index]\n",
755 | "\n",
756 | " __match_args__ = ('x', 'y', 'z', 't')\n",
757 | "\n",
758 | " def __getattr__(self, name):\n",
759 | " print(\"test\")\n",
760 | " cls = type(self)\n",
761 | " try:\n",
762 | " pos = cls.__match_args__.index(name)\n",
763 | " except ValueError:\n",
764 | " pos = -1\n",
765 | " if 0 <= pos < len(self._components):\n",
766 | " return self._components[pos]\n",
767 | " msg = f'{cls.__name__!r} object has no attribute {name!r}'\n",
768 | " raise AttributeError(msg)\n",
769 | "\n",
770 | " def angle(self, n):\n",
771 | " r = math.hypot(*self[n:])\n",
772 | " a = math.atan2(r, self[n-1])\n",
773 | " if (n == len(self) - 1) and (self[-1] < 0):\n",
774 | " return math.pi * 2 - a\n",
775 | " else:\n",
776 | " return a\n",
777 | "\n",
778 | " def angles(self):\n",
779 | " return (self.angle(n) for n in range(1, len(self)))\n",
780 | "\n",
781 | " def __format__(self, fmt_spec=''):\n",
782 | " if fmt_spec.endswith('h'): # hyperspherical coordinates\n",
783 | " fmt_spec = fmt_spec[:-1]\n",
784 | " coords = itertools.chain([abs(self)],\n",
785 | " self.angles()) # use itertools.chain to produce genexp to iterate seamlessly over the magnitude and angular coordinates\n",
786 | " outer_fmt = '<{}>'\n",
787 | " else:\n",
788 | " coords = self\n",
789 | " outer_fmt = '({})'\n",
790 | " components = (format(c, fmt_spec) for c in coords)\n",
791 | " return outer_fmt.format(', '.join(components))\n",
792 | "\n",
793 | " @classmethod\n",
794 | " def frombytes(cls, octets):\n",
795 | " typecode = chr(octets[0])\n",
796 | " memv = memoryview(octets[1:]).cast(typecode)\n",
797 | " return cls(memv)"
798 | ]
799 | },
800 | {
801 | "cell_type": "code",
802 | "execution_count": 18,
803 | "metadata": {},
804 | "outputs": [
805 | {
806 | "name": "stdout",
807 | "output_type": "stream",
808 | "text": [
809 | "(0.0, 1.0, 2.0)\n"
810 | ]
811 | }
812 | ],
813 | "source": [
814 | "v1 = Vector(range(3))\n",
815 | "print(v1)"
816 | ]
817 | },
818 | {
819 | "cell_type": "markdown",
820 | "metadata": {
821 | "id": "pXUPlNOLBUTS"
822 | },
823 | "source": [
824 | "### Lecturers"
825 | ]
826 | },
827 | {
828 | "cell_type": "markdown",
829 | "metadata": {
830 | "id": "8tsL-0RLBXCD"
831 | },
832 | "source": [
833 | "\n",
834 | "\n",
835 | "* Mohammad Mehdi Heidari: [Linkedin](https://www.linkedin.com/in/mohammad-mehdi-heidari-70b76611a)\n",
836 | "* Saeed Hemmati : [Linkedin](https://www.linkedin.com/in/saeed-hemati/)\n",
837 | "\n",
838 | "Presentation Date : 12-08-2023"
839 | ]
840 | },
841 | {
842 | "cell_type": "markdown",
843 | "metadata": {
844 | "id": "SJb1BZJisCIB"
845 | },
846 | "source": [
847 | "### Reviewers"
848 | ]
849 | },
850 | {
851 | "cell_type": "markdown",
852 | "metadata": {
853 | "id": "v5DzZV8gsHfy"
854 | },
855 | "source": [
856 | "\n",
857 | "\n",
858 | "1. Mahya Asgarian, Review Date : 12-08-2023, [Linkedin](https://www.linkedin.com/in/mahya-asgarian-9a7b13249/)\n"
859 | ]
860 | }
861 | ],
862 | "metadata": {
863 | "colab": {
864 | "provenance": [],
865 | "toc_visible": true
866 | },
867 | "kernelspec": {
868 | "display_name": "Python 3 (ipykernel)",
869 | "language": "python",
870 | "name": "python3"
871 | },
872 | "language_info": {
873 | "codemirror_mode": {
874 | "name": "ipython",
875 | "version": 3
876 | },
877 | "file_extension": ".py",
878 | "mimetype": "text/x-python",
879 | "name": "python",
880 | "nbconvert_exporter": "python",
881 | "pygments_lexer": "ipython3",
882 | "version": "3.11.2"
883 | }
884 | },
885 | "nbformat": 4,
886 | "nbformat_minor": 1
887 | }
888 |
--------------------------------------------------------------------------------