{{
Python  DECORATORS
DEMYSTIFIED
Python  DECORATORS
DEMYSTIFIED
"ʺevent"ʺ:      "ʺEuroPython 2015"ʺ
"ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ
"ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ
"ʺevent"ʺ:      "ʺEuroPython 2015"ʺ
"ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ
"ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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
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!Welcome!
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
And  that’s  why  we  will  talk  about  
Python  namespaces  and  scopes
And  that’s  why  we  will  talk  about  
Python  namespaces  and  scopes
Welcome!Welcome!
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
And  finally  will  manually implement  
and  apply  a  handcrafted  decorator
And  finally  will  manually implement  
and  apply  a  handcrafted  decorator
Welcome!Welcome!
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Let’s  start  implementing  some
useful stuff  for  the  talk
Let’s  start  implementing  some
useful stuff  for  the  talk
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  simple  software  cacheA  simple  software  cache
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Use  functools.lru_cache,  not  this!!!Use  functools.lru_cache,  not  this!!!
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Factorial  and  fibonacci functionsFactorial  and  fibonacci functions
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]
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Pretty  easy,  right?Pretty  easy,  right?
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
However…However…
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
How  are  we  accessing  this  attribute?How  are  we  accessing  this  attribute?
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
How  are  recursive  calls  possible?How  are  recursive  calls  possible?
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]
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  simpler  caseA  simpler  case
>>> 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']
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
The  same  name  defined  several  times?The  same  name  defined  several  times?
>>> 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']
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Let  me  introduce  Python  namespacesLet  me  introduce  Python  namespaces
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  namespace  is  a  mapping  
from  names  to  objects
A  namespace  is  a  mapping  
from  names  to  objects
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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
> 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 examplesPython  namespaces examples
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
There  is  no  relation  between  names  in  
different  namespaces
Two  modules  or  functions  may  define  the  same  
name  without  confusion
There  is  no  relation  between  names  in  
different  namespaces
Two  modules  or  functions  may  define  the  same  
name  without  confusion
Python  namespacesPython  namespaces
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Namespaces  are  created  (and  deleted)  
at  different  moments  and  have  
different  lifetimes
Namespaces  are  created  (and  deleted)  
at  different  moments  and  have  
different  lifetimes
Python  namespacesPython  namespaces
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
The  built-­‐‑ins  namespace is  created  
when  the  Python  interpreter  starts
And  is  never  deleted
The  built-­‐‑ins  namespace is  created  
when  the  Python  interpreter  starts
And  is  never  deleted
Python  namespaces lifetimesPython  namespaces lifetimes
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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
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 lifetimesPython  namespaces lifetimes
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  function  local  namespace is  
created  each  time  it  is  called
It  is  deleted  when  the  function  returns  or  raises
A  function  local  namespace is  
created  each  time  it  is  called
It  is  deleted  when  the  function  returns  or  raises
Python  namespaces lifetimesPython  namespaces lifetimes
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
And  what  about  Python  scopes?And  what  about  Python  scopes?
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  scope is  a  textual  region  
of  a  program  where  a  
namespace  is  directly  
accessible
A  scope is  a  textual  region  
of  a  program  where  a  
namespace  is  directly  
accessible
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Scopes  are  determined  statically
but  used  dynamically
Scopes  are  determined  statically
but  used  dynamically
Python  scopesPython  scopes
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
At  any  time  during  execution,  there  
are  at  least  three  nested  scopes whose  
namespaces  are  directly  accessible
At  any  time  during  execution,  there  
are  at  least  three  nested  scopes whose  
namespaces  are  directly  accessible
Python  scopesPython  scopes
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
1. The  innermost  scope  contains  the  local  
names
> The  scopes  of  any  enclosing  functions,  
with  non-­‐‑local,  but  also  non-­‐‑global  names
2. The  next-­‐‑to-­‐‑last  scope  contains  the  
current  module'ʹs  global  names
3. The  outermost  scope  is  the  namespace  
containing  built-­‐‑in  names
1. The  innermost  scope  contains  the  local  
names
> The  scopes  of  any  enclosing  functions,  
with  non-­‐‑local,  but  also  non-­‐‑global  names
2. The  next-­‐‑to-­‐‑last  scope  contains  the  
current  module'ʹs  global  names
3. The  outermost  scope  is  the  namespace  
containing  built-­‐‑in  names
Python  nested scopesPython  nested scopes
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Names  are  searched  in  nested  scopes  
from  inside  out
From  locals  to  built-­‐‑ins
Read-­‐‑only  access  except  for  globals
Names  are  searched  in  nested  scopes  
from  inside  out
From  locals  to  built-­‐‑ins
Read-­‐‑only  access  except  for  globals
Python  nested  scopesPython  nested  scopes
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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
>>>
Python  scopesPython  scopes
"""
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)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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
>>>
Python  scopesPython  scopes
"""
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)
The  outermost  scope:
built-­‐‑in  names
The  next-­‐‑to-­‐‑last  scope:
current  module’s  global  names
The  innermost  scope:
current  local  names
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Another  more  complex  caseAnother  more  complex  case
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 4
>>> x = 3
>>> print(raise_to_4(x))
Calling to raise 3 to power of 4
81
>>> print(x)
3
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Where  is  y defined?Where  is  y defined?
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 4
>>> x = 3
>>> print(raise_to_4(x))
Calling to raise 3 to power of 4
81
>>> print(x)
3
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 4
>>> x = 3
>>> print(raise_to_4(x))
Calling to raise 3 to power of 4
81
>>> print(x)
3
Nested  scopesNested  scopes
The  next-­‐‑to-­‐‑last  scope:
current  module’s  global  names
Enclosing  function  scope:
non-­‐‑local  non-­‐‑global  names
The  innermost  scope:  local  names
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 4
>>> 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!There  is  a  closure!
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  function  closure is  a  
reference  to  each  of  the  non-­‐‑
local  variables  of  the  function
A  function  closure is  a  
reference  to  each  of  the  non-­‐‑
local  variables  of  the  function
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 4
>>> 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?So,  where  is  y defined?
The  innermost  scope:  local  names
Enclosing  function  scope
The  next-­‐‑to-­‐‑last  scope:
current  module’s  global  names
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Maybe  you  are  wondering
where  are  the decorators in  this  talk…
Maybe  you  are  wondering
where  are  the decorators in  this  talk…
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
So,  let’s  manually apply  a  decoratorSo,  let’s  manually apply  a  decorator
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Let’s  go  back  to  these  functionsLet’s  go  back  to  these  functions
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
fibonacci(5)  recursive  calls  graphfibonacci(5)  recursive  calls  graph
5
4 3
1
2 1
02 1
0
3 2 1
01
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Do  you  remember  we  have  a  cache?Do  you  remember  we  have  a  cache?
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
What  about  this  version?What  about  this  version?
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
DRY:  Don’t  Repeat  Yourself!DRY:  Don’t  Repeat  Yourself!
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Pay  attention  to  the  magic  trickPay  attention  to  the  magic  trick
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Original  function  is  not  modifiedOriginal  function  is  not  modified
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Which  function is  called  here?Which  function is  called  here?
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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
Remember  the  scopes…Remember  the  scopes…
The  next-­‐‑to-­‐‑last  scope:
current  module’s  global  names
The  innermost  scope:
current  local  names
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
And  now  the  trick  in  slow  motionAnd  now  the  trick  in  slow  motion
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
1.  Create  original  fibonacci function1.  Create  original  fibonacci function
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
2.  Create  alternative  name  pointing  
to  the  same  function  object
2.  Create  alternative  name  pointing  
to  the  same  function  object
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
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
3.  Replace  original  name  with  new  a  
function  which  calls  the  alternative  name
3.  Replace  original  name  with  new  a  
function  which  calls  the  alternative  name
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
>>> print(id(fibonacci))
4298858568
>>> real_fib = fibonacci
def fibonacci(n):
fib = cache.get_key(n)
if fib is None:
fib = real_fib (n)
cache.set_key(n, fib)
return fib
>>> print(id(fibonacci))
4302081696
>>> print(id(real_fib))
4298858568
{
fibonacci:  4302081696,
real_fib:        4298858568,
}
Global  names
4298858568:  <function  fibonacci at  0x1003b6048>
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
4302081696:  <function  fibonacci at  0x1006c8ea0>
fib = cache.get_key(n)
if fib is None:
fib = real_fib (n)
cache.set_key(n, fib)
return fib Objects
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
This  way  we  swap  both  functionsThis  way  we  swap  both  functions
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
>>> print(id(fibonacci))
4298858568
>>> real_fib = fibonacci
def fibonacci(n):
fib = cache.get_key(n)
if fib is None:
fib = real_fib (n)
cache.set_key(n, fib)
return fib
>>> print(id(fibonacci))
4302081696
>>> print(id(real_fib))
4298858568
{
fibonacci:  4302081696,
real_fib:        4298858568,
}
Global  names
4298858568:  <function  fibonacci at  0x1003b6048>
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
4302081696:  <function  fibonacci at  0x1006c8ea0>
fib = cache.get_key(n)
if fib is None:
fib = real_fib (n)
cache.set_key(n, fib)
return fib Objects
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
But  the  original  function  does  not  know  itBut  the  original  function  does  not  know  it
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
>>> print(id(fibonacci))
4298858568
>>> real_fib = fibonacci
def fibonacci(n):
fib = cache.get_key(n)
if fib is None:
fib = real_fib (n)
cache.set_key(n, fib)
return fib
>>> print(id(fibonacci))
4302081696
>>> print(id(real_fib))
4298858568
{
fibonacci:  4302081696,
real_fib:        4298858568,
}
Global  names
4298858568:  <function  fibonacci at  0x1003b6048>
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
4302081696:  <function  fibonacci at  0x1006c8ea0>
fib = cache.get_key(n)
if fib is None:
fib = real_fib (n)
cache.set_key(n, fib)
return fib Objects
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Let’s  make  this  trick  fully  reusableLet’s  make  this  trick  fully  reusable
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Let’s  make  it  work  with  any*  functionLet’s  make  it  work  with  any*  function
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  factory  of  memoization functionsA  factory  of  memoization functions
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)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
A  factory  of  memoization functionsA  factory  of  memoization functions
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)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
It  works  with  any*  functionIt  works  with  any*  function
import time
>>> start = time.time()
>>> fibonacci(250)
7896325826131730509282738943634332893686268675876375
>>> print("Elapsed:", time.time() - start)
Elapsed: 0.0009610652923583984
>>> start = time.time()
>>> factorial(250)
32328562609091077323208145520243684709948437176737806667479424271
12823747555111209488817915371028199450928507353189432926730931712
80899082279103027907128192167652724018926473321804118626100683292
53651336789390895699357135301750405131787600772479330654023390061
64825552248819436572586057399222641254832982204849137721776650641
27685880715312897877767295191399084437747870258917297325515028324
17873206581884820624785826598088488255488000000000000000000000000
00000000000000000000000000000000000000
>>> print("Elapsed:", time.time() - start)
Elapsed: 0.00249481201171875
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
And  finally,  at  long  last,  decoratorsAnd  finally,  at  long  last,  decorators
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Pay  attention…Pay  attention…
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)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Did  you  spot  the  difference?Did  you  spot  the  difference?
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)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Did  you  spot  the  difference?Did  you  spot  the  difference?
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)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
This  is  the  only  thing  the  @  does
Calls  a  decorator  providing  the  decorated  
function,  then  makes  the  function  name  point  to  
the  result
This  is  the  only  thing  the  @  does
Calls  a  decorator  providing  the  decorated  
function,  then  makes  the  function  name  point  to  
the  result
Decorators  demystifiedDecorators  demystified
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
>>> fibonacci = memoize_any_function(fibonacci)
@memoize_any_function
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n – 2)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
This  is  the  only  thing  the  @  does
Calls  a  decorator  providing  the  decorated  
function,  then  makes  the  function  name  point  to  
the  result
This  is  the  only  thing  the  @  does
Calls  a  decorator  providing  the  decorated  
function,  then  makes  the  function  name  point  to  
the  result
Decorators  demystifiedDecorators  demystified
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
>>> fibonacci = memoize_any_function(fibonacci)
@memoize_any_function
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n – 2)
{  "ʺevent"ʺ:  "ʺEuroPython 2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
Q&AQ&A
Thanks  for  coming!
Slides:  https://goo.gl/fMP4jH
Thanks  for  coming!
Slides:  https://goo.gl/fMP4jH

EuroPython 2015 - Decorators demystified

  • 1.
    {{ Python  DECORATORS DEMYSTIFIED Python  DECORATORS DEMYSTIFIED "ʺevent"ʺ:     "ʺEuroPython 2015"ʺ "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ "ʺevent"ʺ:      "ʺEuroPython 2015"ʺ "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ
  • 2.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 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!Welcome!
  • 3.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } And  that’s  why  we  will  talk  about   Python  namespaces  and  scopes And  that’s  why  we  will  talk  about   Python  namespaces  and  scopes Welcome!Welcome!
  • 4.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } And  finally  will  manually implement   and  apply  a  handcrafted  decorator And  finally  will  manually implement   and  apply  a  handcrafted  decorator Welcome!Welcome!
  • 5.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Let’s  start  implementing  some useful stuff  for  the  talk Let’s  start  implementing  some useful stuff  for  the  talk
  • 6.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  simple  software  cacheA  simple  software  cache 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
  • 7.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Use  functools.lru_cache,  not  this!!!Use  functools.lru_cache,  not  this!!! 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
  • 8.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Factorial  and  fibonacci functionsFactorial  and  fibonacci functions 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]
  • 9.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Pretty  easy,  right?Pretty  easy,  right?
  • 10.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } However…However…
  • 11.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } How  are  we  accessing  this  attribute?How  are  we  accessing  this  attribute? 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
  • 12.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } How  are  recursive  calls  possible?How  are  recursive  calls  possible? 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]
  • 13.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  simpler  caseA  simpler  case >>> 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']
  • 14.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } The  same  name  defined  several  times?The  same  name  defined  several  times? >>> 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']
  • 15.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Let  me  introduce  Python  namespacesLet  me  introduce  Python  namespaces
  • 16.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  namespace  is  a  mapping   from  names  to  objects A  namespace  is  a  mapping   from  names  to  objects
  • 17.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 > 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 examplesPython  namespaces examples
  • 18.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } There  is  no  relation  between  names  in   different  namespaces Two  modules  or  functions  may  define  the  same   name  without  confusion There  is  no  relation  between  names  in   different  namespaces Two  modules  or  functions  may  define  the  same   name  without  confusion Python  namespacesPython  namespaces
  • 19.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Namespaces  are  created  (and  deleted)   at  different  moments  and  have   different  lifetimes Namespaces  are  created  (and  deleted)   at  different  moments  and  have   different  lifetimes Python  namespacesPython  namespaces
  • 20.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } The  built-­‐‑ins  namespace is  created   when  the  Python  interpreter  starts And  is  never  deleted The  built-­‐‑ins  namespace is  created   when  the  Python  interpreter  starts And  is  never  deleted Python  namespaces lifetimesPython  namespaces lifetimes
  • 21.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 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 lifetimesPython  namespaces lifetimes
  • 22.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  function  local  namespace is   created  each  time  it  is  called It  is  deleted  when  the  function  returns  or  raises A  function  local  namespace is   created  each  time  it  is  called It  is  deleted  when  the  function  returns  or  raises Python  namespaces lifetimesPython  namespaces lifetimes
  • 23.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } And  what  about  Python  scopes?And  what  about  Python  scopes?
  • 24.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  scope is  a  textual  region   of  a  program  where  a   namespace  is  directly   accessible A  scope is  a  textual  region   of  a  program  where  a   namespace  is  directly   accessible
  • 25.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Scopes  are  determined  statically but  used  dynamically Scopes  are  determined  statically but  used  dynamically Python  scopesPython  scopes
  • 26.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } At  any  time  during  execution,  there   are  at  least  three  nested  scopes whose   namespaces  are  directly  accessible At  any  time  during  execution,  there   are  at  least  three  nested  scopes whose   namespaces  are  directly  accessible Python  scopesPython  scopes
  • 27.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } 1. The  innermost  scope  contains  the  local   names > The  scopes  of  any  enclosing  functions,   with  non-­‐‑local,  but  also  non-­‐‑global  names 2. The  next-­‐‑to-­‐‑last  scope  contains  the   current  module'ʹs  global  names 3. The  outermost  scope  is  the  namespace   containing  built-­‐‑in  names 1. The  innermost  scope  contains  the  local   names > The  scopes  of  any  enclosing  functions,   with  non-­‐‑local,  but  also  non-­‐‑global  names 2. The  next-­‐‑to-­‐‑last  scope  contains  the   current  module'ʹs  global  names 3. The  outermost  scope  is  the  namespace   containing  built-­‐‑in  names Python  nested scopesPython  nested scopes
  • 28.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Names  are  searched  in  nested  scopes   from  inside  out From  locals  to  built-­‐‑ins Read-­‐‑only  access  except  for  globals Names  are  searched  in  nested  scopes   from  inside  out From  locals  to  built-­‐‑ins Read-­‐‑only  access  except  for  globals Python  nested  scopesPython  nested  scopes
  • 29.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 >>> Python  scopesPython  scopes """ 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)
  • 30.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 >>> Python  scopesPython  scopes """ 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) The  outermost  scope: built-­‐‑in  names The  next-­‐‑to-­‐‑last  scope: current  module’s  global  names The  innermost  scope: current  local  names
  • 31.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Another  more  complex  caseAnother  more  complex  case 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 4 >>> x = 3 >>> print(raise_to_4(x)) Calling to raise 3 to power of 4 81 >>> print(x) 3
  • 32.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Where  is  y defined?Where  is  y defined? 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 4 >>> x = 3 >>> print(raise_to_4(x)) Calling to raise 3 to power of 4 81 >>> print(x) 3
  • 33.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 4 >>> x = 3 >>> print(raise_to_4(x)) Calling to raise 3 to power of 4 81 >>> print(x) 3 Nested  scopesNested  scopes The  next-­‐‑to-­‐‑last  scope: current  module’s  global  names Enclosing  function  scope: non-­‐‑local  non-­‐‑global  names The  innermost  scope:  local  names
  • 34.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 4 >>> 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!There  is  a  closure!
  • 35.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  function  closure is  a   reference  to  each  of  the  non-­‐‑ local  variables  of  the  function A  function  closure is  a   reference  to  each  of  the  non-­‐‑ local  variables  of  the  function
  • 36.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 4 >>> 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?So,  where  is  y defined? The  innermost  scope:  local  names Enclosing  function  scope The  next-­‐‑to-­‐‑last  scope: current  module’s  global  names
  • 37.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
  • 38.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Maybe  you  are  wondering where  are  the decorators in  this  talk… Maybe  you  are  wondering where  are  the decorators in  this  talk…
  • 39.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } So,  let’s  manually apply  a  decoratorSo,  let’s  manually apply  a  decorator
  • 40.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Let’s  go  back  to  these  functionsLet’s  go  back  to  these  functions 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
  • 41.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } fibonacci(5)  recursive  calls  graphfibonacci(5)  recursive  calls  graph 5 4 3 1 2 1 02 1 0 3 2 1 01
  • 42.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Do  you  remember  we  have  a  cache?Do  you  remember  we  have  a  cache? 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
  • 43.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
  • 44.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } What  about  this  version?What  about  this  version? 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
  • 45.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } DRY:  Don’t  Repeat  Yourself!DRY:  Don’t  Repeat  Yourself! 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
  • 46.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Pay  attention  to  the  magic  trickPay  attention  to  the  magic  trick
  • 47.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Original  function  is  not  modifiedOriginal  function  is  not  modified 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
  • 48.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Which  function is  called  here?Which  function is  called  here? 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
  • 49.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@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 Remember  the  scopes…Remember  the  scopes… The  next-­‐‑to-­‐‑last  scope: current  module’s  global  names The  innermost  scope: current  local  names
  • 50.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } And  now  the  trick  in  slow  motionAnd  now  the  trick  in  slow  motion
  • 51.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } 1.  Create  original  fibonacci function1.  Create  original  fibonacci function 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
  • 52.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } 2.  Create  alternative  name  pointing   to  the  same  function  object 2.  Create  alternative  name  pointing   to  the  same  function  object 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
  • 53.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } 3.  Replace  original  name  with  new  a   function  which  calls  the  alternative  name 3.  Replace  original  name  with  new  a   function  which  calls  the  alternative  name def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> print(id(fibonacci)) 4298858568 >>> real_fib = fibonacci def fibonacci(n): fib = cache.get_key(n) if fib is None: fib = real_fib (n) cache.set_key(n, fib) return fib >>> print(id(fibonacci)) 4302081696 >>> print(id(real_fib)) 4298858568 { fibonacci:  4302081696, real_fib:        4298858568, } Global  names 4298858568:  <function  fibonacci at  0x1003b6048> if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) 4302081696:  <function  fibonacci at  0x1006c8ea0> fib = cache.get_key(n) if fib is None: fib = real_fib (n) cache.set_key(n, fib) return fib Objects
  • 54.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } This  way  we  swap  both  functionsThis  way  we  swap  both  functions def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> print(id(fibonacci)) 4298858568 >>> real_fib = fibonacci def fibonacci(n): fib = cache.get_key(n) if fib is None: fib = real_fib (n) cache.set_key(n, fib) return fib >>> print(id(fibonacci)) 4302081696 >>> print(id(real_fib)) 4298858568 { fibonacci:  4302081696, real_fib:        4298858568, } Global  names 4298858568:  <function  fibonacci at  0x1003b6048> if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) 4302081696:  <function  fibonacci at  0x1006c8ea0> fib = cache.get_key(n) if fib is None: fib = real_fib (n) cache.set_key(n, fib) return fib Objects
  • 55.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } But  the  original  function  does  not  know  itBut  the  original  function  does  not  know  it def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> print(id(fibonacci)) 4298858568 >>> real_fib = fibonacci def fibonacci(n): fib = cache.get_key(n) if fib is None: fib = real_fib (n) cache.set_key(n, fib) return fib >>> print(id(fibonacci)) 4302081696 >>> print(id(real_fib)) 4298858568 { fibonacci:  4302081696, real_fib:        4298858568, } Global  names 4298858568:  <function  fibonacci at  0x1003b6048> if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) 4302081696:  <function  fibonacci at  0x1006c8ea0> fib = cache.get_key(n) if fib is None: fib = real_fib (n) cache.set_key(n, fib) return fib Objects
  • 56.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  }
  • 57.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Let’s  make  this  trick  fully  reusableLet’s  make  this  trick  fully  reusable
  • 58.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Let’s  make  it  work  with  any*  functionLet’s  make  it  work  with  any*  function
  • 59.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  factory  of  memoization functionsA  factory  of  memoization functions 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)
  • 60.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } A  factory  of  memoization functionsA  factory  of  memoization functions 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)
  • 61.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } It  works  with  any*  functionIt  works  with  any*  function import time >>> start = time.time() >>> fibonacci(250) 7896325826131730509282738943634332893686268675876375 >>> print("Elapsed:", time.time() - start) Elapsed: 0.0009610652923583984 >>> start = time.time() >>> factorial(250) 32328562609091077323208145520243684709948437176737806667479424271 12823747555111209488817915371028199450928507353189432926730931712 80899082279103027907128192167652724018926473321804118626100683292 53651336789390895699357135301750405131787600772479330654023390061 64825552248819436572586057399222641254832982204849137721776650641 27685880715312897877767295191399084437747870258917297325515028324 17873206581884820624785826598088488255488000000000000000000000000 00000000000000000000000000000000000000 >>> print("Elapsed:", time.time() - start) Elapsed: 0.00249481201171875
  • 62.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } And  finally,  at  long  last,  decoratorsAnd  finally,  at  long  last,  decorators
  • 63.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Pay  attention…Pay  attention… 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)
  • 64.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Did  you  spot  the  difference?Did  you  spot  the  difference? 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)
  • 65.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Did  you  spot  the  difference?Did  you  spot  the  difference? 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)
  • 66.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } This  is  the  only  thing  the  @  does Calls  a  decorator  providing  the  decorated   function,  then  makes  the  function  name  point  to   the  result This  is  the  only  thing  the  @  does Calls  a  decorator  providing  the  decorated   function,  then  makes  the  function  name  point  to   the  result Decorators  demystifiedDecorators  demystified def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> fibonacci = memoize_any_function(fibonacci) @memoize_any_function def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n – 2)
  • 67.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } This  is  the  only  thing  the  @  does Calls  a  decorator  providing  the  decorated   function,  then  makes  the  function  name  point  to   the  result This  is  the  only  thing  the  @  does Calls  a  decorator  providing  the  decorated   function,  then  makes  the  function  name  point  to   the  result Decorators  demystifiedDecorators  demystified def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) >>> fibonacci = memoize_any_function(fibonacci) @memoize_any_function def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n – 2)
  • 68.
    {  "ʺevent"ʺ:  "ʺEuroPython2015"ʺ,  "ʺauthor"ʺ:  "ʺPablo  Enfedaque"ʺ,  "ʺtwitter"ʺ:  "ʺ@pablitoev56"ʺ  } Q&AQ&A Thanks  for  coming! Slides:  https://goo.gl/fMP4jH Thanks  for  coming! Slides:  https://goo.gl/fMP4jH