3. What’s a Decorator?
Decoration is a way to specify management code for functions and classes.
Decorators themselves take the form of callable objects (e.g., functions) that
process other callable objects.
• Function decorators install wrapper objects to intercept later function calls
and process them as needed.
• Class decorators install wrapper objects to intercept later instance creation
calls and process them as required.
Copyright 2009 Trend Micro Inc.
4. Why Decorators?
• Decorators have a very explicit syntax, which makes them easier to spot than
helper function calls that may be arbitrarily far-removed from the subject
functions or classes.
• Decorators are applied once, when the subject function or class is defined;
it’s not necessary to add extra code (which may have to be changed in the
future) at every call to the class or function.
• Because of both of the prior points, decorators make it less likely that a user
of an API will forget to augment a function or class according to API
requirements.
Copyright 2009 Trend Micro Inc.
5. Why Decorators?
def query(): @get('/query')
# parse http request @logging
# logging @authentication
# authenticate user @authorization(―admin‖)
# authorize user permission @cache
# if cache exists, return it. def query():
# get data from DB # get data from DB
Copyright 2009 Trend Micro Inc.
6. Function Decorators
Classification 4/3/2012 Copyright 2009 Trend Micro Inc. 6
7. Usage
In terms of code, function decorators automatically map the following syntax:
@decorator
def F(arg):
...
F(99) # Call function
into this equivalent form, where decorator is a one-argument callable object
that re- turns a callable object with the same number of arguments as F:
def F(arg):
...
F = decorator(F) # Rebind function name to decorator result
F(99) # Essentially calls decorator(F)(99)
Copyright 2009 Trend Micro Inc.
8. Implementation
A decorator itself is a callable that returns a callable.
def decorator(F):
print ‖init decorator"
return F
@decorator
def func():
print "Hello‖
This decorator is invoked at decoration time, and the callable it returns is
invoked when the original function name is later called.
Copyright 2009 Trend Micro Inc.
9. Implementation
the decorator returns a wrapper that retains the original function in an enclosing scope
def decorator(F):
def wrapper(*args):
print ‖run function"
F()
return wrapper
@decorator
def func():
print "Hello"
When the name func is later called, it really invokes the wrapper function
returned by decorator; the wrapper function can then run the original func
because it is still available in an enclosing scope.
Copyright 2009 Trend Micro Inc.
10. Implementation
To do the same with classes, we can overload the call operation and use instance at-
tributes instead of enclosing scopes:
class decorator:
def __init__(self, func): # On @ decoration
self.func = func
print "init decorator‖
def __call__(self, *args): # On wrapped function call
print "run function"
self.func(*args)
@decorator
def func(): # func = decorator(func), then func is passed to __init__
print "Hello‖
func() #call __call__'s *args
Copyright 2009 Trend Micro Inc.
11. Class Decorators
Classification 4/3/2012 Copyright 2009 Trend Micro Inc. 11
12. Usage
@decorator # Decorate
class C:
...
x = C(99) # Make an instance
is equivalent to the following—the class is automatically passed to the
decorator func- tion, and the decorator’s result is assigned back to the class
name:
class C:
...
C = decorator(C) # Rebind class name to decorator result
x = C(99) # Essentially calls decorator(C)(99)
Copyright 2009 Trend Micro Inc.
13. Implementation
A decorator itself is a callable that returns a callable.
def decorator(C):
print ‖init decorator"
return C
@decorator
class C:
def __init__(self, x, y):
self.attr = 'spam’
Copyright 2009 Trend Micro Inc.
14. Implementation
the decorator returns a wrapper that retains the original function in an enclosing scope
def decorator(cls):
class Wrapper:
def __init__(self, *args):
self.wrapped = cls(*args)
def __getattr__(self, name):
return getattr(self.wrapped, name)
return Wrapper
@decorator
class C:
def __init__(self, x, y):
self.attr = 'spam’
It does support multiple instances, because each instance creation call makes a new
independent wrapper object.
Copyright 2009 Trend Micro Inc.
15. Implementation - invalid
class Decorator:
def __init__(self, C): # On @ decoration
self.C = C
def __call__(self, *args): # On instance creation
self.wrapped = self.C(*args)
return self
def __getattr__(self, attrname): # On atrribute fetch
return getattr(self.wrapped, attrname)
@decorator
class C:
….
x = C()
y = C() # Overwrites x!
this version fails to handle multiple instances of a given class—each instance
creation call overwrites the prior saved instance.
Copyright 2009 Trend Micro Inc.
17. Usage
Sometimes one decorator isn’t enough. To support multiple steps of augmentation,
decorator syntax allows you to add multiple layers of wrapper logic to a decorated
function or method. When this feature is used, each decorator must appear on a line of
its own. Decorator syntax of this form:
@A
@B
@C
def f(...):
...
runs the same as the following:
def f(...):
...
f = A(B(C(f)))
Copyright 2009 Trend Micro Inc.
20. Usage
Both function and class decorators can also seem to take arguments, although really
these arguments are passed to a callable that in effect returns the decorator, which in
turn returns a callable. The following, for instance:
@decorator(A, B)
def F(arg):
...
F(99)
is automatically mapped into this equivalent form, where decorator is a callable that
returns the actual decorator. The returned decorator in turn returns the callable run later
for calls to the original function name:
def F(arg):
...
F = decorator(A, B)(F) # Rebind F to result of decorator's return value
F(99) # Essentially calls decorator(A, B)(F)(99)
Copyright 2009 Trend Micro Inc.
23. Decorators Manage Functions and Classes
def decorate(O):
# Save or augment function or class O
return O
@decorator
def F(): ... # F = decorator(F)
@decorator
class C: ... # C = decorator(C)
Copyright 2009 Trend Micro Inc.
24. Logging example
>>> spam(1, 2, 3)
class tracer:
call 1 to spam
def __init__(self, func):
6
self.calls = 0
>>> spam ('a', 'b', 'c')
self.func = func
call 2 to spam
def __call__(self, *args):
abc
self.calls += 1
>>> spam.calls
print('call %s to %s' % (self.calls, self.func.__name__))
2
self.func(*args)
@tracer
def spam(a, b, c):
print(a + b + c)
Copyright 2009 Trend Micro Inc.
25. State Information
Classification 4/3/2012 Copyright 2009 Trend Micro Inc. 25
26. Class instance attributes
>>>spam(1, 2, 3)
class tracer:
def __init__(self, func):
call 1 to spam
self.calls = 0 6
self.func = func >>>spam(a=4, b=5, c=6)
def __call__(self, *args, **kwargs): call 2 to spam
self.calls += 1 15
print('call %s to %s' % (self.calls, self.func.__name__))
>>>eggs(2, 16)
return self.func(*args, **kwargs)
call 1 to eggs
@tracer
65536
def spam(a, b, c): >>>eggs(4, y=4)
print(a + b + c) call 2 to eggs
256
@tracer
def eggs(x, y):
print(x ** y)
Copyright 2009 Trend Micro Inc.
27. Enclosing scopes and nonlocals
>>>spam(1, 2, 3)
def tracer(func):
call 1 to spam
calls = 0
6
def wrapper(*args, **kwargs):
>>>spam(a=4, b=5, c=6)
nonlocal calls
call 2 to spam
calls += 1
15
print('call %s to %s' % (calls, func.__name__))
>>>eggs(2, 16)
return func(*args, **kwargs)
call 1 to eggs
return wrapper
65536
>>>eggs(4, y=4)
call 2 to eggs
256
Copyright 2009 Trend Micro Inc.
28. Enclosing scopes and globals
calls = 0 >>>spam(1, 2, 3)
def tracer(func): call 1 to spam
def wrapper(*args, **kwargs): 6
global calls >>>spam(a=4, b=5, c=6)
calls += 1 call 2 to spam
print('call %s to %s' % (calls, func.__name__)) 15
return func(*args, **kwargs) >>>eggs(2, 16)
return wrapper call 3 to eggs
65536
>>>eggs(4, y=4)
call 4 to eggs
256
Copyright 2009 Trend Micro Inc.
30. Class Blunders I - Decorating Class Methods
class tracer:
def __init__(self, func):
self.calls = 0
self.func = func
def __call__(self, *args, **kwargs):
self.calls += 1
print('call %s to %s' % (self.calls, self.func.__name__))
return self.func(*args, **kwargs)
Copyright 2009 Trend Micro Inc.
31. Class Blunders I - Decorating Class Methods
class Person:
def __init__(self, name, pay):
self.name = name
self.pay = pay
@tracer
def giveRaise(self, percent):
self.pay *= (1.0 + percent)
@tracer
def lastName(self):
return self.name.split()[-1]
bob = Person('Bob Smith', 50000)
bob.giveRaise(.25) ## Runs tracer.__call__(???, .25)
The root of the problem here is in the self argument of the tracer class’s __call__ method,
is it a tracer instance or a Person instance?
Copyright 2009 Trend Micro Inc.
32. Using nested functions to decorate methods
def tracer(func):
calls = 0
def onCall(*args, **kwargs):
nonlocal calls
calls += 1
print('call %s to %s' % (calls, func.__name__))
return func(*args, **kwargs)
return onCall
Copyright 2009 Trend Micro Inc.
33. Singleton Classes
instances = {}
def getInstance(aClass, *args):
if aClass not in instances:
instances[aClass] = aClass(*args)
return instances[aClass]
def singleton(aClass):
def onCall(*args):
return getInstance(aClass, *args)
return onCall
@singleton
class Spam:
def __init__(self, val):
self.attr = val
Copyright 2009 Trend Micro Inc.
34. Why Decorators? (Revisited)
drawbacks:
• Type changes
– As we’ve seen, when wrappers are inserted, a decorated function or class
does not retain its original type—its name is rebound to a wrapper object.
• Extra calls
– A wrapping layer added by decoration incurs the additional performance
cost of an extra call each time the decorated object is invoked
Copyright 2009 Trend Micro Inc.
35. Why Decorators? (Revisited)
benefits:
• Explicit syntax
– Decorators make augmentation explicit and obvious.
• Code maintenance
– Decorators avoid repeated augmentation code at each function or class
call.
• Consistency
– Decorators make it less likely that a programmer will forget to use
required wrapping logic.
Copyright 2009 Trend Micro Inc.
36. Q&A
Classification 4/3/2012 Copyright 2009 Trend Micro Inc. 36
Hinweis der Redaktion
Like onion
Like onion
For example, @Transactional annotation in java
AOPUse decorator to validate argumentsLoggingCaching
Python 3.0
The root of the problem here is in the self argument of the tracer class’s __call__ method—is it a tracer instance or a Person instance? We really need both as it’s coded: the tracer for decorator state, and the Person for routing on to the original method. Really, self must be the tracer object, to provide access to tracer’s state information; this is true whether decorating a simple function or a method. Unfortunately, when our decorated method name is rebound to a class instance object with a __call__, Python passes only the tracer instance to self; it doesn’t pass along the Person subject in the arguments list at all. Moreover, because the tracer knows nothing about the Person instance we are trying to process with method calls, there’s no way to create a bound method with an instance, and thus no way to correctly dis- patch the call.
If you want your function decorators to work on both simple functions and class methods, the most straightforward solution lies in using one of the other state retention solutions described earlier—code your function decorator as nested defs, so that you don’t depend on a single self instance argument to be both the wrapper class instance and the subject class instance. It can store both state by nonlocal attribute and pass arguments
Explicit syntax Decorators make augmentation explicit and obvious. Their @ syntax is easier to recognize than special code in calls that may appear anywhere in a source file—in our singleton and tracer examples, for instance, the decorator lines seem more likely to be noticed than extra code at calls would be. Moreover, decorators allow function and instance creation calls to use normal syntax familiar to all Python programmers. Code maintenance Decorators avoid repeated augmentation code at each function or class call. Be- cause they appear just once, at the definition of the class or function itself, they obviate redundancy and simplify future code maintenance. For our singleton and tracer cases, we need to use special code at each call to use a manager function approach—extra work is required both initially and for any modifications that must be made in the future. Consistency Decorators make it less likely that a programmer will forget to use required wrap- ping logic. This derives mostly from the two prior advantages—because decoration is explicit and appears only once, at the decorated objects themselves, decorators promote more consistent and uniform API usage than special code that must be included at each call. In the singleton example, for instance, it would be easy to forget to route all class creation calls through special code, which would subvert the singleton management altogether.