Decorators Explained
A Powerful Tool That Should Be in Your Python Toolbelt
Samuel Fortier-Galarneau
Software Developer @ Digium, Inc@samuelg
http://github.com/samuelg
Agenda
• Primer
• What is a decorator?
• How do they work?
• Examples
PrimerFunctions
• Functions in Python are first class members
• They can be passed to other functions and return new functions
def add(first, second): return first + second
def partial(func, first): def wrapper(second): return func(first, second) return wrapper
PrimerFunctions
>>> func = partial(add, 1)>>> func(2)3
PrimerVariable function arguments
• Functions can have variable positional and keyword arguments via *args and **kwargs
• *args and **kwargs can be packed again and passed along to another function
def func(*args, **kwargs): print args print kwargs
PrimerVariable function arguments
>>> func(1, 2, my_arg=‘value')(1, 2){'my_arg': 'value'}
PrimerVariable function arguments
def func(*args, **kwargs): other_func(*args, **kwargs)
def other_func(arg1, arg2, arg3): print arg1 print arg2 print arg3
PrimerVariable function arguments
>>> func(1, 2, arg3=3)123
What is a decorator?
• Injects code before/after a function call or object creation
• A callable wrapper around a callable resource (object that implements __call__ is callable)
• Similar to macros in C/C++ but uses built in Python syntax and language semantics
What is a decorator?• Can be implemented as a function or a
class
• Can be applied to a function or a class
• Functions have an implicit __call__ method which makes them callable
• Classes must implement a __call__ method to qualify as a decorator
How do they work?Decorator function
def decorator(func): def wrapper(): print 'in wrapper()' return func() return wrapper
@decoratordef my_function(): print 'in my_function()'
How do they work?Decorator function
>>> my_function()in wrapper()in my_function()
How do they work?Decorator function
def decorator(func): def wrapper(): print 'in wrapper()' return func() return wrapper
@decoratordef my_function(): print 'in my_function()'
How do they work?Decorator function
def decorator(func): def wrapper(): print 'in wrapper()' return func() return wrapper
@decoratordef my_function(): print 'in my_function()'
How do they work?Decorator function
def decorator(func): def wrapper(): print 'in wrapper()' return func() return wrapper
@decoratordef my_function(): print 'in my_function()'
How do they work?Decorator function
def decorator(func): def wrapper(): print 'in wrapper()' return func() return wrapper
@decoratordef my_function(): print 'in my_function()'
How do they work?Decorator function
wrapper is a closure over func
def decorator(func): def wrapper(): print 'in wrapper()' return func() return wrapper
@decoratordef my_function(): print 'in my_function()'
How do they work?Decorator function
def decorator(func): def wrapper(): print 'in wrapper()' return func() return wrapper
@decoratordef my_function(): print 'in my_function()'
How do they work?Decorator class
class Decorator(object): def __init__(self, func): self.func = func def __call__(self): print 'in __call__()' return self.func()
@Decoratordef my_function(): print 'in my_function()'
How do they work?Decorator class
>>> my_function()in __call__()in my_function()
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
arguments
>>> my_function()in wrapper()(1,){'config': 'value'}in my_function()
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator function with
argumentsdef decorator(*args, **kwargs): def receiver(func): def wrapper(): print 'in wrapper()' print args print kwargs return func() return wrapper return receiver
@decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator class with
argumentsclass Decorator(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def __call__(self, func): def wrapper(): print 'in __call__()' print self.args print self.kwargs return func() return wrapper
@Decorator(1, config='value')def my_function(): print 'in my_function()'
How do they work?Decorator class with
arguments
>>> my_function()in __call__()(1,){'config': 'value'}in my_function()
Example: counting function calls
class Counter(object): def __init__(self, func): self.count = 0 self.func = func def __call__(self): self.count += 1 return self.func() def getCount(self): return self.count
@Counterdef function(): pass
Example: counting function calls
>>> function()>>> function()>>> function()>>> function.getCount()3
Example: memoizeclass Memoize(object): def __init__(self, func): self.cache = {} self.func = func def __call__(self, *args, **kwargs): cache_key = '%s%s' % (args, kwargs) if cache_key in self.cache: return self.cache[cache_key] else: result = self.func(*args, **kwargs) self.cache[cache_key] = result return result
@Memoizedef function(bound): sum = 0 for x in range(bound): sum += x return sum
Example: memoize
>>> function(100000000)~ 11 seconds>>> function(100000000)~ 10 milliseconds
Example: type checker
def type_check(*types): def receiver(func): def wrapper(*args, **kwargs): for index, type_arg in enumerate(types): if type(args[index]) != type_arg: raise TypeError('Expected %s at index %s' % (type_arg, index)) return func(*args, **kwargs) return wrapper return receiver
@type_check(int, int, str)def function(first, second, third): print 'success'
Example: type checker
>>> function(1, 2, ‘works')success>>> function('will', 'not', ‘work')Traceback (most recent call last): File "type.py", line 17, in <module> function('will', 'not', 'work') File "type.py", line 7, in wrapper raise TypeError('Expected %s at index %s' % (type_arg, index))TypeError: Expected <type 'int'> at index 0
Example: singleton
instances = {}def singleton(cls): def create(*args): if cls not in instances: instances[cls] = cls(*args) return instances[cls] return create
@singletonclass Toolbelt(object): def __init__(self): pass
Example: singleton
>>> toolbelt = Toolbelt()>>> another = Toolbelt()>>> another == toolbeltTrue
Other uses
• Retries
• Pre/Post conditions
• Function input/output transforms
• Performance logging
• Authorization
Thanks!
Samuel Fortier-Galarneau@samuelg
http://github.com/samuelghttp://www.slideshare.net/sgalarne/decorators-2