decorators explained: a powerful tool that should be in your python toolbelt
DESCRIPTION
Learn what Python decorators are, how they are implemented, and when you should turn to them to simplify your solutions.TRANSCRIPT
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