Python  DECORATORS  
DEMYSTIFIED	

{

“event”:      “PyCon  ES  2013”	
“author”:  “Pablo  Enfedaque”	
“twi4er”:  “pablitoev56”
Do  you  know  what’s  happening  each  
time  you  use  the  @    (at)  symbol  to  
decorate  a  function  or  class?	
	
Today  we  are  going  to  see  how  
Python’s  decorators  syntactic  sugar  
works  under  the  hood	

Welcome!	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
And  that’s  why  we  will  talk  about  
Python  namespaces  and  scopes	

Welcome!	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
And  finally  will  manually  implement  
and  apply  a  handcrafted  decorator	

Welcome!	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Let’s  start  implementing  some	
useful  stuff  for  the  talk	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
from collections import OrderedDict	
	
CACHE = OrderedDict()	
MAX_SIZE = 100	
	
def set_key(key, value):	
"Set a key value, removing oldest key if MAX_SIZE exceeded"	
CACHE[key] = value	
if len(CACHE) > MAX_SIZE:	
CACHE.popitem(last=False)	
	
def get_key(key):	
"Retrieve a key value from the cache, or None if not found"	
return CACHE.get(key, None)	
	
	
>>> set_key("my_key", "the_value”)	
>>> print(get_key("my_key"))	
the_value	
	
>>> print(get_key("not_found_key"))	
None	

A  simple  software  cache	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def factorial(n):	
"Return n! (the factorial of n): n! = n * (n-1)!"	
if n < 2:	
return 1	
return n * factorial(n - 1)	
	
	
>>> list(map(factorial, range(10)))	
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]	
	
	
	
def fibonacci(n):	
"Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)"	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
	
>>> list(map(fibonacci, range(10)))	
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]	
	

Factorial  and  fibonacci  functions	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Pre4y  easy,  right?	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
However…	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
from collections import OrderedDict	
	
CACHE = OrderedDict()	
MAX_SIZE = 100	
	
def set_key(key, value):	
"Set a key value, removing oldest key if MAX_SIZE exceeded"	
CACHE[key] = value	
if len(CACHE) > MAX_SIZE:	
CACHE.popitem(last=False)	
	
def get_key(key):	
"Retrieve a key value from the cache, or None if not found"	
return CACHE.get(key, None)	
	
	
>>> set_key("my_key", "the_value”)	
>>> print(get_key("my_key"))	
the_value	
	
>>> print(get_key("not_found_key"))	
None	

How  do  we  access  this  a4ribute?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def factorial(n):	
"Return n! (the factorial of n): n! = n * (n-1)!"	
if n < 2:	
return 1	
return n * factorial(n - 1)	
	
	
>>> list(map(factorial, range(10)))	
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]	
	
	
	
def fibonacci(n):	
"Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)"	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
	
>>> list(map(fibonacci, range(10)))	
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]	
	

How  are  recursive  calls  possible?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
>>> from os import path	
	
>>> print(type(path), id(path))	
<class 'module'> 4300435112	
	
	
	
>>> from sys import path	
	
>>> print(type(path), id(path))	
<class 'list'> 4298480008	
	
	
	
def split_path(path, sep="/"):	
print(type(path), id(path))	
return path.split(sep)	
	
>>> split_path("/this/is/a/full/path")	
<class 'str'> 4302038120	
['', 'this', 'is', 'a', 'full', 'path']	
	

A  simpler  case	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
>>> from os import path	
	
>>> print(type(path), id(path))	
<class 'module'> 4300435112	
	
	
	
>>> from sys import path	
	
>>> print(type(path), id(path))	
<class 'list'> 4298480008	
	
	
	
def split_path(path, sep="/"):	
print(type(path), id(path))	
return path.split(sep)	
	
>>> split_path("/this/is/a/full/path")	
<class 'str'> 4302038120	
['', 'this', 'is', 'a', 'full', 'path']	
	

The  same  name  defined  several  times?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Let  me  introduce  Python  namespaces	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
A  namespace  is  a  mapping  
from  names  to  objects	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
> 

The  set  of  built-­‐‑in  names  (functions,  exceptions)	

> 

Global  names  in  a  module  (including  imports)	

> 

Local  names  in  a  function  invocation	

> 

Names  defined  in  top-­‐‑level  invocation  of  interpreter	

Python  namespaces  examples	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
There  is  no  relation  between  names  in  
different  namespaces	
	

Two  modules  or  functions  may  define  the  same  
name  without  confusion	

Python  namespaces	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Namespaces  are  created  (and  deleted)  
at  different  moments  and  have  
different  lifetimes	

Python  namespaces	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
The  built-­‐‑ins  namespace  is  created  
when  the  Python  interpreter  starts	
	

And  is  never  deleted	

Python  namespaces  lifetimes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
A  module  global  namespace  is  
created  when  the  module  definition  is  
read-­‐‑in  (when  it  is  imported)	
	

Normally  it  lasts  until  the  interpreter  quits	

Python  namespaces  lifetimes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
A  function  local  namespace  is  created  
each  time  it  is  called	
	

It  is  deleted  when  the  function  returns  or  raises	

Python  namespaces  lifetimes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
And  what  about  Python  scopes?	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
A  scope  is  a  textual  region  

of  a  program  where  a  
namespace  is  directly  
accessible	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Scopes  are  determined  statically	
but  used  dynamically	

Python  scopes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
At  any  time  during  execution,  there  
are  at  least  three  nested  scopes  whose  
namespaces  are  directly  accessible	

Python  scopes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
1. 

The  innermost  scope  contains  the  local  
names	
> 

2. 

3. 

The  scopes  of  any  enclosing  functions,  
with  non-­‐‑local,  but  also  non-­‐‑global  names	

The  next-­‐‑to-­‐‑last  scope  contains  the  
current  module'ʹs  global  names	
The  outermost  scope  is  the  namespace  
containing  built-­‐‑in  names	

Python  nested  scopes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Names  are  searched  in  nested  scopes  
from  inside  out	
	

From  locals  to  built-­‐‑ins	

Python  nested  scopes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
$ python	
Python 2.7.5 (default, Aug 25 2013, 00:04:04)	
[GCC 4.2.1 Compatible Apple LLVM 5.0
(clang-500.0.68)] on darwin	
Type "help", "copyright", "credits" or "license"
for more information.	
>>> import cache	
>>> cache.set_key("my_key", 7)	
>>> cache.get_key("my_key")	
7	
>>>	
"""	

Simple cache implementation	
"""	
from collections import OrderedDict	
	
CACHE = OrderedDict()	
MAX_SIZE = 100	
	
def set_key(key, value):	
"Set a key value, removing oldest key if MAX_SIZE exceeded"	
CACHE[key] = value	
if len(CACHE) > MAX_SIZE:	
CACHE.popitem(last=False)	
	
def get_key(key):	
"Retrieve a key value from the cache, or None if not found"	
return CACHE.get(key, None)	

Python  scopes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
$ python	
Python 2.7.5 (default, Aug 25 2013, 00:04:04)	
[GCC 4.2.1 Compatible Apple LLVM 5.0
(clang-500.0.68)] on darwin	
Type "help", "copyright", "credits" or "license"
for more information.	
>>> import cache	
>>> cache.set_key("my_key", 7)	
>>> cache.get_key("my_key")	
7	
>>>	
"""	

The  outermost  scope:	
built-­‐‑in  names	

The  next-­‐‑to-­‐‑last  scope:	

Simple cache implementation	
current  module’s  global  names	
"""	
from collections import OrderedDict	
	
CACHE = OrderedDict()	
MAX_SIZE = 100	
	
def set_key(key, value):	
"Set a key value, removing oldest key if MAX_SIZE exceeded"	
CACHE[key] = value	
if len(CACHE) > MAX_SIZE:	
The  innermost  scope:	
CACHE.popitem(last=False)	
current  local  names	
	
def get_key(key):	
"Retrieve a key value from the cache, or None if not found"	
return CACHE.get(key, None)	

Python  scopes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def get_power_func(y):	
print("Creating function to raise to {}".format(y))	
	
def power_func(x):	
print("Calling to raise {} to power of {}".format(x, y))	
x = pow(x, y)	
return x	
	
return power_func	
	
	
>>> raise_to_4 = get_power_func(4)	
Creating function to raise to 3	
	
>>> x = 3	
>>> print(raise_to_4(x))	
Calling to raise 3 to power of 4	
81	
	
>>> print(x)	
3	

Another  more  complex  case	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def get_power_func(y):	
print("Creating function to raise to {}".format(y))	
	
def power_func(x):	
print("Calling to raise {} to power of {}".format(x, y))	
x = pow(x, y)	
return x	
	
return power_func	
	
	
>>> raise_to_4 = get_power_func(4)	
Creating function to raise to 3	
	
>>> x = 3	
>>> print(raise_to_4(x))	
Calling to raise 3 to power of 4	
81	
	
>>> print(x)	
3	

Where  is  y  defined?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def get_power_func(y):	
print("Creating function to raise to {}".format(y))	
	
def power_func(x):	
print("Calling to raise {} to power of {}".format(x, y))	
x = pow(x, y)	
return x	
The  innermost  scope:  local  names	
	
return power_func	
	
Enclosing  function  scope:	
	
>>> raise_to_4 = get_power_func(4)	
non-­‐‑local  non-­‐‑global  names	
Creating function to raise to 3	
	
>>> x = 3	
>>> print(raise_to_4(x))	
Calling to raise 3 to power of 4	
81	
	
The  next-­‐‑to-­‐‑last  scope:	
>>> print(x)	
3	
current  module’s  global  names	

Nested  scopes	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def get_power_func(y):	
print("Creating function to raise to {}".format(y))	
	
def power_func(x):	
print("Calling to raise {} to power of {}".format(x, y))	
x = pow(x, y)	
return x	
	
return power_func	
	
>>> raise_to_4 = get_power_func(4)	
Creating function to raise to 3	
	
>>> print(raise_to_4.__globals__)	
{'x': 3, 'raise_to_4': <function
get_power_func.<locals>.power_func at 0x100658488>,
'get_power_func': <function get_power_func at 0x1003b6048>, ...}	
	
	
>>> print(raise_to_4.__closure__)	
(<cell at 0x10065f048: int object at 0x10023b280>,)	

There  is  a  closure!	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
A  function  closure  is  a  
reference  to  each  of  the  non-­‐‑
local  variables  of  the  function	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def get_power_func(y):	
print("Creating function to raise to {}".format(y))	
	
def power_func(x):	
print("Calling to raise {} to power of {}".format(x, y))	
x = pow(x, y)	
return x	
	
return power_func	
	
>>> raise_to_4 = get_power_func(4)	
Creating function to raise to 3	
	
>>> print(raise_to_4.__globals__)	
{'x': 3, 'raise_to_4': <function
get_power_func.<locals>.power_func at 0x100658488>,
'get_power_func': <function get_power_func at 0x1003b6048>, ...}	
	
	
>>> print(raise_to_4.__closure__)	
(<cell at 0x10065f048: int object at 0x10023b280>,)	

So,  where  is  y  defined?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Well,  maybe  you  are  wondering	
where  are  the  decorators  in  this  talk…	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
So,  let’s  manually  apply  a  decorator	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def factorial(n):	
"Return n! (the factorial of n): n! = n * (n-1)!"	
if n < 2:	
return 1	
return n * factorial(n - 1)	
	
>>> start = time.time()	
>>> factorial(35)	
10333147966386144929666651337523200000000	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0007369518280029297	
	
def fibonacci(n):	
"Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)"	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> start = time.time()	
>>> fibonacci(35)	
9227465	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 6.916048049926758	

Let’s  go  back  to  these  functions	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
4	

3	
2	

5	

2	
1	

1	

1	

0	

0	
3	

2	

1	

1	

0	

fibonacci(5)  recursive  calls  graph	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
from collections import OrderedDict	
	
CACHE = OrderedDict()	
MAX_SIZE = 100	
	
def set_key(key, value):	
"Set a key value, removing oldest key if MAX_SIZE exceeded"	
CACHE[key] = value	
if len(CACHE) > MAX_SIZE:	
CACHE.popitem(last=False)	
	
def get_key(key):	
"Retrieve a key value from the cache, or None if not found"	
return CACHE.get(key, None)	
	
	
>>> set_key("my_key", "the_value”)	
>>> print(get_key("my_key"))	
the_value	
	
>>> print(get_key("not_found_key"))	
None	

Do  you  remember  we  have  a  cache?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import cache	
	
def fibonacci(n):	
"Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)"	
if n < 2:	
return n	
fib = cache.get_key(n)	
if fib is None:	
fib = fibonacci(n - 1) + fibonacci(n - 2)	
cache.set_key(n, fib)	
return fib	
	
>>> start = time.time()	
>>> fibonacci(35)	
9227465	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0007810592651367188	
	
>>> start = time.time()	
>>> fibonacci(100)	
354224848179261915075	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0013179779052734375	

What  about  this  version?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import cache	
	
def fibonacci(n):	
"Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)"	
if n < 2:	
return n	
fib = cache.get_key(n)	
if fib is None:	
fib = fibonacci(n - 1) + fibonacci(n - 2)	
cache.set_key(n, fib)	
return fib	
	
>>> start = time.time()	
>>> fibonacci(35)	
9227465	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0007810592651367188	
	
>>> start = time.time()	
>>> fibonacci(100)	
354224848179261915075	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0013179779052734375	

DRY:  Don’t  Repeat  Yourself!	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Pay  a4ention  to  the  magic  trick	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import time	
import cache	
	
def fibonacci(n): # The function remains unchanged	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> real_fibonacci = fibonacci	
	
def fibonacci(n):	
fib = cache.get_key(n)	
if fib is None:	
fib = real_fibonacci(n)	
cache.set_key(n, fib)	
return fib	
	
	
>>> start = time.time()	
>>> fibonacci(35)	
9227465	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0010080337524414062	

Original  function  is  not  modified	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import time	
import cache	
	
def fibonacci(n): # The function remains unchanged	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> real_fibonacci = fibonacci	
	
def fibonacci(n):	
fib = cache.get_key(n)	
if fib is None:	
fib = real_fibonacci(n)	
cache.set_key(n, fib)	
return fib	
	
	
>>> start = time.time()	
>>> fibonacci(35)	
9227465	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0010080337524414062	

Which  function  is  called  here?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import time	
The  next-­‐‑to-­‐‑last  scope:	
import cache	
current  module’s  global  names	
	
def fibonacci(n): # The function remains unchanged	
if n < 2:	
The  innermost  scope:	
return n	
current  local  names	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> real_fibonacci = fibonacci	
	
def fibonacci(n):	
fib = cache.get_key(n)	
if fib is None:	
fib = real_fibonacci(n)	
cache.set_key(n, fib)	
return fib	
	
	
>>> start = time.time()	
>>> fibonacci(35)	
9227465	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0010080337524414062	

Remember  the  scopes…	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
And  now  the  trick  in  slow  motion	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> print(id(fibonacci))	
4298858568	

{	
        fibonacci:  4298858568	
}	

Global  names	

4298858568:  <function  fibonacci  at  0x1003b6048>	

if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	

	

Objects	

1.  Create  original  fibonacci  function	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> print(id(fibonacci))	
4298858568	
	
>>> real_fib = fibonacci	

{	
        fibonacci:  4298858568,	
        real_fib:        4298858568,	
}	

Global  names	

4298858568:  <function  fibonacci  at  0x1003b6048>	

if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	

	
	

Objects	

2.  Create  alternative  name  pointing  
to  the  same  function  object	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def fibonacci(n):	
{	
if n < 2:	
        fibonacci:  4302081696,	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	         real_fib:        4298858568,	
	
}	
>>> print(id(fibonacci))	
4298858568	
Global  names	
	
>>> real_fib = fibonacci	
	
4298858568:  <function  fibonacci  at  0x1003b6048>	
def fibonacci(n):	
if n < 2:	
fib = cache.get_key(n)	
return n	
if fib is None:	
return fibonacci(n - 1) + fibonacci(n - 2)	
fib = real_fib (n)	
	
cache.set_key(n, fib)	
	
return fib	
4302081696:  <function  fibonacci  at  0x1006c8ea0>	
	
fib = cache.get_key(n)	
>>> print(id(fibonacci))	
if fib is None:	
4302081696	
fib = real_fib (n)	
	
cache.set_key(n, fib)	
>>> print(id(real_fib))	
return fib	
Objects	
4298858568	

	

3.  Replace  original  name  with  new  a  
function  which  calls  the  alternative  name	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def fibonacci(n):	
{	
if n < 2:	
        fibonacci:  4302081696,	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	         real_fib:        4298858568,	
	
}	
>>> print(id(fibonacci))	
4298858568	
Global  names	
	
>>> real_fib = fibonacci	
	
4298858568:  <function  fibonacci  at  0x1003b6048>	
def fibonacci(n):	
if n < 2:	
fib = cache.get_key(n)	
return n	
if fib is None:	
return fibonacci(n - 1) + fibonacci(n - 2)	
fib = real_fib (n)	
	
cache.set_key(n, fib)	
	
return fib	
4302081696:  <function  fibonacci  at  0x1006c8ea0>	
	
fib = cache.get_key(n)	
>>> print(id(fibonacci))	
if fib is None:	
4302081696	
fib = real_fib (n)	
	
cache.set_key(n, fib)	
>>> print(id(real_fib))	
return fib	
Objects	
4298858568	

	

This  way  we  swap  both  functions	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def fibonacci(n):	
{	
if n < 2:	
        fibonacci:  4302081696,	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	         real_fib:        4298858568,	
	
}	
>>> print(id(fibonacci))	
4298858568	
Global  names	
	
>>> real_fib = fibonacci	
	
4298858568:  <function  fibonacci  at  0x1003b6048>	
def fibonacci(n):	
if n < 2:	
fib = cache.get_key(n)	
return n	
if fib is None:	
return fibonacci(n - 1) + fibonacci(n - 2)	
fib = real_fib (n)	
	
cache.set_key(n, fib)	
	
return fib	
4302081696:  <function  fibonacci  at  0x1006c8ea0>	
	
fib = cache.get_key(n)	
>>> print(id(fibonacci))	
if fib is None:	
4302081696	
fib = real_fib (n)	
	
cache.set_key(n, fib)	
>>> print(id(real_fib))	
return fib	
Objects	
4298858568	

	

But  the  original  function  does  not  know  it	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Let’s  make  this  trick  fully  reusable	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Let’s  make  it  work  with  any*  function	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import cache	
	
def memoize_any_function(func_to_memoize):	
"Return a wrapped version of the function using memoization"	
	
def memoized_version_of_func(n):	
"Wrapper using memoization"	
res = cache.get_key(n)	
if res is None:	
res = func_to_memoize(n) # Call the real function	
cache.set_key(n, res)	
return res	
return memoized_version_of_func	
	
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> fibonacci = memoize_any_function(fibonacci)	

A  factory  of  memoization  functions	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import cache	
	
def memoize_any_function(func_to_memoize):	
"Return a wrapped version of the function using memoization"	
	
def memoized_version_of_func(n):	
"Wrapper using memoization"	
res = cache.get_key(n)	
if res is None:	
res = func_to_memoize(n) # Call the real function	
cache.set_key(n, res)	
return res	
return memoized_version_of_func	
	
def factorial(n):	
if n < 2:	
return 1	
return n * factorial(n - 1)	
	
>>> factorial= memoize_any_function(factorial)	

A  factory  of  memoization  functions	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import time	
	
>>> start = time.time()	
>>> fibonacci(250)	
7896325826131730509282738943634332893686268675876375	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.0009610652923583984	
	
>>> start = time.time()	
>>> factorial(250)	
3232856260909107732320814552024368470994843717673780666747942427
1128237475551112094888179153710281994509285073531894329267309317
1280899082279103027907128192167652724018926473321804118626100683
2925365133678939089569935713530175040513178760077247933065402339
0061648255522488194365725860573992226412548329822048491377217766
5064127685880715312897877767295191399084437747870258917297325515
0283241787320658188482062478582659808848825548800000000000000000
000000000000000000000000000000000000000000000	
>>> print("Elapsed:", time.time() - start)	
Elapsed: 0.00249481201171875	

It  works  with  any*  function	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
And  finally,  at  long  last,  decorators	

{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import cache	
	
def memoize_any_function(func_to_memoize):	
"Return a wrapped version of the function using memoization"	
	
def memoized_version_of_func(n):	
"Wrapper using memoization"	
res = cache.get_key(n)	
if res is None:	
res = func_to_memoize(n) # Call the real function	
cache.set_key(n, res)	
return res	
return memoized_version_of_func	
	
	
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> fibonacci = memoize_any_function(fibonacci)	

Pay  a4ention…	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
import cache	
	
def memoize_any_function(func_to_memoize):	
"Return a wrapped version of the function using memoization"	
	
def memoized_version_of_func(n):	
"Wrapper using memoization"	
res = cache.get_key(n)	
if res is None:	
res = func_to_memoize(n) # Call the real function	
cache.set_key(n, res)	
return res	
return memoized_version_of_func	
	
@memoize_any_function	
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
	

Did  you  spot  the  difference?	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> fibonacci = memoize_any_function(fibonacci)	

This  is  the  only  thing  the  @  does	
	

Calls  a  decorator  providing  the  decorated  function,  
then  makes  the  function  name  point  to  the  result	
@memoize_any_function	
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n – 2)	

Decorators  demystified	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n - 2)	
	
>>> fibonacci = memoize_any_function(fibonacci)	

This  is  the  only  thing  the  @  does	
	

Calls  a  decorator  providing  the  decorated  function,  
then  makes  the  function  name  point  to  the  result	
@memoize_any_function	
def fibonacci(n):	
if n < 2:	
return n	
return fibonacci(n - 1) + fibonacci(n – 2)	

Decorators  demystified	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
Thanks  for  coming!	
	
	

Slides:  h4p://goo.gl/bIlG9R  	

Q&A	
{  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}

More Related Content

PDF
EuroPython 2015 - Decorators demystified
KEY
Perl Web Client
PDF
Nubilus Perl
PDF
PostgreSQL Open SV 2018
PDF
Go for the paranoid network programmer
PDF
Legacy applications - 4Developes konferencja, Piotr Pasich
PDF
From Zero to Application Delivery with NixOS
PDF
Symfony2 revealed
EuroPython 2015 - Decorators demystified
Perl Web Client
Nubilus Perl
PostgreSQL Open SV 2018
Go for the paranoid network programmer
Legacy applications - 4Developes konferencja, Piotr Pasich
From Zero to Application Delivery with NixOS
Symfony2 revealed

What's hot (20)

PDF
Whispered secrets
PPTX
10 tips for making Bash a sane programming language
PDF
The Ring programming language version 1.6 book - Part 48 of 189
PDF
Server Side Events
PDF
Finding Clojure
PDF
JSON and Swift, Still A Better Love Story Than Twilight
ODP
My app is secure... I think
PDF
Go for the would be network programmer
PPT
Php introduction
PDF
2020 Droid Knights CustomLint 적용기
PDF
PuppetDB, Puppet Explorer and puppetdbquery
KEY
PDF
Don't do this
PDF
#SPUG - Legacy applications
PDF
Kamailio and VoIP Wild World
ODP
My app is secure... I think
PDF
DEF CON 27 - PATRICK WARDLE - harnessing weapons of Mac destruction
PPTX
Corinna Status 2022.pptx
PDF
Gabriele Lana - The Magic of Elixir
PDF
The state of your own hypertext preprocessor
Whispered secrets
10 tips for making Bash a sane programming language
The Ring programming language version 1.6 book - Part 48 of 189
Server Side Events
Finding Clojure
JSON and Swift, Still A Better Love Story Than Twilight
My app is secure... I think
Go for the would be network programmer
Php introduction
2020 Droid Knights CustomLint 적용기
PuppetDB, Puppet Explorer and puppetdbquery
Don't do this
#SPUG - Legacy applications
Kamailio and VoIP Wild World
My app is secure... I think
DEF CON 27 - PATRICK WARDLE - harnessing weapons of Mac destruction
Corinna Status 2022.pptx
Gabriele Lana - The Magic of Elixir
The state of your own hypertext preprocessor
Ad

Similar to Decorators demystified (20)

PDF
A tour of Python
PDF
An overview of Python 2.7
PDF
Luciano Ramalho - Fluent Python_ Clear, Concise, and Effective Programming-O'...
PDF
Processing data with Python, using standard library modules you (probably) ne...
PPTX
Learn python in 20 minutes
PPTX
Introduction to the basics of Python programming (part 3)
PDF
Python lecture 05
PPT
python language programming presentation
PDF
Intro to Python
PDF
Python Viva Interview Questions PDF By ScholarHat
DOCX
Python Interview Questions For Experienced
PPT
PYTHON
PDF
Python: The Dynamic!
PDF
Intermediate python
PPT
ComandosDePython_ComponentesBasicosImpl.ppt
PPTX
About Python
PPTX
Python 101++: Let's Get Down to Business!
PPTX
Introduction to Python programming Language
PPTX
IoT-Week1-Day1-Lab.pptx
PDF
Python 2.5 reference card (2009)
A tour of Python
An overview of Python 2.7
Luciano Ramalho - Fluent Python_ Clear, Concise, and Effective Programming-O'...
Processing data with Python, using standard library modules you (probably) ne...
Learn python in 20 minutes
Introduction to the basics of Python programming (part 3)
Python lecture 05
python language programming presentation
Intro to Python
Python Viva Interview Questions PDF By ScholarHat
Python Interview Questions For Experienced
PYTHON
Python: The Dynamic!
Intermediate python
ComandosDePython_ComponentesBasicosImpl.ppt
About Python
Python 101++: Let's Get Down to Business!
Introduction to Python programming Language
IoT-Week1-Day1-Lab.pptx
Python 2.5 reference card (2009)
Ad

More from Pablo Enfedaque (7)

PDF
Why I miss MongoDB
PDF
Python 2 vs. Python 3
PDF
Execution model and other must-know's
PDF
Sprayer: low latency, reliable multichannel messaging
PDF
The (unknown) collections module
PDF
Python: the coolest is yet to come
PDF
From Oracle to MongoDB
Why I miss MongoDB
Python 2 vs. Python 3
Execution model and other must-know's
Sprayer: low latency, reliable multichannel messaging
The (unknown) collections module
Python: the coolest is yet to come
From Oracle to MongoDB

Recently uploaded (20)

PDF
1 - Historical Antecedents, Social Consideration.pdf
PPT
Module 1.ppt Iot fundamentals and Architecture
PPTX
Custom Battery Pack Design Considerations for Performance and Safety
PPT
What is a Computer? Input Devices /output devices
PDF
A contest of sentiment analysis: k-nearest neighbor versus neural network
PDF
Flame analysis and combustion estimation using large language and vision assi...
PPTX
2018-HIPAA-Renewal-Training for executives
DOCX
search engine optimization ppt fir known well about this
PDF
Getting started with AI Agents and Multi-Agent Systems
PDF
Hybrid horned lizard optimization algorithm-aquila optimizer for DC motor
PDF
NewMind AI Weekly Chronicles – August ’25 Week III
PDF
Zenith AI: Advanced Artificial Intelligence
PPTX
Final SEM Unit 1 for mit wpu at pune .pptx
PPTX
AI IN MARKETING- PRESENTED BY ANWAR KABIR 1st June 2025.pptx
PDF
Hindi spoken digit analysis for native and non-native speakers
PPTX
Modernising the Digital Integration Hub
PPTX
Configure Apache Mutual Authentication
PDF
Architecture types and enterprise applications.pdf
PPT
Galois Field Theory of Risk: A Perspective, Protocol, and Mathematical Backgr...
PDF
Two-dimensional Klein-Gordon and Sine-Gordon numerical solutions based on dee...
1 - Historical Antecedents, Social Consideration.pdf
Module 1.ppt Iot fundamentals and Architecture
Custom Battery Pack Design Considerations for Performance and Safety
What is a Computer? Input Devices /output devices
A contest of sentiment analysis: k-nearest neighbor versus neural network
Flame analysis and combustion estimation using large language and vision assi...
2018-HIPAA-Renewal-Training for executives
search engine optimization ppt fir known well about this
Getting started with AI Agents and Multi-Agent Systems
Hybrid horned lizard optimization algorithm-aquila optimizer for DC motor
NewMind AI Weekly Chronicles – August ’25 Week III
Zenith AI: Advanced Artificial Intelligence
Final SEM Unit 1 for mit wpu at pune .pptx
AI IN MARKETING- PRESENTED BY ANWAR KABIR 1st June 2025.pptx
Hindi spoken digit analysis for native and non-native speakers
Modernising the Digital Integration Hub
Configure Apache Mutual Authentication
Architecture types and enterprise applications.pdf
Galois Field Theory of Risk: A Perspective, Protocol, and Mathematical Backgr...
Two-dimensional Klein-Gordon and Sine-Gordon numerical solutions based on dee...

Decorators demystified

  • 1. Python  DECORATORS   DEMYSTIFIED { “event”:      “PyCon  ES  2013” “author”:  “Pablo  Enfedaque” “twi4er”:  “pablitoev56”
  • 2. Do  you  know  what’s  happening  each   time  you  use  the  @    (at)  symbol  to   decorate  a  function  or  class? Today  we  are  going  to  see  how   Python’s  decorators  syntactic  sugar   works  under  the  hood Welcome! {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 3. And  that’s  why  we  will  talk  about   Python  namespaces  and  scopes Welcome! {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 4. And  finally  will  manually  implement   and  apply  a  handcrafted  decorator Welcome! {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 5. Let’s  start  implementing  some useful  stuff  for  the  talk {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 6. from collections import OrderedDict CACHE = OrderedDict() MAX_SIZE = 100 def set_key(key, value): "Set a key value, removing oldest key if MAX_SIZE exceeded" CACHE[key] = value if len(CACHE) > MAX_SIZE: CACHE.popitem(last=False) def get_key(key): "Retrieve a key value from the cache, or None if not found" return CACHE.get(key, None) >>> set_key("my_key", "the_value”) >>> print(get_key("my_key")) the_value >>> print(get_key("not_found_key")) None A  simple  software  cache {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 7. def factorial(n): "Return n! (the factorial of n): n! = n * (n-1)!" if n < 2: return 1 return n * factorial(n - 1) >>> list(map(factorial, range(10))) [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] def fibonacci(n): "Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)" if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> list(map(fibonacci, range(10))) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] Factorial  and  fibonacci  functions {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 8. Pre4y  easy,  right? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 9. However… {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 10. from collections import OrderedDict CACHE = OrderedDict() MAX_SIZE = 100 def set_key(key, value): "Set a key value, removing oldest key if MAX_SIZE exceeded" CACHE[key] = value if len(CACHE) > MAX_SIZE: CACHE.popitem(last=False) def get_key(key): "Retrieve a key value from the cache, or None if not found" return CACHE.get(key, None) >>> set_key("my_key", "the_value”) >>> print(get_key("my_key")) the_value >>> print(get_key("not_found_key")) None How  do  we  access  this  a4ribute? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 11. def factorial(n): "Return n! (the factorial of n): n! = n * (n-1)!" if n < 2: return 1 return n * factorial(n - 1) >>> list(map(factorial, range(10))) [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] def fibonacci(n): "Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)" if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> list(map(fibonacci, range(10))) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] How  are  recursive  calls  possible? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 12. >>> from os import path >>> print(type(path), id(path)) <class 'module'> 4300435112 >>> from sys import path >>> print(type(path), id(path)) <class 'list'> 4298480008 def split_path(path, sep="/"): print(type(path), id(path)) return path.split(sep) >>> split_path("/this/is/a/full/path") <class 'str'> 4302038120 ['', 'this', 'is', 'a', 'full', 'path'] A  simpler  case {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 13. >>> from os import path >>> print(type(path), id(path)) <class 'module'> 4300435112 >>> from sys import path >>> print(type(path), id(path)) <class 'list'> 4298480008 def split_path(path, sep="/"): print(type(path), id(path)) return path.split(sep) >>> split_path("/this/is/a/full/path") <class 'str'> 4302038120 ['', 'this', 'is', 'a', 'full', 'path'] The  same  name  defined  several  times? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 14. Let  me  introduce  Python  namespaces {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 15. A  namespace  is  a  mapping   from  names  to  objects {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 16. >  The  set  of  built-­‐‑in  names  (functions,  exceptions) >  Global  names  in  a  module  (including  imports) >  Local  names  in  a  function  invocation >  Names  defined  in  top-­‐‑level  invocation  of  interpreter Python  namespaces  examples {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 17. There  is  no  relation  between  names  in   different  namespaces Two  modules  or  functions  may  define  the  same   name  without  confusion Python  namespaces {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 18. Namespaces  are  created  (and  deleted)   at  different  moments  and  have   different  lifetimes Python  namespaces {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 19. The  built-­‐‑ins  namespace  is  created   when  the  Python  interpreter  starts And  is  never  deleted Python  namespaces  lifetimes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 20. A  module  global  namespace  is   created  when  the  module  definition  is   read-­‐‑in  (when  it  is  imported) Normally  it  lasts  until  the  interpreter  quits Python  namespaces  lifetimes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 21. A  function  local  namespace  is  created   each  time  it  is  called It  is  deleted  when  the  function  returns  or  raises Python  namespaces  lifetimes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 22. And  what  about  Python  scopes? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 23. A  scope  is  a  textual  region   of  a  program  where  a   namespace  is  directly   accessible {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 24. Scopes  are  determined  statically but  used  dynamically Python  scopes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 25. At  any  time  during  execution,  there   are  at  least  three  nested  scopes  whose   namespaces  are  directly  accessible Python  scopes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 26. 1.  The  innermost  scope  contains  the  local   names >  2.  3.  The  scopes  of  any  enclosing  functions,   with  non-­‐‑local,  but  also  non-­‐‑global  names The  next-­‐‑to-­‐‑last  scope  contains  the   current  module'ʹs  global  names The  outermost  scope  is  the  namespace   containing  built-­‐‑in  names Python  nested  scopes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 27. Names  are  searched  in  nested  scopes   from  inside  out From  locals  to  built-­‐‑ins Python  nested  scopes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 28. $ python Python 2.7.5 (default, Aug 25 2013, 00:04:04) [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import cache >>> cache.set_key("my_key", 7) >>> cache.get_key("my_key") 7 >>> """ Simple cache implementation """ from collections import OrderedDict CACHE = OrderedDict() MAX_SIZE = 100 def set_key(key, value): "Set a key value, removing oldest key if MAX_SIZE exceeded" CACHE[key] = value if len(CACHE) > MAX_SIZE: CACHE.popitem(last=False) def get_key(key): "Retrieve a key value from the cache, or None if not found" return CACHE.get(key, None) Python  scopes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 29. $ python Python 2.7.5 (default, Aug 25 2013, 00:04:04) [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import cache >>> cache.set_key("my_key", 7) >>> cache.get_key("my_key") 7 >>> """ The  outermost  scope: built-­‐‑in  names The  next-­‐‑to-­‐‑last  scope: Simple cache implementation current  module’s  global  names """ from collections import OrderedDict CACHE = OrderedDict() MAX_SIZE = 100 def set_key(key, value): "Set a key value, removing oldest key if MAX_SIZE exceeded" CACHE[key] = value if len(CACHE) > MAX_SIZE: The  innermost  scope: CACHE.popitem(last=False) current  local  names def get_key(key): "Retrieve a key value from the cache, or None if not found" return CACHE.get(key, None) Python  scopes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 30. def get_power_func(y): print("Creating function to raise to {}".format(y)) def power_func(x): print("Calling to raise {} to power of {}".format(x, y)) x = pow(x, y) return x return power_func >>> raise_to_4 = get_power_func(4) Creating function to raise to 3 >>> x = 3 >>> print(raise_to_4(x)) Calling to raise 3 to power of 4 81 >>> print(x) 3 Another  more  complex  case {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 31. def get_power_func(y): print("Creating function to raise to {}".format(y)) def power_func(x): print("Calling to raise {} to power of {}".format(x, y)) x = pow(x, y) return x return power_func >>> raise_to_4 = get_power_func(4) Creating function to raise to 3 >>> x = 3 >>> print(raise_to_4(x)) Calling to raise 3 to power of 4 81 >>> print(x) 3 Where  is  y  defined? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 32. def get_power_func(y): print("Creating function to raise to {}".format(y)) def power_func(x): print("Calling to raise {} to power of {}".format(x, y)) x = pow(x, y) return x The  innermost  scope:  local  names return power_func Enclosing  function  scope: >>> raise_to_4 = get_power_func(4) non-­‐‑local  non-­‐‑global  names Creating function to raise to 3 >>> x = 3 >>> print(raise_to_4(x)) Calling to raise 3 to power of 4 81 The  next-­‐‑to-­‐‑last  scope: >>> print(x) 3 current  module’s  global  names Nested  scopes {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 33. def get_power_func(y): print("Creating function to raise to {}".format(y)) def power_func(x): print("Calling to raise {} to power of {}".format(x, y)) x = pow(x, y) return x return power_func >>> raise_to_4 = get_power_func(4) Creating function to raise to 3 >>> print(raise_to_4.__globals__) {'x': 3, 'raise_to_4': <function get_power_func.<locals>.power_func at 0x100658488>, 'get_power_func': <function get_power_func at 0x1003b6048>, ...} >>> print(raise_to_4.__closure__) (<cell at 0x10065f048: int object at 0x10023b280>,) There  is  a  closure! {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 34. A  function  closure  is  a   reference  to  each  of  the  non-­‐‑ local  variables  of  the  function {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 35. def get_power_func(y): print("Creating function to raise to {}".format(y)) def power_func(x): print("Calling to raise {} to power of {}".format(x, y)) x = pow(x, y) return x return power_func >>> raise_to_4 = get_power_func(4) Creating function to raise to 3 >>> print(raise_to_4.__globals__) {'x': 3, 'raise_to_4': <function get_power_func.<locals>.power_func at 0x100658488>, 'get_power_func': <function get_power_func at 0x1003b6048>, ...} >>> print(raise_to_4.__closure__) (<cell at 0x10065f048: int object at 0x10023b280>,) So,  where  is  y  defined? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 36. Well,  maybe  you  are  wondering where  are  the  decorators  in  this  talk… {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 37. {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 38. So,  let’s  manually  apply  a  decorator {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 39. def factorial(n): "Return n! (the factorial of n): n! = n * (n-1)!" if n < 2: return 1 return n * factorial(n - 1) >>> start = time.time() >>> factorial(35) 10333147966386144929666651337523200000000 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0007369518280029297 def fibonacci(n): "Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)" if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> start = time.time() >>> fibonacci(35) 9227465 >>> print("Elapsed:", time.time() - start) Elapsed: 6.916048049926758 Let’s  go  back  to  these  functions {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 40. 4 3 2 5 2 1 1 1 0 0 3 2 1 1 0 fibonacci(5)  recursive  calls  graph {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 41. from collections import OrderedDict CACHE = OrderedDict() MAX_SIZE = 100 def set_key(key, value): "Set a key value, removing oldest key if MAX_SIZE exceeded" CACHE[key] = value if len(CACHE) > MAX_SIZE: CACHE.popitem(last=False) def get_key(key): "Retrieve a key value from the cache, or None if not found" return CACHE.get(key, None) >>> set_key("my_key", "the_value”) >>> print(get_key("my_key")) the_value >>> print(get_key("not_found_key")) None Do  you  remember  we  have  a  cache? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 42. {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 43. import cache def fibonacci(n): "Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)" if n < 2: return n fib = cache.get_key(n) if fib is None: fib = fibonacci(n - 1) + fibonacci(n - 2) cache.set_key(n, fib) return fib >>> start = time.time() >>> fibonacci(35) 9227465 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0007810592651367188 >>> start = time.time() >>> fibonacci(100) 354224848179261915075 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0013179779052734375 What  about  this  version? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 44. import cache def fibonacci(n): "Return nth fibonacci number: fib(n) = fib(n-1) + fib(n-2)" if n < 2: return n fib = cache.get_key(n) if fib is None: fib = fibonacci(n - 1) + fibonacci(n - 2) cache.set_key(n, fib) return fib >>> start = time.time() >>> fibonacci(35) 9227465 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0007810592651367188 >>> start = time.time() >>> fibonacci(100) 354224848179261915075 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0013179779052734375 DRY:  Don’t  Repeat  Yourself! {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 45. Pay  a4ention  to  the  magic  trick {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 46. import time import cache def fibonacci(n): # The function remains unchanged if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> real_fibonacci = fibonacci def fibonacci(n): fib = cache.get_key(n) if fib is None: fib = real_fibonacci(n) cache.set_key(n, fib) return fib >>> start = time.time() >>> fibonacci(35) 9227465 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0010080337524414062 Original  function  is  not  modified {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 47. import time import cache def fibonacci(n): # The function remains unchanged if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> real_fibonacci = fibonacci def fibonacci(n): fib = cache.get_key(n) if fib is None: fib = real_fibonacci(n) cache.set_key(n, fib) return fib >>> start = time.time() >>> fibonacci(35) 9227465 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0010080337524414062 Which  function  is  called  here? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 48. import time The  next-­‐‑to-­‐‑last  scope: import cache current  module’s  global  names def fibonacci(n): # The function remains unchanged if n < 2: The  innermost  scope: return n current  local  names return fibonacci(n - 1) + fibonacci(n - 2) >>> real_fibonacci = fibonacci def fibonacci(n): fib = cache.get_key(n) if fib is None: fib = real_fibonacci(n) cache.set_key(n, fib) return fib >>> start = time.time() >>> fibonacci(35) 9227465 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0010080337524414062 Remember  the  scopes… {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 49. And  now  the  trick  in  slow  motion {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 50. def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> print(id(fibonacci)) 4298858568 {        fibonacci:  4298858568 } Global  names 4298858568:  <function  fibonacci  at  0x1003b6048> if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) Objects 1.  Create  original  fibonacci  function {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 51. def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> print(id(fibonacci)) 4298858568 >>> real_fib = fibonacci {        fibonacci:  4298858568,        real_fib:        4298858568, } Global  names 4298858568:  <function  fibonacci  at  0x1003b6048> if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) Objects 2.  Create  alternative  name  pointing   to  the  same  function  object {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 52. def fibonacci(n): { if n < 2:        fibonacci:  4302081696, return n return fibonacci(n - 1) + fibonacci(n - 2)        real_fib:        4298858568, } >>> print(id(fibonacci)) 4298858568 Global  names >>> real_fib = fibonacci 4298858568:  <function  fibonacci  at  0x1003b6048> def fibonacci(n): if n < 2: fib = cache.get_key(n) return n if fib is None: return fibonacci(n - 1) + fibonacci(n - 2) fib = real_fib (n) cache.set_key(n, fib) return fib 4302081696:  <function  fibonacci  at  0x1006c8ea0> fib = cache.get_key(n) >>> print(id(fibonacci)) if fib is None: 4302081696 fib = real_fib (n) cache.set_key(n, fib) >>> print(id(real_fib)) return fib Objects 4298858568 3.  Replace  original  name  with  new  a   function  which  calls  the  alternative  name {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 53. def fibonacci(n): { if n < 2:        fibonacci:  4302081696, return n return fibonacci(n - 1) + fibonacci(n - 2)        real_fib:        4298858568, } >>> print(id(fibonacci)) 4298858568 Global  names >>> real_fib = fibonacci 4298858568:  <function  fibonacci  at  0x1003b6048> def fibonacci(n): if n < 2: fib = cache.get_key(n) return n if fib is None: return fibonacci(n - 1) + fibonacci(n - 2) fib = real_fib (n) cache.set_key(n, fib) return fib 4302081696:  <function  fibonacci  at  0x1006c8ea0> fib = cache.get_key(n) >>> print(id(fibonacci)) if fib is None: 4302081696 fib = real_fib (n) cache.set_key(n, fib) >>> print(id(real_fib)) return fib Objects 4298858568 This  way  we  swap  both  functions {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 54. def fibonacci(n): { if n < 2:        fibonacci:  4302081696, return n return fibonacci(n - 1) + fibonacci(n - 2)        real_fib:        4298858568, } >>> print(id(fibonacci)) 4298858568 Global  names >>> real_fib = fibonacci 4298858568:  <function  fibonacci  at  0x1003b6048> def fibonacci(n): if n < 2: fib = cache.get_key(n) return n if fib is None: return fibonacci(n - 1) + fibonacci(n - 2) fib = real_fib (n) cache.set_key(n, fib) return fib 4302081696:  <function  fibonacci  at  0x1006c8ea0> fib = cache.get_key(n) >>> print(id(fibonacci)) if fib is None: 4302081696 fib = real_fib (n) cache.set_key(n, fib) >>> print(id(real_fib)) return fib Objects 4298858568 But  the  original  function  does  not  know  it {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 55. Let’s  make  this  trick  fully  reusable {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 56. Let’s  make  it  work  with  any*  function {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 57. import cache def memoize_any_function(func_to_memoize): "Return a wrapped version of the function using memoization" def memoized_version_of_func(n): "Wrapper using memoization" res = cache.get_key(n) if res is None: res = func_to_memoize(n) # Call the real function cache.set_key(n, res) return res return memoized_version_of_func def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> fibonacci = memoize_any_function(fibonacci) A  factory  of  memoization  functions {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 58. import cache def memoize_any_function(func_to_memoize): "Return a wrapped version of the function using memoization" def memoized_version_of_func(n): "Wrapper using memoization" res = cache.get_key(n) if res is None: res = func_to_memoize(n) # Call the real function cache.set_key(n, res) return res return memoized_version_of_func def factorial(n): if n < 2: return 1 return n * factorial(n - 1) >>> factorial= memoize_any_function(factorial) A  factory  of  memoization  functions {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 59. import time >>> start = time.time() >>> fibonacci(250) 7896325826131730509282738943634332893686268675876375 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0009610652923583984 >>> start = time.time() >>> factorial(250) 3232856260909107732320814552024368470994843717673780666747942427 1128237475551112094888179153710281994509285073531894329267309317 1280899082279103027907128192167652724018926473321804118626100683 2925365133678939089569935713530175040513178760077247933065402339 0061648255522488194365725860573992226412548329822048491377217766 5064127685880715312897877767295191399084437747870258917297325515 0283241787320658188482062478582659808848825548800000000000000000 000000000000000000000000000000000000000000000 >>> print("Elapsed:", time.time() - start) Elapsed: 0.00249481201171875 It  works  with  any*  function {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 60. And  finally,  at  long  last,  decorators {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 61. import cache def memoize_any_function(func_to_memoize): "Return a wrapped version of the function using memoization" def memoized_version_of_func(n): "Wrapper using memoization" res = cache.get_key(n) if res is None: res = func_to_memoize(n) # Call the real function cache.set_key(n, res) return res return memoized_version_of_func def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> fibonacci = memoize_any_function(fibonacci) Pay  a4ention… {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 62. import cache def memoize_any_function(func_to_memoize): "Return a wrapped version of the function using memoization" def memoized_version_of_func(n): "Wrapper using memoization" res = cache.get_key(n) if res is None: res = func_to_memoize(n) # Call the real function cache.set_key(n, res) return res return memoized_version_of_func @memoize_any_function def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) Did  you  spot  the  difference? {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 63. def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> fibonacci = memoize_any_function(fibonacci) This  is  the  only  thing  the  @  does Calls  a  decorator  providing  the  decorated  function,   then  makes  the  function  name  point  to  the  result @memoize_any_function def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n – 2) Decorators  demystified {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 64. def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> fibonacci = memoize_any_function(fibonacci) This  is  the  only  thing  the  @  does Calls  a  decorator  providing  the  decorated  function,   then  makes  the  function  name  point  to  the  result @memoize_any_function def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n – 2) Decorators  demystified {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}
  • 65. Thanks  for  coming! Slides:  h4p://goo.gl/bIlG9R   Q&A {  “event”:  “PyCon  ES  2013”,  “author”:  “Pablo  Enfedaque”,  “twi4er”:  “pablitoev56”}