Newer
Older
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Decorators\n",
"\n",
"\n",
"Remember that in Python, everything is an object, including functions. This\n",
"means that we can do things like:\n",
"\n",
"\n",
"- Pass a function as an argument to another function.\n",
"- Create/define a function inside another function.\n",
"- Write a function which returns another function.\n",
"\n",
"\n",
"These abilities mean that we can do some neat things with functions in Python.\n",
"\n",
"\n",
"* [Overview](#overview)\n",
"* [Decorators on methods](#decorators-on-methods)\n",
"* [Example - memoization](#example-memoization)\n",
"* [Decorators with arguments](#decorators-with-arguments)\n",
"* [Chaining decorators](#chaining-decorators)\n",
"* [Decorator classes](#decorator-classes)\n",
"* [Appendix: Functions are not special](#appendix-functions-are-not-special)\n",
"* [Appendix: Closures](#appendix-closures)\n",
"* [Appendix: Decorators without arguments versus decorators with arguments](#appendix-decorators-without-arguments-versus-decorators-with-arguments)\n",
"* [Appendix: Per-instance decorators](#appendix-per-instance-decorators)\n",
"* [Appendix: Preserving function metadata](#appendix-preserving-function-metadata)\n",
"* [Appendix: Class decorators](#appendix-class-decorators)\n",
"* [Useful references](#useful-references)\n",
"\n",
"\n",
"<a class=\"anchor\" id=\"overview\"></a>\n",
"## Overview\n",
"\n",
"\n",
"Let's say that we want a way to calculate the execution time of any function\n",
"(this example might feel familiar to you if you have gone through the\n",
"practical on operator overloading).\n",
"\n",
"\n",
"Our first attempt at writing such a function might look like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"def timeFunc(func, *args, **kwargs):\n",
"\n",
" start = time.time()\n",
" retval = func(*args, **kwargs)\n",
" end = time.time()\n",
"\n",
" print('Ran {} in {:0.2f} seconds'.format(func.__name__, end - start))\n",
"\n",
" return retval"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `timeFunc` function accepts another function, `func`, as its first\n",
"argument. It calls `func`, passing it all of the other arguments, and then\n",
"prints the time taken for `func` to complete:"
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import numpy.linalg as npla\n",
"\n",
"def inverse(a):\n",
" return npla.inv(a)\n",
"\n",
"data = np.random.random((2000, 2000))\n",
"invdata = timeFunc(inverse, data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But this means that whenever we want to time something, we have to call the\n",
"`timeFunc` function directly. Let's take advantage of the fact that we can\n",
"define a function inside another funciton. Look at the next block of code\n",
"carefully, and make sure you understand what our new `timeFunc` implementation\n",
"is doing."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"def timeFunc(func):\n",
"\n",
" def wrapperFunc(*args, **kwargs):\n",
"\n",
" start = time.time()\n",
" retval = func(*args, **kwargs)\n",
" end = time.time()\n",
"\n",
" print('Ran {} in {:0.2f} seconds'.format(func.__name__, end - start))\n",
"\n",
" return retval\n",
"\n",
" return wrapperFunc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This new `timeFunc` function is again passed a function `func`, but this time\n",
"as its sole argument. It then creates and returns a new function,\n",
"`wrapperFunc`. This `wrapperFunc` function calls and times the function that\n",
"was passed to `timeFunc`. But note that when `timeFunc` is called,\n",
"`wrapperFunc` is _not_ called - it is only created and returned.\n",
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"\n",
"\n",
"Let's use our new `timeFunc` implementation:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import numpy.linalg as npla\n",
"\n",
"def inverse(a):\n",
" return npla.inv(a)\n",
"\n",
"data = np.random.random((2000, 2000))\n",
"inverse = timeFunc(inverse)\n",
"invdata = inverse(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, we did the following:\n",
"\n",
"\n",
"1. We defined a function called `inverse`:\n",
"\n",
" > ```\n",
" > def inverse(a):\n",
" > return npla.inv(a)\n",
" > ```\n",
"\n",
"2. We passed the `inverse` function to the `timeFunc` function, and\n",
" re-assigned the return value of `timeFunc` back to `inverse`:\n",
"\n",
" > ```\n",
" > inverse = timeFunc(inverse)\n",
" > ```\n",
"\n",
"3. We called the new `inverse` function:\n",
"\n",
" > ```\n",
" > invdata = inverse(data)\n",
" > ```\n",
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
"\n",
"\n",
"So now the `inverse` variable refers to an instantiation of `wrapperFunc`,\n",
"which holds a reference to the original definition of `inverse`.\n",
"\n",
"\n",
"> If this is not clear, take a break now and read through the appendix on how\n",
"> [functions are not special](#appendix-functions-are-not-special).\n",
"\n",
"\n",
"Guess what? We have just created a __decorator__. A decorator is simply a\n",
"function which accepts a function as its input, and returns another function\n",
"as its output. In the example above, we have _decorated_ the `inverse`\n",
"function with the `timeFunc` decorator.\n",
"\n",
"\n",
"Python provides an alternative syntax for decorating one function with\n",
"another, using the `@` character. The approach that we used to decorate\n",
"`inverse` above:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def inverse(a):\n",
" return npla.inv(a)\n",
"\n",
"inverse = timeFunc(inverse)\n",
"invdata = inverse(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"is semantically equivalent to this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@timeFunc\n",
"def inverse(a):\n",
" return npla.inv(a)\n",
"\n",
"invdata = inverse(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a class=\"anchor\" id=\"decorators-on-methods\"></a>\n",
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
"## Decorators on methods\n",
"\n",
"\n",
"Applying a decorator to the methods of a class works in the same way:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy.linalg as npla\n",
"\n",
"class MiscMaths(object):\n",
"\n",
" @timeFunc\n",
" def inverse(self, a):\n",
" return npla.inv(a)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, the `inverse` method of all `MiscMaths` instances will be timed:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mm1 = MiscMaths()\n",
"mm2 = MiscMaths()\n",
"\n",
"i1 = mm1.inverse(np.random.random((1000, 1000)))\n",
"i2 = mm2.inverse(np.random.random((1500, 1500)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that only one `timeFunc` decorator was created here - the `timeFunc`\n",
"function was only called once - when the `MiscMaths` class was defined. This\n",
"might be clearer if we re-write the above code in the following (equivalent)\n",
"manner:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class MiscMaths(object):\n",
" def inverse(self, a):\n",
" return npla.inv(a)\n",
"\n",
"MiscMaths.inverse = timeFunc(MiscMaths.inverse)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So only one `wrapperFunc` function exists, and this function is _shared_ by\n",
"all instances of the `MiscMaths` class - (such as the `mm1` and `mm2`\n",
"instances in the example above). In many cases this is not a problem, but\n",
"there can be situations where you need each instance of your class to have its\n",
"own unique decorator.\n",
"\n",
"\n",
"> If you are interested in solutions to this problem, take a look at the\n",
"> appendix on [per-instance decorators](#appendix-per-instance-decorators).\n",
"\n",
"\n",
"<a class=\"anchor\" id=\"example-memoization\"></a>\n",
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
"## Example - memoization\n",
"\n",
"\n",
"Let's move onto another example.\n",
"[Meowmoization](https://en.wikipedia.org/wiki/Memoization) is a common\n",
"performance optimisation technique used in cats. I mean software. Essentially,\n",
"memoization refers to the process of maintaining a cache for a function which\n",
"performs some expensive calculation. When the function is executed with a set\n",
"of inputs, the calculation is performed, and then a copy of the inputs and the\n",
"result are cached. If the function is called again with the same inputs, the\n",
"cached result can be returned.\n",
"\n",
"\n",
"This is a perfect problem to tackle with decorators:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def memoize(func):\n",
"\n",
" cache = {}\n",
"\n",
" def wrapper(*args):\n",
"\n",
" # is there a value in the cache\n",
" # for this set of inputs?\n",
" cached = cache.get(args, None)\n",
"\n",
" # If not, call the function,\n",
" # and cache the result.\n",
" if cached is None:\n",
" cached = func(*args)\n",
" cache[args] = cached\n",
" else:\n",
" print('Cached {}({}): {}'.format(func.__name__, args, cached))\n",
"\n",
" return cached\n",
"\n",
" return wrapper"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now use our `memoize` decorator to add a memoization cache to any\n",
"function. Let's memoize a function which generates the $n^{th}$ number in the\n",
"[Fibonacci series](https://en.wikipedia.org/wiki/Fibonacci_number):"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@memoize\n",
"def fib(n):\n",
"\n",
" if n in (0, 1):\n",
" print('fib({}) = {}'.format(n, n))\n",
" return n\n",
"\n",
" twoback = 1\n",
" oneback = 1\n",
" val = 1\n",
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
"\n",
" for _ in range(2, n):\n",
"\n",
" val = oneback + twoback\n",
" twoback = oneback\n",
" oneback = val\n",
"\n",
" print('fib({}) = {}'.format(n, val))\n",
"\n",
" return val"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For a given input, when `fib` is called the first time, it will calculate the\n",
"$n^{th}$ Fibonacci number:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for i in range(10):\n",
" fib(i)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, on repeated calls with the same input, the calculation is skipped,\n",
"and instead the result is retrieved from the memoization cache:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for i in range(10):\n",
" fib(i)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> If you are wondering how the `wrapper` function is able to access the\n",
"> `cache` variable, refer to the [appendix on closures](#appendix-closures).\n",
"\n",
"\n",
"<a class=\"anchor\" id=\"decorators-with-arguments\"></a>\n",
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
"## Decorators with arguments\n",
"\n",
"\n",
"Continuing with our memoization example, let's say that we want to place a\n",
"limit on the maximum size that our cache can grow to. For example, the output\n",
"of our function might have large memory requirements, so we can only afford to\n",
"store a handful of pre-calculated results. It would be nice to be able to\n",
"specify the maximum cache size when we define our function to be memoized,\n",
"like so:\n",
"\n",
"\n",
"> ```\n",
"> # cache at most 10 results\n",
"> @limitedMemoize(10):\n",
"> def fib(n):\n",
"> ...\n",
"> ```\n",
"\n",
"\n",
"In order to support this, our `memoize` decorator function needs to be\n",
"modified - it is currently written to accept a function as its sole argument,\n",
"but we need it to accept a cache size limit."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from collections import OrderedDict\n",
"\n",
"def limitedMemoize(maxSize):\n",
"\n",
" cache = OrderedDict()\n",
"\n",
" def decorator(func):\n",
" def wrapper(*args):\n",
"\n",
" # is there a value in the cache\n",
" # for this set of inputs?\n",
" cached = cache.get(args, None)\n",
"\n",
" # If not, call the function,\n",
" # and cache the result.\n",
" if cached is None:\n",
"\n",
" cached = func(*args)\n",
"\n",
" # If the cache has grown too big,\n",
" # remove the oldest item. In practice\n",
" # it would make more sense to remove\n",
" # the item with the oldest access\n",
" # time (or remove the least recently\n",
" # used item, as the built-in\n",
" # @functools.lru_cache does), but this\n",
" # is good enough for now!\n",
" if len(cache) >= maxSize:\n",
" cache.popitem(last=False)\n",
"\n",
" cache[args] = cached\n",
" else:\n",
" print('Cached {}({}): {}'.format(func.__name__, args, cached))\n",
"\n",
" return cached\n",
" return wrapper\n",
" return decorator"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> We used the handy\n",
"> [`collections.OrderedDict`](https://docs.python.org/3.5/library/collections.html#collections.OrderedDict)\n",
"> class here which preserves the insertion order of key-value pairs.\n",
"\n",
"\n",
"This is starting to look a little complicated - we now have _three_ layers of\n",
"functions. This is necessary when you wish to write a decorator which accepts\n",
"arguments (refer to the\n",
"[appendix](#appendix-decorators-without-arguments-versus-decorators-with-arguments)\n",
"for more details).\n",
"\n",
"\n",
"But this `limitedMemoize` decorator is used in essentially the same way as our\n",
"earlier `memoize` decorator:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@limitedMemoize(5)\n",
"def fib(n):\n",
"\n",
" print('fib({}) = 1'.format(n))\n",
"\n",
" twoback = 1\n",
" oneback = 1\n",
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
"\n",
" for _ in range(2, n):\n",
"\n",
" val = oneback + twoback\n",
" twoback = oneback\n",
" oneback = val\n",
"\n",
" print('fib({}) = {}'.format(n, val))\n",
"\n",
" return val"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Except that now, the `fib` function will only cache up to 5 values."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fib(10)\n",
"fib(11)\n",
"fib(12)\n",
"fib(13)\n",
"fib(14)\n",
"print('The result for 10 should come from the cache')\n",
"fib(10)\n",
"fib(15)\n",
"print('The result for 10 should no longer be cached')\n",
"fib(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a class=\"anchor\" id=\"chaining-decorators\"></a>\n",
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
"## Chaining decorators\n",
"\n",
"\n",
"Decorators can easily be chained, or nested:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"\n",
"@timeFunc\n",
"@memoize\n",
"def expensiveFunc(n):\n",
" time.sleep(n)\n",
" return n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> Remember that this is semantically equivalent to the following:\n",
">\n",
"> ```\n",
"> def expensiveFunc(n):\n",
"> time.sleep(n)\n",
"> return n\n",
">\n",
"> expensiveFunc = timeFunc(memoize(expensiveFunc))\n",
"> ```\n",
"\n",
"\n",
"Now we can see the effect of our memoization layer on performance:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> Note that in Python 3.2 and newer you can use the\n",
"> [`functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache)\n",
"> to memoize your functions.\n",
"\n",
"\n",
"<a class=\"anchor\" id=\"decorator-classes\"></a>\n",
"## Decorator classes\n",
"\n",
"\n",
"By now, you will have gained the impression that a decorator is a function\n",
"which _decorates_ another function. But if you went through the practical on\n",
"operator overloading, you might remember the special `__call__` method, that\n",
"allows an object to be called as if it were a function.\n",
"This feature allows us to write our decorators as classes, instead of\n",
"functions. This can be handy if you are writing a decorator that has\n",
"complicated behaviour, and/or needs to maintain some sort of state which\n",
"cannot be easily or elegantly written using nested functions.\n",
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
"As an example, let's say we are writing a framework for unit testing. We want\n",
"to be able to \"mark\" our test functions like so, so they can be easily\n",
"identified and executed:\n",
"\n",
"\n",
"> ```\n",
"> @unitTest\n",
"> def testblerk():\n",
"> \"\"\"tests the blerk algorithm.\"\"\"\n",
"> ...\n",
"> ```\n",
"\n",
"\n",
"With a decorator like this, we wouldn't need to worry about where our tests\n",
"are located - they will all be detected because we have marked them as test\n",
"functions. What does this `unitTest` decorator look like?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class TestRegistry(object):\n",
"\n",
" def __init__(self):\n",
" self.testFuncs = []\n",
"\n",
" def __call__(self, func):\n",
" self.testFuncs.append(func)\n",
"\n",
" def listTests(self):\n",
" print('All registered tests:')\n",
" for test in self.testFuncs:\n",
" print(' ', test.__name__)\n",
"\n",
" def runTests(self):\n",
" for test in self.testFuncs:\n",
" print('Running test {:10s} ... '.format(test.__name__), end='')\n",
" try:\n",
" test()\n",
" print('passed!')\n",
" except Exception as e:\n",
" print('failed!')\n",
"\n",
"registry = TestRegistry()\n",
"\n",
"# Alias our registry to \"unitTest\"\n",
"# so that we can register tests\n",
"# with a \"@unitTest\" decorator.\n",
"unitTest = registry"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So we've defined a class, `TestRegistry`, and created an instance of it,\n",
"`registry`, which will manage all of our unit tests. Now, in order to \"mark\"\n",
"any function as being a unit test, we just need to use the `unitTest`\n",
"decorator (which is simply a reference to our `TestRegistry` instance):"
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@unitTest\n",
"def testFoo():\n",
" assert 'a' in 'bcde'\n",
"\n",
"@unitTest\n",
"def testBar():\n",
" assert 1 > 0\n",
"\n",
"@unitTest\n",
"def testBlerk():\n",
" assert 9 % 2 == 0"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that these functions have been registered with our `TestRegistry`\n",
"instance, we can run them all:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"registry.listTests()\n",
"registry.runTests()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> Unit testing is something which you must do! This is __especially__\n",
"> important in an interpreted language such as Python, where there is no\n",
"> compiler to catch all of your mistakes.\n",
">\n",
"> Python has a built-in\n",
"> [`unittest`](https://docs.python.org/3.5/library/unittest.html) module,\n",
"> however the third-party [`pytest`](https://docs.pytest.org/en/latest/) and\n",
"> [`nose`](http://nose2.readthedocs.io/en/latest/) are popular. It is also\n",
"> wise to combine your unit tests with\n",
"> [`coverage`](https://coverage.readthedocs.io/en/coverage-4.5.1/), which\n",
"> tells you how much of your code was executed, or _covered_ when your\n",
"> tests were run.\n",
"\n",
"<a class=\"anchor\" id=\"appendix-functions-are-not-special\"></a>\n",
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
"## Appendix: Functions are not special\n",
"\n",
"\n",
"When we write a statement like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"a = [1, 2, 3]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"the variable `a` is a reference to a `list`. We can create a new reference to\n",
"the same list, and delete `a`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b = a\n",
"del a"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Deleting `a` doesn't affect the list at all - the list still exists, and is\n",
"now referred to by a variable called `b`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('b: ', b)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`a` has, however, been deleted:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print('a: ', a)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The variables `a` and `b` are just references to a list that is sitting in\n",
"memory somewhere - renaming or removing a reference does not have any effect\n",
"upon the list<sup>2</sup>.\n",
"\n",
"\n",
"If you are familiar with C or C++, you can think of a variable in Python as\n",
"like a `void *` pointer - it is just a pointer of an unspecified type, which\n",
"is pointing to some item in memory (which does have a specific type). Deleting\n",
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
"the pointer does not have any effect upon the item to which it was pointing.\n",
"\n",
"\n",
"> <sup>2</sup> Until no more references to the list exist, at which point it\n",
"> will be\n",
"> [garbage-collected](https://www.quora.com/How-does-garbage-collection-in-Python-work-What-are-the-pros-and-cons).\n",
"\n",
"\n",
"Now, functions in Python work in _exactly_ the same way as variables. When we\n",
"define a function like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def inverse(a):\n",
" return npla.inv(a)\n",
"\n",
"print(inverse)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"there is nothing special about the name `inverse` - `inverse` is just a\n",
"reference to a function that resides somewhere in memory. We can create a new\n",
"reference to this function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"inv2 = inverse"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And delete the old reference:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"del inverse"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But the function still exists, and is still callable, via our second\n",
"reference:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(inv2)\n",
"data = np.random.random((10, 10))\n",
"invdata = inv2(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So there is nothing special about functions in Python - they are just items\n",
"that reside somewhere in memory, and to which we can create as many references\n",
"as we like.\n",
"\n",
"\n",
"> If it bothers you that `print(inv2)` resulted in\n",
"> `<function inverse at ...>`, and not `<function inv2 at ...>`, then refer to\n",
"> the appendix on\n",
"> [preserving function metdata](#appendix-preserving-function-metadata).\n",
"<a class=\"anchor\" id=\"appendix-closures\"></a>\n",
"## Appendix: Closures\n",
"\n",
"\n",
"Whenever we define or use a decorator, we are taking advantage of a concept\n",
"called a [_closure_][wiki-closure]. Take a second to re-familiarise yourself\n",
"with our `memoize` decorator function from earlier - when `memoize` is called,\n",
"it creates and returns a function called `wrapper`:\n",
"\n",
"\n",
"[wiki-closure]: https://en.wikipedia.org/wiki/Closure_(computer_programming)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def memoize(func):\n",
" cache = {}\n",
" def wrapper(*args):\n",
" # is there a value in the cache\n",
" # for this set of inputs?\n",
" cached = cache.get(args, None)\n",
" # If not, call the function,\n",
" # and cache the result.\n",
" if cached is None:\n",
" cached = func(*args)\n",
" cache[args] = cached\n",
" else:\n",
" print('Cached {}({}): {}'.format(func.__name__, args, cached))\n",
" return cached\n",
"\n",
" return wrapper"