It’s all good- Decorating Python like Martha Stewart¶
by Matt Harrison
Works for http://fusion.io
http://hairysun.com/books/decorators/
- His talk is under creative commons
Impetus¶
You can get by in Python with basic constructs but…
- you might get bored
- be confused by other’s code
- want more power
Function Review¶
A function is an instance ot type function
>>> def spam():
... "A function"
... print 'eggs'
>>> spam
<function 0x2342342>
>>> callable(spam)
True
>>> spam()
'eggs'
Functions have attributes
>>> spam.func_name
'spam'
>>> spam.__doc__
"A function"
A function knows about itself
>>> def foo2():
... print "NAME", foo2.func_name
A function can have attributes assigned:
>>> def foo3():
... print "STUFF", foo3.stuff
>>> foo3.stuff = "Data"
>>> foo3()
Data
Function Definition¶
def func_name(arg1, arg2=value, *args, **kwargs):
""" docstring """
# implementation
Function Gotcha¶
When a function is created, the named/default parameters are defined when the function is created
def named_param(a, foo=[]):
if not poo:
foo.append(a)
print named_param.func_defaults
([])
named_param(1)
print named_param.func_defaults
([1, ])
Lists and dicts are mutable. When you modify them you don’t create a new list (or dict). Strings and ints are immutable
Parameters are evaluated when the def they belong to is imported
Don’t default to mutable types.
def named_param(a, foo=None):
foo = foo or []
if not foo:
foo.append(a)
*args and **kwargs¶
Looksee:
def param_func(a, b=2, c=5):
print [x for x in [a, b, c]]
The ‘*’ before args flattens the tuple of parameters values.
def param_func(a, *args):
print [x for x in [args]]
# TODO check I got this right
def kwargs_func(a, **kwargs):
print [x for x in [kwargs]]
# TODO check I got this right
def param_func(a, b='b', *args, **kwargs):
print [x for x in [a, b, args, kwargs]]
Closures¶
- PEP 227 and came out in Python 2.1
- Don’t be afraid of them
- In Python a function can return a new function. The inner function a closuse and any variable it accesses that are defined outside of that function are free variables.
def add_x(x):
def adder(num):
# we have read acces to x
return x + num # x is a free variable here
return adder
sadd_5 = add_x(5)
add_5 # doctest: + ELLIPSESS
<function add at 0x12324ewe>
print add_5(10)
15
Nested functions only have write access to global and local scope.
x = 3
def outer():
x = 4 # now local
y = 2
def inner():
global x
x = 5 #
print x
inner() # only changes the local inside the function
print x
print outer()
4
4
print x # since global the global value
5
Python 3.x has a non-local keyword that replaces the global in Python 2.x
Decorators¶
PEPS 318, 3129, implemented in Python 2.4
allow you to
- modify arguments
- modify function
- modify results
# count how many times a function is called
call_count = 0
def count(func):
def wrapper(*args, **kwargs):
global call_count
call_count += 1
return func(*args, **kwargs)
return wrapper
def hello():
print 'invoked hello'
>>> hello = count(hello) # invoking count with the argument being the hello object
>>> hello()
>>> print call_count
>>> 1
>>> hello()
>>> print call_count
>>> 2
# Decorator Shortcut
@count
def hello():
return 'hello'
Better decorator:
def count2(func):
# TODO - show this one out
Decorator Template¶
def decorator(function_to_decorate):
def wrapper(*args, **kwargs):
# do something before invoation
result = func_to_decorate(*args, **kwargs)
# do something after
return result
# update wrapper.__doc__ and .func_name
# or functools.wraps
return wrapper
# class as a decorator
class decorator_class(object):
def __init__(self, function):
self.function = function
def __call__(self, *arg, **kwargs):
result = self.function(*arg, **kwargs):
# do stuff to result
return result
@decorator_class
def hello():
return 'hello'
Note
Anything that is callable can be used to create a decorator
# using a class instance as a decorator
# instead of using __call__ use __init__ and then instantiate the class before using it.
deco = Decorator()
@deco
def hello():
return 'hello'
# You can modify deco later! This is UBER powerful!
Note
Not the same as “Class Decorators”. See PEP 3129
Paramaterized decorators¶
- need 2 closures
def limit(length):
def decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
return result[:length]
return wrapper
return decorator
@limit(5) #notice parens
def echo(foo):
return foo
# usage
echo('123456')
'12345'
#syntactical sugar for
echo = limit(5)
Warning: Function attributes get mangled in decorators¶
- I’ve run into this - when you wrap a function a decorator the attributes get lost
- Docstring kills me
- Do this:
def limit(length):
def decorator(function)
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
result = result[:length]
return wrapper
wrapper.__doc__ = function.__doc__
return decorator
You can also use functools to deal with this issue, but it’s not as clear a read
import functools
def limit(length):
def decorator(function)
@functools.wraps(function)
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
result = result[:length]
return wrapper
wrapper.__doc__ = function.__doc__
return decorator
Uses for decorators¶
caching
- I wrote a cache decorator that uses Raymond Hettinger’s LRU cache code.
monkey patching stfio
jsonify
logging time in function call
change cwd
timeout a function call
What if I want to tweak decorator paramers at runtime?¶
What if I made a mistake in a param and want to change values?
Use class instance decorator
Tweak wrapper attributes
Use context manager
or…
- Since a decorator is just a class you can invoke it at runtime. Like this:
# TODO get example
result = limit(4)(echo)