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
>>> avg.__code__.co_varnames ('new_value',) >>> avg.__code__.co_freevars ('series',) >>> avg.__closure__ (<cell at 0x107a44f78: list object at 0x107a91a48>,) >>> avg.__closure__.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
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