Harrison, Matt - Guide To_ Learning Python Decorators (2013_2012, Hairysun.com)
Harrison, Matt - Guide To_ Learning Python Decorators (2013_2012, Hairysun.com)
Matt Harrison
Copyright © 2012
While every precaution has been taken in the preparation of this book, the
publisher and author assumes no responsibility for errors or omissions, or
for damages resulting from the use of the information contained herein.
Table of Contents
Introduction
Programming Styles
Functions
Function Parameters
Closures
Decorators
About the Author
Also Available
Introduction
Decorators are pretty common in Python. While not strictly necessary, they
can reduce code size while enabling control of function input, invocation
and output. Many explanations of decorators are brief and leave the reader
somewhat confused. This book is an attempt to remedy that. It is based off
of popular tutorials given at PyCon and Python user groups. Without fail,
attendees mention that “decorators now make sense”. Not only do they
understand them, but they can write them as well.
This is a quick yet detailed introduction to decorators. It starts off with an
introduction to many aspects of Python functions, and then proceeds to
closures. With the underlying foundation covered, decorators are discussed
in detail. Some might find this style pendantic, but the during teaching
people have commented that the review is useful and that they learned a
few things along the way.
It is assumed that the reader has some knowledge of Python.
Good luck in your quest to unlock the power of decorators!
– Matt
Programming Styles
Imperative/Procedural
Object Oriented
Declarative/Functional
# some processing
return output
First Class Functions
Unlike some languages, Python supports first-class functions. The idea that
"functions [are] first-class citizens", coined in the 60's by British computer
scientist Christopher Strachey, allows for functions to be passed into other
functions, stored as variables and returned from functions. This is a hard
requirement for functional languages such as Lisp. In Lisp, the function map
takes a function as its first parameter and applies to members of the second
parameter. Python also has a map function to perform an analogous
operation. In contrast, the Java language does not support passing around
functions.
Function Instances
Note that the above code did not invoke the function, it only printed out
the string representation of the function foo.
Using the type function, the foo object can be introspected:
>>> type(foo)
<type 'function'>
Both foo and bar could be passed into other functions as arguments.
They could also be returned as the result of invoking other functions:
This might seem obvious, but it bears mentioning here. Functions are
callable. Because they are callable, they can be invoked. Python provides a
way to introspect whether objects are invokable with the callable
function:
>>> callable(foo)
True
Note
Python 3 removed the callable function. Two alternatives (that also
work in Python 2.6 and above) are:
and:
>>> foo()
invoked foo
Note
On a somewhat related note, if a class implements the __call__ method,
you can invoke instances of the class:
>>> c = CallMe()
>>> c()
Called
This is subtle but this feature allows for using instances as decorators (or
anything that is callable for that matter).
Note that functions also have __call__ methods, that are invoked under
the hood as well.
Functions have attributes
>>> dir(foo)
...
'__doc__',
...
'__name__',
>>> foo.__name__
'foo'
A variable holding a function will also list the function's name when
asked, not the variable name:
>>> bar.__name__
'foo'
>>> foo.__doc__
'docstring for foo'
Note
PEP 232, introduced Function Attributes in Python 2.1. Function
attributes allow getting and setting of arbitrary members to function
instances. This data is stored in the __dict__ dictionary:
>>> foo.__dict__
Tip
Another function attribute is func_defaults. Python stores default
parameters in func_defaults. It is suggested that default parameters use
only non-mutable types. However, it is not uncommon to see Python
beginners use [] as a default parameter. This eventually leads them down a
debugging rabbit hole. To understand why, it is important to understand
when the default parameters are created. Default parameters are initialized
during function definition time, which occurs at either module load time or
during program execution. During module load time, globally defined
functions are created. Because Python has first-class functions, functions
can also be created and returned from inside the body of other function
during runtime. When the function is created, the default parameters are
examined and stored in the func_defaults attribute.
The following is a function that returns positive values from an iterable,
but will also append them to another list, seq, if seq is passed into the
function:
... seq.append(item)
>>> positive.func_defaults
([],)
In this case, the first and only item in func_defaults is an empty list,
which is mutable. On the first invocation of positive, it appears to behave
correctly:
>>> positive([1])
[1]
>>> positive.func_defaults
([1],)
>>> positive([-2])
[1]
>>> positive([5])
[1, 5]
>>> positive.func_defaults
([1, 5],)
The general solution to resolve the need for mutable default parameters
is to shift their creation from module import time to runtime. A common
idiom is to have the parameter default to None then check for that value in
the body of the function:
... seq.append(item)
>>> positive2([1])
[1]
>>> positive2([-2])
[]
>>> positive2([5])
[5]
Note
The function attribute func_defaults becomes __defaults__ in Python
3. Note that unlike the other attributes that have extra dunder spellings in
Python 2, __defaults__ does not exist in Python 2.
Function Scope
Functions are defined in a scope. Within the body of a function, you can
access the function itself, as well as anything that was in scope during the
definition of the function. This enables recursion (a function being able to
call itself), but also allows for reading and setting the attributes of a
function while it is executing:
>>> foo2()
NAME foo2
Any variable instances created within a function but not returned from it
are local to the function and will be garbage collected when the function
exits:
... result = 0
20
>>> print i
Given that the function name is in global scope within the body of that
function, it is possible to attach data to the function while the the function is
executing. It is also possible to attach data to a function outside of the
function, even before it is ever executed:
>>> def foo3():
>>> foo3()
Hello
Note
The built-in functions locals and globals will return a mapping of
mapping of names in their respective namespaces to objects that the names
contain:
... a = 1
... b = 'b'
>>> local_test()
>>> globals()
...
Another feature that falls out of this notion of first-class functions is the
ability to nest functions. In addition to just returning a function as a result of
an invocation, a function can be defined within another function and then
returned:
... return x + y
>>> adder()
>>> adder()(2, 4)
Note
If the above example were in a Python program, the adder function
would have created at module import time. The inner function, add, on the
other hand, does not exist when the module is imported. It is created during
runtime, everytime the adder function is invoked. Thus every invocation of
adder creates a new instance of add as illustrated by thier changing id
below:
>>> adder()
>>> adder()
Nested functions in Python also create the need for nested scope. Any
nested function has read/write access to built-ins and globals. Also nested
functions have read-only access to variables defined the enclosing
functions:
>>> y = 3
... # UnboundLocalError
... global x
... x = 6
... z = 0
... inner()
>>> wrapper()
Inner 6 -2 0
Wrap 6 1 0
Global 6 3
Note
Python 3 introduces the nonlocal keywords which allows for finer
grained modification of non-global variables.
Function Parameters
... return a * b
>>> x = 3
>>> y = 2
In the above code, a and b are parameters because they appear in the
definition of the function. x and y are arguments.
This is a slight distinction in Python, and it is pretty common to see them
used interchangeably. In fact the common naming scheme for variable
parameters is *args and variable keyword parameters is **kwargs.
Normal and Keyword Parameters
... print a, b, c
>>> normal(1, 2, 3)
1 2 3
1 2 3
If the arguments have thier name provided during invocation, the the
order of the parameters is not important:
1 2 3
Variable Parameters
... start = 0
... tup_idx = 0
... if i == -1:
... else:
>>> printf('hello')
hello
My name: Matt
nums: 1, 2, 3
Note
Variable parameters are commonly labeled as *args. Much like self is
the accepted Python naming convention for the first parameter to a method
for an object, *args is the standard naming convention used for variable
parameters. In reality, the Python interpreter does not care what the name
of the parameter following the asterisk is. Because there can only be one
parameter defined as a variable parameter (not including the variable
keyword parameter), and it is common convention to spell the variable
parameter as *args.
>>> demo_args()
<type 'tuple'> ()
>>> demo_args(1)
Note
Variable parameters are commonly combined with variable keyword
parameters. Together they are often seen in the constructors of subclasses.
This allows a subclass to easily accept any of the arguments for a parent
class without enumerating any of them.
Variable parameters and variable keyword parameters are also used for
decorators which are described later in this book.
The * operator
The last two will be discussed later in the following sections. The
previous section discussed variable parameters. Flattening arguments is
next in the list. What if you already have appropriate arguments for a
function sitting around in a sequence? Is it necessary to pull each item out
during function invocation?:
>>> demo_args(*vars)
If the * is left off the vars argument, args would be tuple containing a
single item, the list with the two parameters. This is probably not what was
intended if vars was meant to contain the parameters for a function:
>>> demo_args(vars)
<type 'tuple'> (['John', 'Paul'],)
Tip
When invoking functions declared with variable arguments, make sure
you understand which arguments need to be flattened. (Usually for
decorators and constructors they should all be flattened).
... return a + b + c
15
Note
Only a single sequence may be flattened into a function:
...
demo_args(*vars, *vars)
Note
If a function has normal, keyword and variable parameters, it may be
invoked with just a flattened sequence. In that case the sequence will
populate the normal and keyword parameters, and any left over variables
will be left in the variable argument:
>>> func(*vars)
[3, 4, (5,)]
Again, because the * flattens the arguments, they fill out the parameters.
The above invocation is the same as calling the function with the arguments
listed out:
[3, 4, (5,)]
Variable Keyword Parameters
>>> demo_kwargs()
<type 'dict'> {}
>>> demo_kwargs(one=1)
The **, when used within a function or method definition, indicates that
a function will take any number of keyword arguments. The arguments
arrive in a dictionary containing the names and their corresponding values.
Similar to args, kwargs is the standard convention for a parameter name
used for variable keyword parameters.
Note
Variable keyword parameters require arguments to provide a name:
>>> demo_kwargs(1)
...
demo_kwargs(1)
This error can be a little confusing, since demo_kwargs takes zero normal
parameters, but any number of keyword parameters.
Flattening dictionaries
... (y1-y2)**2) ** .5
>>> distance(**points)
5.0
5.0
>>> demo_kwargs(**points)
'x1': 1, 'y2': 5}
Arbitrary function parameters
normal
keyword
variable
variable keyword
Tip
Functions may only define one variable parameter and one variable
keyword parameter. Also, the order of the parameter definition must follow
the order of the four types of parameters listed above.
Tip
Remember when invoking functions with the splat operator — with either
variable arguments or variable keyword arguments — it is the same as if
those arguments are listed out individually. If variable keyword arguments
keys have the same name as normal or keyword parameters, they can be
used for them. Otherwise, variable arguments would come after the normal
and keyword arguments, and variable keyword arguments would appear
after that with their corresponding names:
>>> args = (0, 1, 2)
Notice that the variable parameters flowed into the normal and keyword
parameters. Again, this invocation is equivalent to:
Closures often have an aura around them that makes them appear
unapproachable. It does not help that the descriptions of them are terse:
[A] closure (also lexical closure, function closure, function value or
functional value) is a function together with a referencing environment
for the non-local variables of that function. A closure allows a function
to access variables outside its typical scope. Such a function is said to
be "closed over" its free variables.
—Wikipedia
A closure in Python is simply a function that is returned by another
function:
>>> add_5
>>> add_5(10)
15
In the above example, the function add_x returns an inner function. The
inner function, adder, is “closed over” — hence a closure. Inside of adder,
the variable x is a free variable because it is non-local to adder and defined
outside of it. A simplified definition of a Python closure might be:
In Python functions can return new functions. The inner function is a
closure and any variable it accesses that are defined outside of that
function are free variables.
Common uses of closures
... results = []
... if result:
... results.append(result)
>>> matt_filter({'name':'Matt'})
{'name': 'Matt'}
True
... if f(row):
... john_filter,
... ringo_filter,
... george_filter])
True
>>> beatle({'name':'John'})
{'name': 'John'}
before invocation
during invocation — the implementation can be changed/replaced
after invocation
Normally the callables that are decorated in Python are either functions
or methods.
A simple decorator
Here is a decorator, verbose, that prints out the name of the function it
decorates before and after execution:
Please make sure you understand what the verbose function does. It
accepts a function, func, and returns a new function, wrapper. When
wrapper is invoked, it will print the name, execute the original wrapped
function, print the name again and return the result of the original function.
This is about as simple as a decorator can get, and the others that follow
will build upon this same pattern.
A decorator is really only useful when applied to a function. There are
two ways to do this. The first is to simply invoke the decorator on a
function:
>>> hello.__name__
'wrapper'
Now, when hello is invoked, it prints a message before and after
executing:
>>> hello()
Before hello
Hello
After hello
>>> @verbose
>>> greet()
Before greet
G'day
After greet
Tip
The PEP 318 style of decorating does not require parentheses following
verbose. It will result in an error if attempted:
>>> @verbose()
...
def howdy()
>>> @verbose
... return x + y
>>> add(2, 3)
...
add(2, 3)
>>> @chatty
... return x * y
>>> mult(2, 4)
Before mult
After mult
8
>>> @chatty
>>> goodbye()
Before goodbye
Later
After goodbye
A decorator template
There are two lines in this template that weren’t present in the previous
examples. In well-behaved decorators the __doc__ and __name__ of the
wrapper function need to be updated with the values from the function that
is being decorated. For pickling (serialization to disk) of objects, it is
required that __name__ is updated. The __doc__ attribute is updated so the
function is friendly to introspection.
Note
The function wraps (which is a decorator itself) found in the functools
module, will update __doc__ and __name__ as well. So another template is:
... @functool.wraps(func_to_decorate)
Note
As was discussed in the closure section, any callable can wrap another
function. So a callable can also serve as a decorator. Here is an example of
a class that can be used to decorate functions:
>>> @decorator_class
... # implementation
Note
The above decorator is not the same as what are known as Class
Decorators in Python. These are discussed in PEP 3129.
Parameterized decorators
>>> @trunc
'fooba'
>>> @limit(3)
>>> data3()
'lim'
>>> @limit(6)
>>> data6()
'limit '
Note
The syntactic sugar for decorating with parameterized decorators is
@limit(3). It is the same as the following:
>>> def data3():
... # before
... # after
>>> @chatty
... @limit(2)
Before wrapper
After wrapper
Gr
function arguments
function being wrapped
results of the function
With that in mind there are common instances where decorators are used.