Closures and Decorators
Austin Bingham Robert Smallshire
@austin_bingham @robsmallshire
[email protected] [email protected] Presenter
def
define new functions, executed at runtime
Local functions
def func():
x = 1
y = 2
return x + y
Local functions
def func(): def func():
x = 1 def local_func():
y = 2 a = 'hello, '
return x + y b = 'world'
return a + b
!
x = 1
y = 2
return x + y
Local functions
def func(): def func():
x = 1 def local_func():
y = 2 a = 'hello, '
return x + y b = 'world'
return a + b
!
x = 1
y = 2
return x + y
LEGB rule
local, enclosing, global, built-in
LEGB Rule
PI = TAU / 2
!
def func(x):
def local_func(n):
a = 'hello, '
return a + n
!
y = 2
return x + y
module.py
LEGB Rule
PI = TAU / 2
!
def func(x):
def local_func(n):
a = 'hello, '
return a + n
!
y = 2
return x + y
module.py
LEGB Rule
PI = TAU / 2
!
def func(x):
def local_func(n):
a = 'hello, '
return a + n
!
y = 2
return x + y
module.py
LEGB Rule
PI = TAU / 2
!
def func(x):
def local_func(n):
a = 'hello, '
return a + n
!
y = 2
return x + y
module.py
Local functions
•Useful for specialized, one-off functions
•Aid in code organization and readability
•Similar to lambdas, but more general
• May contain multiple expressions
• May contain statements
Returning functions
def outer():
def inner():
print('inner')
inner()
Returning functions
def outer():
def inner():
print('inner')
return inner
!
i = outer()
i()
First-class
Functions
functions can be treated like any other object
Closures
def outer():
x = 3
!
def inner(y):
return x + y
!
return inner
!
i = outer()
Closures
def outer():
x = 3
!
def inner(y):
return x + y
!
return inner
!
i = outer()
Closures
def outer():
x = 3
!
def inner(y):
return x + y
!
return inner
!
i = outer()
Closures
!
x = 3
?
def inner(y):
return x + y
!
!
i = outer()
Closures
maintain references to objects from earlier scopes
Function
factory
function that returns new, specialized functions
!
LEGB does not apply when
making new bindings.
global
introduce names from global namespace
into the local namespace
nonlocal
introduce names from the enclosing namespace
into the local namespace
E r r o r
y n t a x
t a S s n ’ t
ge d o e
You e name
f th
i t.
ex is
decorators
modify or enhance functions without changing their definition
@my_decorator
def my_function():
. . .
Decorators
@my_decorator!
def my_function(x, y):!
return x + y
Decorators
@my_decorator!
def my_function(x, y):! FUNCTION
OBJECT
return x + y
Decorators
def my_decorator(f):!
y):! FUNCTION . . .!
OBJECT
return new_f
Decorators
def my_decorator(f):!
FUNCTION FUNCTION
. . .!
OBJECT OBJECT
return new_f
Decorators
@my_decorator!
FUNCTION
def my_function(x, y):!
OBJECT
return x + y
Decorators
def my_function(x, y):!
!
! FUNCTION
! OBJECT
!
Decorators
•Replace, enhance, or modify existing functions
•Does not change the original function definition
•Calling code does not need to change
•Decorator mechanism uses the modified
function’s original name
Decorators
def vegetable():
return 'blomkål'
!
def animal():
return 'bjørn'
!
def mineral():
return 'stål'
Decorators
def vegetable():
return ascii(‘blomkål’)
!
def animal():
return ascii(‘bjørn’)
!
def mineral():
return ascii(‘stål’)
Decorators
def vegetable():
return ascii(‘blomkål’)
!
def animal():
return ascii(‘bjørn’)
!
def mineral():
return ascii(‘stål’)
Not
very
le. main
alab tain
y sc able.
ver
Not
Decorators
We’ve seen functions as
decorators…
!
…but other objects can be
decorators as well.
Classes as decorators
class MyDec:!
def __init__(self, f):!
@MyDec!
! . . .!
def func():!
!
. . .
def __call__(self):!
! . . .
Classes as decorators
Classes are
callable…
class MyDec:!
def __init__(self, f):!
@MyDec!
! . . .!
def func():!
!
. . .
def __call__(self):!
! . . .
Classes as decorators
Classes are …so they can be
callable… used as decorators.
class MyDec:!
def __init__(self, f):!
@MyDec!
! . . .!
def func():!
!
. . .
def __call__(self):!
! . . .
Classes as decorators
Applying a class
decorator creates a
new instance…
class MyDec:!
def __init__(self, f):!
@MyDec!
! . . .!
def func():!
!
. . .
def __call__(self):!
! . . .
Classes as decorators
Applying a class
decorator creates a
new instance…
class MyDec:!
def __init__(self, f):!
@MyDec!
! . . .!
def func():!
!
. . .
def __call__(self):!
! . . .
…so the instance
must be callable.
Instances as decorators
Decorating with
an instance calls
the instance.
class AnotherDec:!
def __call__(self, f):! @AnotherDec()!
! def wrap():! def func():!
! . . .! . . .
! return wrap
Instances as decorators
Decorating
with an
instance calls
the instance.
class AnotherDec:!
def __call__(self, f):! @AnotherDec()!
! def wrap():! def func():!
! . . .! . . .
! return wrap
The return value of
__call__ must be
callable.
Multiple decorators
@decorator1
@decorator2
@decorator3
def some_function():
. . .
Multiple decorators
@decorator1
@decorator2
@decorator3
def some_function():
. . .
Multiple decorators
@decorator1
@decorator2
def some_function():
. . .
!
Multiple decorators
@decorator1
@decorator2
def some_function():
. . .
!
Multiple decorators
@decorator1
def some_function():
. . .
!
!
Multiple decorators
@decorator1
def some_function():
. . .
!
!
Multiple decorators
def some_function():
. . .
!
!
!
functools.wrap()
Naive decorators can lose
important metadata.
functools.wraps()
properly update metadata on wrapped functions
Decorators
•Decorators are a powerful tool
•Decorators are widely used in Python
•It’s possible to overuse decorators; be mindful
•They can improve maintainability, increase
clarity, and reduce complexity
Duck Tails
Factory functions which produce
decorator functions which make
wrapper functions which wrap functions
(with added closures!)
Duck Tails
Whew!
Closures and Decorators
def function(a, b):
print("A function")
def local_function(x, y):
print("A local function")
return x * y + a * b
return local_function
>>> p = function(5, 7)
A function
>>> p
<function function.<locals>.local_function at 0x10299f320>
>>> p.__closure__
(<cell at 0x1029bc8d8: int object at 0x100233120>,
<cell at 0x1029bc980: int object at 0x100233160>)
Closures and Decorators
def log_to(stream):
def logging_decorator( func):
@wraps(func)
def logging_wrapper(*args, **kwargs):
print(func.__name__ + " was called" , file=stream)
return func(*args, **kwargs)
return logging_wrapper
return logging_decorator
@second_decorator @log_to(sys.stderr)
@logging_decorator
def another_function(x):
def some_function(x):
"""A decorated function"""
"""A decorated function"""
return x + x
return x * x
>>> print(some_function.__name__)
some_function
>>> print(some_function.__doc__)
A decorated function