Python Decorator

Posted by Jeffery Yee | 2:08 PM

What is decorator in Python?

A decorator replaces an invocation of one function with another in a way that that is imperceptible to the client.
Normally a decorator will add a small amount of functionality to the original function which it invokes. A decorator can modify the arguments before passing them to the original function or modify the return value before returning it to the client. Or it can leave the arguments and return value unmodified but perform a side effect such as logging the call.

A simple decorator

 Let's start with simple decorators,  and then to useful decorators.
>>> def outer(a_func):
...     def inner():
...         print "before a_func"
...         ret = a_func() # 1
...         return ret + 1
...     return inner
>>> def myfun():
...     return 1
>>> decorated = outer(myfun) # 2
or 
>>> myfun = outer(myfun) # 2
>>> decorated()
before a_func
2

>>> def wrapper(func):
...     def checker(a, b): # 1
...         if a.x < 0 or a.y < 0:
...             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
...         if b.x < 0 or b.y < 0:
...             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
...         ret = func(a, b)
...         if ret.x < 0 or ret.y < 0:
...             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
...         return ret
...     return checker
>>> add = wrapper(add)
>>> sub = wrapper(sub)
>>> sub(one, two)
Coord: {'y': 0, 'x': 0}
>>> add(one, three)
Coord: {'y': 200, 'x': 100}

Use @ symbol to apply a decorator to a function

The @ symbol is an easy way to decorate a function. This pattern can be used at any time, to wrap any function. See the following example
>>> @wrapper
... def add(a, b):
...     return Coordinate(a.x + b.x, a.y + b.y)
In other words, you don't have to use add = wrapper(add) any more, just add @wrapper right on top of the function to be decorated. 
Python just adds some syntactic sugar to make what is going on very explicit. 

Use funtools.wrap


functools.wraps(wrapped[, assigned][, updated])
This is a convenience function for invoking partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) as a function decorator when defining a wrapper function. For example:
>>>
>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print 'Calling decorated function'
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print 'Called example function'
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'
Without the use of this decorator factory, the name of the example function would have been 'wrapper', and the docstring of the original example() would have been lost.


Make a chain decorate

See the following code as an example: 
In [32]: def makebold(fn):
   ....:         def wrapped():
   ....:                 return "<b>" + fn() + "</b>"
   ....:         return wrapped
   ....: 

In [33]: def makeitalic(fn):
   ....:         def wrapped():
   ....:                 return "<i>" + fn() + "</i>"
   ....:         return wrapped
   ....: 

In [34]: @makebold
   ....: @makeitalic
   ....: def hello():
   ....:         return "hello world"
   ....: 

In [35]: hello()
Out[35]: '<b><i>hello world</i></b>

Complex nested Wrapper

If they were to return a new function, an extra level of nestedness would be required. In the worst case, three levels of nested functions.
>>>
>>> def replacing_decorator_with_args(arg):

...   print "defining the decorator"

...   def _decorator(function):

...       # in this inner function, arg is available too

...       print "doing decoration,", arg

...       def _wrapper(*args, **kwargs):

...           print "inside wrapper,", args, kwargs

...           return function(*args, **kwargs)

...       return _wrapper

...   return _decorator

>>> @replacing_decorator_with_args("abc")

... def function(*args, **kwargs):

...     print "inside function,", args, kwargs

...     return 14

defining the decorator

doing decoration, abc

>>> function(11, 12)

inside wrapper, (11, 12) {}

inside function, (11, 12) {}

14

The _wrapper function is defined to accept all positional and keyword arguments. In general we cannot know what arguments the decorated function is supposed to accept, so the wrapper function just passes everything to the wrapped function. One unfortunate consequence is that the apparent argument list is misleading.
Compared to decorators defined as functions, complex decorators defined as classes are simpler. When an object is created, the __init__ method is only allowed to return None, and the type of the created object cannot be changed. This means that when a decorator is defined as a class, it doesn’t make much sense to use the argument-less form: the final decorated object would just be an instance of the decorating class, returned by the constructor call, which is not very useful. Therefore it’s enough to discuss class-based decorators where arguments are given in the decorator expression and the decorator __init__ method is used for decorator construction.
>>>
>>> class decorator_class(object):

...   def __init__(self, arg):

...       # this method is called in the decorator expression

...       print "in decorator init,", arg

...       self.arg = arg

...   def __call__(self, function):

...       # this method is called to do the job

...       print "in decorator call,", self.arg

...       return function

>>> deco_instance = decorator_class('foo')

in decorator init, foo

>>> @deco_instance

... def function(*args, **kwargs):

...   print "in function,", args, kwargs

in decorator call, foo

>>> function()

in function, () {}


Class decorator

Contrary to normal rules (PEP 8) decorators written as classes behave more like functions and therefore their name often starts with a lowercase letter.
In reality, it doesn’t make much sense to create a new class just to have a decorator which returns the original function. Objects are supposed to hold state, and such decorators are more useful when the decorator returns a new object.
>>>
>>> class replacing_decorator_class(object):

...   def __init__(self, arg):

...       # this method is called in the decorator expression

...       print "in decorator init,", arg

...       self.arg = arg

...   def __call__(self, function):

...       # this method is called to do the job

...       print "in decorator call,", self.arg

...       self.function = function

...       return self._wrapper

...   def _wrapper(self, *args, **kwargs):

...       print "in the wrapper,", args, kwargs

...       return self.function(*args, **kwargs)

>>> deco_instance = replacing_decorator_class('foo')

in decorator init, foo

>>> @deco_instance

... def function(*args, **kwargs):

...   print "in function,", args, kwargs

in decorator call, foo

>>> function(11, 12)

in the wrapper, (11, 12) {}

in function, (11, 12) {}

A decorator like this can do pretty much anything, since it can modify the original function object and mangle the arguments, call the original function or not, and afterwards mangle the return value.



0 comments

Popular Posts

无觅相关文章插件,迅速提升网站流量