Skip to content

Commit 6404b29

Browse files
author
Fred Baptiste
committed
Added Descriptors section
1 parent dac60dc commit 6404b29

11 files changed

+10920
-0
lines changed
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"### Descriptors"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"Python **descriptors** are simply objects that implement the **descriptor protocol**.\n",
15+
"\n",
16+
"The protocol is comprised of the following special methods - not all are required.\n",
17+
"- `__get__`: used to retrieve the property value\n",
18+
"- `__set__`: used to store the property value (we'll see where we can do this in a bit)\n",
19+
"- `__del__`: delete a property from the instance\n",
20+
"- `__set_name__`: new to Python 3.6, we can use this to capture the property name as it is being defined in the owner class (the class where the property is defined).\n",
21+
"\n",
22+
"There are two types of descriptors we need to distingush as I explain in the video lecture:\n",
23+
"- non-data descriptors: these are descriptors that only implement `__get__` (and optionally `__set_name__`)\n",
24+
"- data descriptors: these implement the `__set__` method, and normally, also the `__get__` method."
25+
]
26+
},
27+
{
28+
"cell_type": "markdown",
29+
"metadata": {},
30+
"source": [
31+
"Let's create a simple non-data descriptor:"
32+
]
33+
},
34+
{
35+
"cell_type": "code",
36+
"execution_count": 1,
37+
"metadata": {},
38+
"outputs": [],
39+
"source": [
40+
"from datetime import datetime\n",
41+
"\n",
42+
"class TimeUTC:\n",
43+
" def __get__(self, instance, owner_class):\n",
44+
" return datetime.utcnow().isoformat()"
45+
]
46+
},
47+
{
48+
"cell_type": "markdown",
49+
"metadata": {},
50+
"source": [
51+
"So `TimeUTC` is a class that implements the `__get__` method only, and is therefore considered a non-data descriptor."
52+
]
53+
},
54+
{
55+
"cell_type": "markdown",
56+
"metadata": {},
57+
"source": [
58+
"We can now use it to create properties in other classes:"
59+
]
60+
},
61+
{
62+
"cell_type": "code",
63+
"execution_count": 2,
64+
"metadata": {},
65+
"outputs": [],
66+
"source": [
67+
"class Logger:\n",
68+
" current_time = TimeUTC()"
69+
]
70+
},
71+
{
72+
"cell_type": "markdown",
73+
"metadata": {},
74+
"source": [
75+
"Note that `current_time` is a class attribute:"
76+
]
77+
},
78+
{
79+
"cell_type": "code",
80+
"execution_count": 3,
81+
"metadata": {},
82+
"outputs": [
83+
{
84+
"data": {
85+
"text/plain": [
86+
"mappingproxy({'__module__': '__main__',\n",
87+
" 'current_time': <__main__.TimeUTC at 0x7fdcd84bbd68>,\n",
88+
" '__dict__': <attribute '__dict__' of 'Logger' objects>,\n",
89+
" '__weakref__': <attribute '__weakref__' of 'Logger' objects>,\n",
90+
" '__doc__': None})"
91+
]
92+
},
93+
"execution_count": 3,
94+
"metadata": {},
95+
"output_type": "execute_result"
96+
}
97+
],
98+
"source": [
99+
"Logger.__dict__"
100+
]
101+
},
102+
{
103+
"cell_type": "markdown",
104+
"metadata": {},
105+
"source": [
106+
"We can access that attribute from an instance of the `Logger` class:"
107+
]
108+
},
109+
{
110+
"cell_type": "code",
111+
"execution_count": 4,
112+
"metadata": {},
113+
"outputs": [],
114+
"source": [
115+
"l = Logger()"
116+
]
117+
},
118+
{
119+
"cell_type": "code",
120+
"execution_count": 5,
121+
"metadata": {},
122+
"outputs": [
123+
{
124+
"data": {
125+
"text/plain": [
126+
"'2019-07-13T20:47:06.391770'"
127+
]
128+
},
129+
"execution_count": 5,
130+
"metadata": {},
131+
"output_type": "execute_result"
132+
}
133+
],
134+
"source": [
135+
"l.current_time"
136+
]
137+
},
138+
{
139+
"cell_type": "markdown",
140+
"metadata": {},
141+
"source": [
142+
"We can also access it from the class itself, and for now it behaves the same (we'll come back to that later):"
143+
]
144+
},
145+
{
146+
"cell_type": "code",
147+
"execution_count": 6,
148+
"metadata": {},
149+
"outputs": [
150+
{
151+
"data": {
152+
"text/plain": [
153+
"'2019-07-13T20:47:06.405059'"
154+
]
155+
},
156+
"execution_count": 6,
157+
"metadata": {},
158+
"output_type": "execute_result"
159+
}
160+
],
161+
"source": [
162+
"Logger.current_time"
163+
]
164+
},
165+
{
166+
"cell_type": "markdown",
167+
"metadata": {},
168+
"source": [
169+
"Let's consider another example."
170+
]
171+
},
172+
{
173+
"cell_type": "markdown",
174+
"metadata": {},
175+
"source": [
176+
"Suppose we want to create class that allows us to select a random suit and random card from that suit from a deck of cards (with replacement, i.e. the same card can be picked more than once)."
177+
]
178+
},
179+
{
180+
"cell_type": "markdown",
181+
"metadata": {},
182+
"source": [
183+
"We could approach it this way:"
184+
]
185+
},
186+
{
187+
"cell_type": "code",
188+
"execution_count": 7,
189+
"metadata": {},
190+
"outputs": [],
191+
"source": [
192+
"from random import choice, seed\n",
193+
"\n",
194+
"class Deck:\n",
195+
" @property\n",
196+
" def suit(self):\n",
197+
" return choice(('Spade', 'Heart', 'Diamond', 'Club'))\n",
198+
" \n",
199+
" @property\n",
200+
" def card(self):\n",
201+
" return choice(tuple('23456789JQKA') + ('10',))"
202+
]
203+
},
204+
{
205+
"cell_type": "code",
206+
"execution_count": 8,
207+
"metadata": {},
208+
"outputs": [],
209+
"source": [
210+
"d = Deck()"
211+
]
212+
},
213+
{
214+
"cell_type": "code",
215+
"execution_count": 9,
216+
"metadata": {},
217+
"outputs": [
218+
{
219+
"name": "stdout",
220+
"output_type": "stream",
221+
"text": [
222+
"8 Club\n",
223+
"2 Diamond\n",
224+
"J Club\n",
225+
"8 Diamond\n",
226+
"9 Diamond\n",
227+
"Q Heart\n",
228+
"J Heart\n",
229+
"6 Heart\n",
230+
"10 Spade\n",
231+
"Q Diamond\n"
232+
]
233+
}
234+
],
235+
"source": [
236+
"seed(0)\n",
237+
"\n",
238+
"for _ in range(10):\n",
239+
" print(d.card, d.suit)"
240+
]
241+
},
242+
{
243+
"cell_type": "markdown",
244+
"metadata": {},
245+
"source": [
246+
"This was pretty easy, but as you can see both properties essentially did the same thing - they picked a random choice from some iterable.\n",
247+
"\n",
248+
"Let's rewrite this using a custom descriptor:"
249+
]
250+
},
251+
{
252+
"cell_type": "code",
253+
"execution_count": 10,
254+
"metadata": {},
255+
"outputs": [],
256+
"source": [
257+
"class Choice:\n",
258+
" def __init__(self, *choices):\n",
259+
" self.choices = choices\n",
260+
" \n",
261+
" def __get__(self, instance, owner_class):\n",
262+
" return choice(self.choices)"
263+
]
264+
},
265+
{
266+
"cell_type": "markdown",
267+
"metadata": {},
268+
"source": [
269+
"And now we can rewrite our `Deck` class this way:"
270+
]
271+
},
272+
{
273+
"cell_type": "code",
274+
"execution_count": 11,
275+
"metadata": {},
276+
"outputs": [],
277+
"source": [
278+
"class Deck:\n",
279+
" suit = Choice('Spade', 'Heart', 'Diamond', 'Club')\n",
280+
" card = Choice(*'23456789JQKA', '10')"
281+
]
282+
},
283+
{
284+
"cell_type": "code",
285+
"execution_count": 12,
286+
"metadata": {},
287+
"outputs": [
288+
{
289+
"name": "stdout",
290+
"output_type": "stream",
291+
"text": [
292+
"8 Club\n",
293+
"2 Diamond\n",
294+
"J Club\n",
295+
"8 Diamond\n",
296+
"9 Diamond\n",
297+
"Q Heart\n",
298+
"J Heart\n",
299+
"6 Heart\n",
300+
"10 Spade\n",
301+
"Q Diamond\n"
302+
]
303+
}
304+
],
305+
"source": [
306+
"seed(0)\n",
307+
"\n",
308+
"d = Deck()\n",
309+
"\n",
310+
"for _ in range(10):\n",
311+
" print(d.card, d.suit)"
312+
]
313+
},
314+
{
315+
"cell_type": "markdown",
316+
"metadata": {},
317+
"source": [
318+
"Of course we are not limited to just cards, we could use it in other classes too:"
319+
]
320+
},
321+
{
322+
"cell_type": "code",
323+
"execution_count": 13,
324+
"metadata": {},
325+
"outputs": [],
326+
"source": [
327+
"class Dice:\n",
328+
" die_1 = Choice(1,2,3,4,5,6)\n",
329+
" die_2 = Choice(1,2,3,4,5,6)\n",
330+
" die_3 = Choice(1,2,3,4,5,6)"
331+
]
332+
},
333+
{
334+
"cell_type": "code",
335+
"execution_count": 14,
336+
"metadata": {},
337+
"outputs": [
338+
{
339+
"name": "stdout",
340+
"output_type": "stream",
341+
"text": [
342+
"4 4 1\n",
343+
"3 5 4\n",
344+
"4 3 4\n",
345+
"3 5 2\n",
346+
"5 2 3\n",
347+
"2 1 5\n",
348+
"3 5 6\n",
349+
"5 2 3\n",
350+
"1 6 1\n",
351+
"6 3 4\n"
352+
]
353+
}
354+
],
355+
"source": [
356+
"seed(0)\n",
357+
"\n",
358+
"dice = Dice()\n",
359+
"for _ in range(10):\n",
360+
" print(dice.die_1, dice.die_2, dice.die_3)"
361+
]
362+
},
363+
{
364+
"cell_type": "code",
365+
"execution_count": null,
366+
"metadata": {},
367+
"outputs": [],
368+
"source": []
369+
}
370+
],
371+
"metadata": {
372+
"kernelspec": {
373+
"display_name": "Python 3",
374+
"language": "python",
375+
"name": "python3"
376+
},
377+
"language_info": {
378+
"codemirror_mode": {
379+
"name": "ipython",
380+
"version": 3
381+
},
382+
"file_extension": ".py",
383+
"mimetype": "text/x-python",
384+
"name": "python",
385+
"nbconvert_exporter": "python",
386+
"pygments_lexer": "ipython3",
387+
"version": "3.6.7"
388+
}
389+
},
390+
"nbformat": 4,
391+
"nbformat_minor": 2
392+
}

0 commit comments

Comments
 (0)