In [1]:
def decorator(f):
    return f

In [2]:
@decorator
def holidays():
    print("PyATL Decemeber 2014")

First Class Functions


In [3]:
my_dir = dir
my_funcs = [enumerate, int]

Higher Order Functions


In [4]:
map(float, range(3))

def helper():
    return help

Closures


In [5]:
def outer(x=4):
    def inner(y=0):
        return x+y
    return inner

In [6]:
o = outer()
o()


Out[6]:
4

Decorators

  • Callables that:
    • Take a callable
    • Do something with it
    • Return a callable

Pass Through


In [7]:
def make_awesome(f):
    f.__awesome__ = True
    return f

In [8]:
@make_awesome
def random():
    return 4

Closure Decorators


In [9]:
def loggit(f):
    def logger(*args, **kwargs):
        print("Calling {}.".format(f.__name__))
        return f(*args, **kwargs)
    return logger

In [10]:
@loggit
def random():
    """Chosen by a fair dice roll."""
    return 4

Muh metadatas


In [11]:
from functools import wraps

def loggit(f):
    @wraps(f)
    def logger(*args, **kwargs):
        print("Calling {}.".format(f.__name__))
        return f(*args, **kwargs)
    return logger

In [12]:
@loggit
def random():
    """Chosen by a fair dice roll."""
    return 4

assert random.__name__ == 'random'

Beyond Functions

Objects as Decorators


In [13]:
from functools import update_wrapper

class wrapperobj:
    def __init__(self, f):
        update_wrapper(self, f)
        self.__f = f
    
    def __call__(self, *args, **kwargs):
        return self.__f(*args,**kwargs)

In [14]:
@wrapperobj
def random():
    return 4

Single Instance Wrapping


In [15]:
class singlewrapper:
    def __call__(self, f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs)
        return wrapper

In [16]:
wrapper = singlewrapper()

@wrapper
def random():
    return 4

Decorating Classes


In [17]:
from functools import total_ordering

@total_ordering
class Person:
    def __init__(self, age, name):
        self.age = age
        self.name = name.lower()
    
    def __eq__(self, other):
        return ((self.age, self.name)) == \
            ((other.age, other.name))
    
    def __lt__(self, other):
        return ((self.age, self.name)) < \
            ((other.age, other.name))
    
Person(26, "Alec") > Person(22, "Colin")


Out[17]:
True

Decorating Class Methods


In [18]:
class Foo:
    @loggit
    def bar(self, n=1):
        return ' '.join(['Frob'] * n)

Foo().bar()


Calling bar.
Out[18]:
'Frob'

In [19]:
class Foo:
    @wrapperobj
    def bar(self, n=1):
        return ' '.join(['Frob'] * n)

Foo().bar


Out[19]:
<__main__.wrapperobj at 0x7f89c1eadc18>

Descriptors in One Slide

  • Objects that live on classes
  • __get__ as the "closure"
  • Python handles calling automatically

In [20]:
from functools import partial

class methodwrapper:
    def __init__(self, method):
        update_wrapper(self, method)
        self.__method = method
    
    def __get__(self, instance, type_):
        if instance is None:
            return self.__method
        return partial(self.__method, instance)

In [21]:
class Foo:
    @methodwrapper
    def bar(self, n):
        return ' '.join(['Frob'] * n)

Foo().bar(2)


Out[21]:
'Frob Frob'

Stacking Decorators


In [22]:
@wrapperobj
@loggit
@make_awesome
def random():
    return 4

assert hasattr(random, '__awesome__')

An Aside

  • Bottom up evaluation
  • Use @wraps or update_wrapper
  • Descriptors (@property) go last

A few problems...

Testing

  • Original object hidden
  • Whole mechanism tested

In [23]:
import inspect

orig = inspect.unwrap(random)

assert isinstance(random, wrapperobj)
assert not isinstance(orig, wrapperobj)

Introspection

  • inspect.getargspec
  • inspect.getsource
  • isinstance and type

In [24]:
print("Wrapped ArgSpec:", inspect.getargspec(random))


Wrapped ArgSpec: ArgSpec(args=['self'], varargs='args', keywords='kwargs', defaults=None)

In [25]:
print("Unwrapped ArgSpec:", inspect.getargspec(
    inspect.unwrap(random)))


Unwrapped ArgSpec: ArgSpec(args=[], varargs=None, keywords=None, defaults=None)

In [26]:
print(inspect.getsource(Foo.bar))


    @methodwrapper
    def bar(self, n):
        return ' '.join(['Frob'] * n)


In [27]:
from types import FunctionType

assert isinstance(Foo.bar, FunctionType)

Function calls are slow

  • random: 108ns
  • random called by closure: 460ns
  • random called by object: 580ns

The Truth

  • Don't be scared
  • Simple, Easy, Useful
  • Use a library to ease pain
  • pip install --user wrapt