================================= Decorators and Context managers ================================= .. general motivation: Showing two important constructs in Python that .. even if they are just syntactic sugar they help a lot writing .. better and less code. .. The reason why I decided to give this talk is to try to demistify .. decorators and context managers, because they are not really so .. hard and they are widely used patterns in Python. Decorator ========= A **decorator** is a function that takes a *function object* as argument, and returns a function object with an alterated behaviour. Decorators have been introduced in Python 2.4, (see decorator-history_). .. TODO: remove the definition, and just show a nice example first Background ========== .. Before I show what a decorator is in Python, it should be clear to .. everyone that in Python a function is a first class object. .. Once a function func is defined the name 'func' will be bound in .. the current namespace to a object, which type is function. .. We can inspect the function and see for example its arguments, .. or even see the compiled code with func.func_code.co_code. .. TODO: add that this is not like this in Java, but similar to .. function pointers in C - every function in Python is a **first class object** :: def func(arg1, arg2): var1 = 10 *func* is now bound to an **object**, with type :: >>> type(func) >>> function :: >>> func.func_code.co_varnames >>> ('arg1', 'arg2', 'var1') >>> func.func_code.co_code >>> 'd\x01\x00}\x02\x00d\x00\x00S' Shocking example ================ .. And here we see a very simple first example of where a decorator .. might be useful. .. literalinclude:: ../code/deco/deco.py :pyobject: fib :: @memoize def fib_memoized(n): if n <= 1: return 1 return fib_memoized(n-1) + fib_memoized(n-2) +-----+---------+--------------+---------+ | n | fib | fib_memoized | speedup | +-----+---------+--------------+---------+ | 5 | 2.66 μs | 1.16 μs | 2x | +-----+---------+--------------+---------+ | 20 | 3780 μs | 1.21 μs |3000x | +-----+---------+--------------+---------+ Hello decorator =============== .. TODO: should I explain why (*args, **kwargs) is the generic way to call any function? .. this is not what is supposed to do, should be in the right order .. literalinclude:: ../code/deco/deco.py :pyobject: decorator Which is simply syntactic sugar for: :: def my_function(): pass my_function = decorator(my_function) Simple example ============== .. literalinclude:: ../code/deco/deco.py :pyobject: verbose :: >>> def silly_func(): >>> print("Simple function") >>> silly_func = verbose(silly_func) :: Entering function silly_func Simple function Exiting function silly_func Why the _decorator? =================== .. One question which I previously received is why do we actually need the _decorator? .. Why can't I just define it like this: .. Can anyone see what's wrong with this? .. literalinclude:: ../code/deco/deco.py :pyobject: naive_decorator .. The problem is that once we don :: my_function = naive_decorator(my_function) Here the function get **immediately executed!**, returning None Back to memoization =================== *memoize* caches the results of generic function calls. .. literalinclude:: ../code/deco/deco.py :pyobject: memoize .. explain step by step what happened there **Completely generic, memoize any recursive function** Memoization unfolded ==================== :: fib(5) fib(4) + fib(3) (fib(3) + fib(2)) + (fib(2) + fib(1)) ... - *cache* is initially {} - fib(2) should be computed twice, but it's cached after first run Running in a forked process =========================== .. literalinclude:: ../code/deco/deco.py :pyobject: on_forked_process .. TODO: add about .. - chaining decorators .. - decorator nesting Parametric decorator 1 ====================== Here is where things might get hairy, how do I add arguments to a decorator? :: @deco(arg1="value", arg2=100) def function.. *Triple* function .. literalinclude:: ../code/deco/deco.py :pyobject: param_deco Parametric decorator 2 ====================== Or alternatively overriding the __call__ method. .. literalinclude:: ../code/deco/deco.py :pyobject: call_decorator Parametric decorator 3 ====================== .. literalinclude:: ../code/deco/deco.py :pyobject: retry_n_times Class decorator =============== Also a class is an object, and can be also decorator since python > 2.5. .. literalinclude:: ../code/deco/deco.py :pyobject: class_decorator .. code:: @class_decorator class C1: pass Patching classes ================ .. use mock.patch to show how to patch entire classes .. literalinclude:: ../code/deco/patch_class.py Which applies the patch for all the methods found by *inspection*. Context manager =============== .. TODO: Add an example about locks .. main idea is to keep track of the context Introduced in Python 2.5 with the with_statement_. A context manager is useful whenever you can split the actions in: - set up - action - teardown **Very common pattern**: - database connection - perforce connection - temporary environment With statement ============== The idea is to *not forget cleanup actions*. :: with open('file.txt') as source: text = source.read() Is equivalent to: :: source = open('file.txt') text = source.read() source.close() Implement __enter__ and __exit__ ================================ .. literalinclude:: ../code/deco/context.py :pyobject: TempFile Add that there can be an exception handling in the with. .. try: .. except: .. finally: Using contextlib ================ Contextmanager runs the generator until yield, then stops and runs until the end. :: from contextlib import contextmanager @contextmanager def tag(name): print "<%s>" % name yield print "</%s>" % name :: >>> with tag('H1'): >>> print('Title') '<H1>Title</H1>' Thanks ====== .. figure:: ../images/questions.jpg Slides generated with hieroglyph_, and can be found on github_. .. notslides:: .. _decorator-history: http://wiki.python.org/moin/PythonDecorators .. _hieroglyph: https://github.com/nyergler/hieroglyph .. TODO: actually create the repo .. _github: https://github.com/andreacrotti/pyconuk2012_slides .. _with_statement: http://www.python.org/dev/peps/pep-0343/