Skip to content

Commit 21b8f52

Browse files
author
Fred Baptiste
committed
Part 4 - Section 14 - Metaprogramming
1 parent c8ba6ed commit 21b8f52

16 files changed

+14996
-0
lines changed
Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"### Decorators and Descriptors - Review"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"#### Decorators"
15+
]
16+
},
17+
{
18+
"cell_type": "markdown",
19+
"metadata": {},
20+
"source": [
21+
"Decorators are in fact a form of metaprogramming."
22+
]
23+
},
24+
{
25+
"cell_type": "markdown",
26+
"metadata": {},
27+
"source": [
28+
"Decorators are pieces of code that modify the behavior of another piece of code."
29+
]
30+
},
31+
{
32+
"cell_type": "markdown",
33+
"metadata": {},
34+
"source": [
35+
"For example, if we want to write a \"super duper debugger\", that prints out every function call and the arguments it was called with, we can easily modify (decorate) any function we want to \"debug\" without modifying the function body directly:"
36+
]
37+
},
38+
{
39+
"cell_type": "code",
40+
"execution_count": 1,
41+
"metadata": {},
42+
"outputs": [],
43+
"source": [
44+
"from functools import wraps"
45+
]
46+
},
47+
{
48+
"cell_type": "code",
49+
"execution_count": 2,
50+
"metadata": {},
51+
"outputs": [],
52+
"source": [
53+
"def debugger(fn):\n",
54+
" @wraps(fn)\n",
55+
" def inner(*args, **kwargs):\n",
56+
" print(f'{fn.__qualname__}', args, kwargs)\n",
57+
" return fn(*args, **kwargs)\n",
58+
" return inner"
59+
]
60+
},
61+
{
62+
"cell_type": "markdown",
63+
"metadata": {},
64+
"source": [
65+
"And now we can just decorate our functions:"
66+
]
67+
},
68+
{
69+
"cell_type": "code",
70+
"execution_count": 4,
71+
"metadata": {},
72+
"outputs": [],
73+
"source": [
74+
"@debugger\n",
75+
"def func_1(*args, **kwargs):\n",
76+
" pass\n",
77+
"\n",
78+
"@debugger\n",
79+
"def func_2(*args, **kwargs):\n",
80+
" pass"
81+
]
82+
},
83+
{
84+
"cell_type": "code",
85+
"execution_count": 5,
86+
"metadata": {},
87+
"outputs": [
88+
{
89+
"name": "stdout",
90+
"output_type": "stream",
91+
"text": [
92+
"func_1 (10, 20) {'kw': 'a'}\n"
93+
]
94+
}
95+
],
96+
"source": [
97+
"func_1(10, 20, kw='a')"
98+
]
99+
},
100+
{
101+
"cell_type": "code",
102+
"execution_count": 6,
103+
"metadata": {},
104+
"outputs": [
105+
{
106+
"name": "stdout",
107+
"output_type": "stream",
108+
"text": [
109+
"func_2 (10,) {}\n"
110+
]
111+
}
112+
],
113+
"source": [
114+
"func_2(10)"
115+
]
116+
},
117+
{
118+
"cell_type": "markdown",
119+
"metadata": {},
120+
"source": [
121+
"The advantage of this decorator approach is that if we want to modify our debugger output, we only need to modify the decorator function once, and when we re-run our program the new changes take effect for every decorated function."
122+
]
123+
},
124+
{
125+
"cell_type": "markdown",
126+
"metadata": {},
127+
"source": [
128+
"#### Descriptors"
129+
]
130+
},
131+
{
132+
"cell_type": "markdown",
133+
"metadata": {},
134+
"source": [
135+
"Although it may not seem like it, descriptors are also a form of metaprogramming."
136+
]
137+
},
138+
{
139+
"cell_type": "markdown",
140+
"metadata": {},
141+
"source": [
142+
"Descriptors essentially allow us to modify the behavior of the dot (`.`) operator."
143+
]
144+
},
145+
{
146+
"cell_type": "markdown",
147+
"metadata": {},
148+
"source": [
149+
"If we have a simple class like so:"
150+
]
151+
},
152+
{
153+
"cell_type": "code",
154+
"execution_count": 7,
155+
"metadata": {},
156+
"outputs": [],
157+
"source": [
158+
"class Point:\n",
159+
" def __init__(self, x, y):\n",
160+
" self.x = x\n",
161+
" self.y = y"
162+
]
163+
},
164+
{
165+
"cell_type": "markdown",
166+
"metadata": {},
167+
"source": [
168+
"Then the dot operator works directly against the object's dictionary (namespace):"
169+
]
170+
},
171+
{
172+
"cell_type": "code",
173+
"execution_count": 8,
174+
"metadata": {},
175+
"outputs": [],
176+
"source": [
177+
"p = Point(10, 20)"
178+
]
179+
},
180+
{
181+
"cell_type": "code",
182+
"execution_count": 9,
183+
"metadata": {},
184+
"outputs": [
185+
{
186+
"data": {
187+
"text/plain": [
188+
"10"
189+
]
190+
},
191+
"execution_count": 9,
192+
"metadata": {},
193+
"output_type": "execute_result"
194+
}
195+
],
196+
"source": [
197+
"p.x"
198+
]
199+
},
200+
{
201+
"cell_type": "code",
202+
"execution_count": 10,
203+
"metadata": {},
204+
"outputs": [],
205+
"source": [
206+
"p.x=100"
207+
]
208+
},
209+
{
210+
"cell_type": "code",
211+
"execution_count": 11,
212+
"metadata": {},
213+
"outputs": [
214+
{
215+
"data": {
216+
"text/plain": [
217+
"{'x': 100, 'y': 20}"
218+
]
219+
},
220+
"execution_count": 11,
221+
"metadata": {},
222+
"output_type": "execute_result"
223+
}
224+
],
225+
"source": [
226+
"p.__dict__"
227+
]
228+
},
229+
{
230+
"cell_type": "markdown",
231+
"metadata": {},
232+
"source": [
233+
"So here, `p.x` was basically referencing the instance dictionary. But descriptors allows us to essentially redefine how the `.` operator works."
234+
]
235+
},
236+
{
237+
"cell_type": "markdown",
238+
"metadata": {},
239+
"source": [
240+
"We saw properties, and properties are based on descriptors, but they are not always conducive to DRY code as we saw earlier."
241+
]
242+
},
243+
{
244+
"cell_type": "markdown",
245+
"metadata": {},
246+
"source": [
247+
"Let's say we want to provide type checking on the `x` and `y` components of the `Point` class."
248+
]
249+
},
250+
{
251+
"cell_type": "markdown",
252+
"metadata": {},
253+
"source": [
254+
"We can use a data descriptor to essentially modify the way the `.` operator works by passing it through getter and setter (and deleter) functions - and we also eliminate repetitive code:"
255+
]
256+
},
257+
{
258+
"cell_type": "code",
259+
"execution_count": 14,
260+
"metadata": {},
261+
"outputs": [],
262+
"source": [
263+
"class IntegerField:\n",
264+
" def __set_name__(self, owner, name):\n",
265+
" self.name = name\n",
266+
" \n",
267+
" def __get__(self, instance, owner):\n",
268+
" return instance.__dict__.get(self.name, None)\n",
269+
" \n",
270+
" def __set__(self, instance, value):\n",
271+
" if not isinstance(value, int):\n",
272+
" raise TypeError('Must be an integer.')\n",
273+
" instance.__dict__[self.name] = value"
274+
]
275+
},
276+
{
277+
"cell_type": "code",
278+
"execution_count": 15,
279+
"metadata": {},
280+
"outputs": [],
281+
"source": [
282+
"class Point:\n",
283+
" x = IntegerField()\n",
284+
" y = IntegerField()\n",
285+
" \n",
286+
" def __init__(self, x, y):\n",
287+
" self.x = x\n",
288+
" self.y = y"
289+
]
290+
},
291+
{
292+
"cell_type": "code",
293+
"execution_count": 16,
294+
"metadata": {},
295+
"outputs": [],
296+
"source": [
297+
"p = Point(10, 20)"
298+
]
299+
},
300+
{
301+
"cell_type": "code",
302+
"execution_count": 17,
303+
"metadata": {},
304+
"outputs": [
305+
{
306+
"data": {
307+
"text/plain": [
308+
"(10, 20)"
309+
]
310+
},
311+
"execution_count": 17,
312+
"metadata": {},
313+
"output_type": "execute_result"
314+
}
315+
],
316+
"source": [
317+
"p.x, p.y"
318+
]
319+
},
320+
{
321+
"cell_type": "code",
322+
"execution_count": 18,
323+
"metadata": {},
324+
"outputs": [
325+
{
326+
"name": "stdout",
327+
"output_type": "stream",
328+
"text": [
329+
"Must be an integer.\n"
330+
]
331+
}
332+
],
333+
"source": [
334+
"try:\n",
335+
" p.x = 10.5\n",
336+
"except TypeError as ex:\n",
337+
" print(ex)"
338+
]
339+
},
340+
{
341+
"cell_type": "markdown",
342+
"metadata": {},
343+
"source": [
344+
"So, without changing the interface of our class, we replaced the default functionality of the `.` operator with another piece of code (that implemented the descriptor protocol)."
345+
]
346+
},
347+
{
348+
"cell_type": "markdown",
349+
"metadata": {},
350+
"source": [
351+
"We'll come back to decorators, and see how we can actually decorate entire classes (so-called **class decorators**)."
352+
]
353+
}
354+
],
355+
"metadata": {
356+
"kernelspec": {
357+
"display_name": "Python 3",
358+
"language": "python",
359+
"name": "python3"
360+
},
361+
"language_info": {
362+
"codemirror_mode": {
363+
"name": "ipython",
364+
"version": 3
365+
},
366+
"file_extension": ".py",
367+
"mimetype": "text/x-python",
368+
"name": "python",
369+
"nbconvert_exporter": "python",
370+
"pygments_lexer": "ipython3",
371+
"version": "3.7.0"
372+
}
373+
},
374+
"nbformat": 4,
375+
"nbformat_minor": 2
376+
}

0 commit comments

Comments
 (0)