2017-09-15 09:05:00 +0000
Python 3, tutorial
This post is a partial summary for the chapter 7 of Fluent Python by Ramalho. More advanced examples and detailed usages are present on the book.
A decorator is a callable that takes another function as argument.
@deco
def target():
# ...
The above is basically the same as
def target():
# ...
target = deco(target)
Actually, a closure is function with an extended scope that encompasses non-global variables referenced in the body of the function but not defined there. It does not matter whether the function is a anonymous or not, what matters is that it can access non-global variables that are defined outside of its body.
def make_averager():
"""
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(15)
12.5
>>> avg(20)
15.0
"""
series = []
def averager(new_value):
series.append(new_value)
return sum(series)/len(series)
return averager
avg
is a closure (for averager
) because it reaches the list series
outside the body of averager
but the series
is not global – actually it is defined as a local variable of the function make_averager
. You can inspect the object through __code__
and __closure__
attributes.
>>> avg.__code__.co_varnames
('new_value',)
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__
(<cell at 0x107a44f78: list object at 0x107a91a48>,)
>>> avg.__closure__[0].cell_contents
[10, 15, 20]
I would just like to leave a usage of the nonlocal
declaration as an example below. The code itself tells us much of what the nonlocal
does.
def make_averager(): # improved in efficiency
count = 0
total = 0
def averager(new_value):
nonlocal count, total # without this, it does not work
count += 1
total += new_value
return total/count
return averager
Let’s start with the simplest.
def deco(func):
def inner():
print('a message from inner of deco')
return inner
@deco
def target():
print('target called')
>>> target()
a message from inner of deco
It is notable that the decorated function target
refers the inner
function, not target
itself, i.e., the decorator deco
replaces the decorated one. Check the below.
>>> target
<function deco.<locals>.inner at 0x10063b598>
A key feature of decorators is that they run right after the decorated function is defined. That is usually at import time, i.e., when a module is loaded by Python.
# registration.py
registry = []
def register(func):
print('registering %s' % func)
registry.append(func)
return func
@register
def f1():
print('hello from f1')
@register
def f2():
print('hi from f2')
def f3():
print('no greeting by f3')
# executor.py
from registration import registry, f1, f2, f3
def main():
print('main function starting ...')
print('registry: ', registry)
f1()
f2()
f3()
if __name__ == '__main__':
main()
Now let’s see what happened when the executor.py
is executed.
$ python3 executor.py
registering <function f1 at 0x7f40174bb668>
registering <function f2 at 0x7f40174bb6e0>
main function starting ...
('registry: ', [<function f1 at 0x7f40174bb668>, <function f2 at 0x7f40174bb6e0>])
hello from f1
hi from f2
no greeting by f3
The registering behavior occurs before the main
function is called. This example shows import time vs. run time in that the decorator runs when the decorated functions are imported, but the decorated functions run as they are explicitly called.
Below is the code for the clock
decorator which measures the elapsed time and kindly prints the result of the decorated function. Also the built-in functools.wraps
decorator is worth learning; go search it =]
import time
import functools
def clock(func):
@functools.wraps(func)
def clocked(*args, **kwargs):
t0 = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - t0
arg_list = []
if args:
arg_list.append(', '.join(repr(arg) for arg in args))
if kwargs:
pairs = ['%s=%r'%(k, w) for k, w in sorted(kwargs.items())]
arg_list.append(', '.join(pairs))
arg_str = ', '.join(arg_list)
print('[%0.8f] %s(%s) -> %r' % (elapsed, func.__name__, arg_str, result))
return result
return clocked
Finally the usage of the clock
decorator comes below.
import time
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
if __name__ == '__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
factorial(6)
$ python3 clock_demo.py
**************************************** Calling snooze(123)
[0.12405610s] snooze(.123) -> None
**************************************** Calling factorial(6)
[0.00000191s] factorial(1) -> 1
[0.00004911s] factorial(2) -> 2
[0.00008488s] factorial(3) -> 6
[0.00013208s] factorial(4) -> 24
[0.00019193s] factorial(5) -> 120
[0.00026107s] factorial(6) -> 720