Three Kinds Of Decorator

ref:
http://mrcoles.com/blog/3-decorator-examples-and-awesome-python/#wraps-it-up
http://www.artima.com/weblogs/viewpost.jsp?thread=240845
book : Python 3 Patterns & Idioms, section singleton
http://pythonconquerstheuniverse.wordpress.com/2012/04/29/python-decorators/
http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python/1594484#1594484

A decorator is a function that takes a function object as an argument, and returns a function object as a return value.

The return value of the decoration process must be a new function( a.k.a wrapped_f ), which wraps the function(i.e. original func: f) to be decorated.

Basic decorator

http://www.artima.com/weblogs/viewpost.jsp?thread=240808

Using Functions as Decorators

def mydecoration(f):
    def wrapped_f():
        print "Entering", f.__name__
        f()
        print "Exited", f.__name__
    return wrapped_f
 
@mydecoration
def func1():
    print "inside func1()"

@ is the same as :

func1 = mydecoration(func1)

basically, we decorated our function func1 by replacing it with a wrapped version. thus the new_f function has been substituted for the original function during decoration.

We often use functtools.wraps. The functools.wraps function is a nice little convenience function that makes the wrapper function (i.e., the return value from the decorator) look like the function it is wrapping. This involves copying/updating a bunch of the double underscore attributes—specifically module, name, doc, and dict. See more in the update_wrapper function in the functools source code.

from functools import wraps
 
def mydecoration(f):
    def wrapped_f():
        print "Entering", f.__name__
        f()
        print "Exited", f.__name__
    return wraps(f)(wrapped_f)
 
@mydecoration
def func1():
    print "inside func1()"

or

def mydecoration(f):
    @functools.wraps(f)
    def wrapped_f():
        print "Entering", f.__name__
        f()
        print "Exited", f.__name__
    return wrapped_f
 
@mydecoration
def func1():
    print "inside func1()"

Using Class as Decorators

class mydecoration(object):
 
    def __init__(self, f):
        self.f = f
 
    def __call__(self, *args, **kwargs):
        print "Entering", self.f.__name__
        self.f(*args, **kwargs)
        print "Exited", self.f.__name__
 
@mydecoration
def func1():
    print "inside func1()"

In this case, the original func1() has been replace by an instance of class mydecoration. i.e. the constructor for mydecorationis executed at the point of decoration of the function.

Parameterized decorator:

http://www.artima.com/weblogs/viewpost.jsp?thread=240845

use class as decorators

class decorator_maker(object):
 
    def __init__(self, arg1, arg2, arg3):
        """
        If there are decorator arguments, the function
        to be decorated is not passed to the constructor!
        """
        print "Inside __init__()"
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
 
    def __call__(self, f):
        """
        If there are decorator arguments, __call__() is only called
        once, as part of the decoration process! You can only give
        it a single argument, which is the function object.
        """
        print "Inside __call__()"
        def wrapped_f(*args):
            print "Inside wrapped_f()"
            print "Decorator arguments:", self.arg1, self.arg2, self.arg3
            f(*args)
            print "After f(*args)"
        return wrapped_f
 
@decorator_maker("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print 'sayHello arguments:', a1, a2, a3, a4
 
print "After decoration"
 
print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "after first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "after second sayHello() call"
 
# the output
 
Inside __init__()
Inside __call__()
After decoration
 
Preparing to call sayHello()
 
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
 
after first sayHello() call
 
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)

@ is the same as the following decoration process

sayHello = decorator_maker("hello", "world", 42)(sayHello)

It first create an instance of class decorator_maker, then call (sayHello).

Now the process of decoration calls the constructor and then immediately invokes call(), which can only take a single argument (the function object) and must return the decorated function object that replaces the original. Notice that call() is now only invoked once, during decoration, and after that the decorated function that you return from call() is used for the actual calls.

use function as decorators

def decorator_maker(arg1, arg2, arg3):
    def mydecoration(f):
        print "Inside mydecoration()"
        def wrapped_f(*args):
            print "Inside wrapped_f()"
            print "Decorator arguments:", arg1, arg2, arg3
            f(*args)
            print "After f(*args)"
        return wrapped_f
    return mydecoration
 
@decorator_maker("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print 'sayHello arguments:', a1, a2, a3, a4
 
print "After decoration"
 
print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "after first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "after second sayHello() call"
 
Here's the output:
 
Inside mydecoration()
After decoration
 
Preparing to call sayHello()
 
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
 
after first sayHello() call
 
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

@ is the same as the following decoration process

sayHello = decorator_maker("hello", "world", 42)(sayHello)

In the decoration process, function decorator_maker("hello", "world", 42) is called, and it returns a function, which is immediately called again with original function as the only argument. That's why we have three levels of functions; the inner one is the actual replacement function.

another explaining: the 1st step is just create a decorator. It's just a new function after all. then use it to decorate original function.

# Let's create a decorator. It's just a new function after all.
new_decorator = decorator_maker()       
# Then we decorate the function
decorated_function = new_decorator(decorated_function)
# Let's call the function:
decorated_function(args)

please note the difference between

@decorator_maker()
def func1(*args):
  pass
 
# vs
 
@mydecoration
def func1(*args):
  pass

decorate class

see the book Python idioms and patterns

class SingletonDecorator:
  def __init__(self,klass):
     self.klass = klass
     self.instance = None
 
  def __call__(self, *args, **kwds):
     if self.instance == None:
        self.instance = self.klass(*args,**kwds)
     return self.instance
 
class foo:
   pass
 
foo = SingletonDecorator(foo)
 
x=foo()
y=foo()
z=foo()
x.val = ’sausagey.val = ’eggsz.val = ’spamprint(x.val)
print(y.val)
print(z.val)
print(x is y is z)

the decoration process will be: replace the original Class foo with an instance of SingletonDecorator.

more examples
http://blog.genforma.com/2011/07/28/class-decorator-talk/

def add_foo_decorator(klass):
    klass.foo = "foo"
    return klass
 
@add_foo_decorator
class A:
    pass

with arguments :

def make_decorator(deco_arg):
    def decorator(klass):
        klass.foo = deco_arg
        return klass
    return decorator
 
@make_decorator("bar")
class B:
    pass

decorate the decorators

decorate the decorator when the decorator is applied.

http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python

def decorator_with_args(decorator):
    """ 
    This function is supposed to be used as a decorator.
    It must decorate an other function, that is intended to be used as a decorator.
    It will allow any decorator to accept an arbitrary number of arguments,
    """
 
    def decorator_maker(*args, **kwargs):
 
        # We create on the fly a decorator that accepts a function
        def decorator_wrapper(f):
 
            # We return the result of the original decorator, which, after all, 
            # IS JUST AN ORDINARY FUNCTION (which returns a function).
            # Only pitfall: the decorator must have this specific signature or it won't work:
            return decorator(f, *args, **kwargs)
 
        return decorator_wrapper
 
    return decorator_maker

or a more clean way:

def decorator_with_args(decorator):
    """ 
    This function is supposed to be used as a decorator.
    It must decorate an other function, that is intended to be used as a decorator.
    It will allow any decorator to accept an arbitrary number of arguments,
    """
 
    def decorator_maker(*args, **kwargs):
        # We return the result of the original decorator, which, after all, 
        # IS JUST AN ORDINARY FUNCTION (which returns a function).
        # Only pitfall: the decorator must have this specific signature or it won't work:
        def mydecorator(f):
          return decorator_to_enhance(f, *args, **kwargs)
 
        # We create on the fly a decorator that accepts only a function
        # but keeps the passed arguments from the maker.
        def decorator_wrapper(func):
          return mydecorator(func)
 
        return decorator_wrapper
 
    return decorator_maker

then it can be used as :

 You create the function you will use as a decorator. And stick a decorator on it :-)
# Don't forget, the signature is "decorator(func, *args, **kwargs)"
@decorator_with_args 
def decorator(f, *args, **kwargs): 
    def wrapper_f(function_arg1, function_arg2):
        print "Decorated with", args, kwargs
        return f(function_arg1, function_arg2)
    return wrapper_f
 
# Then you decorate the functions you wish with your brand new decorated decorator.
 
@decorator(42, 404, 1024)
def f(function_arg1, function_arg2):
    print "Hello", function_arg1, function_arg2
 
f("Universe and", "everything")
#outputs:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything

what is the decoration process? It means it accepts a func and return a wrapped function. however, in the above code
decorator is defined as

decorator(f, *args, **kwargs)

thus, we much create a special decorator on the fly and make it comply with decoration requirements. That explains why
decorator_with_args's inner function has such signature.

Please also note: the parameters are added when the decorator is applied on specific function f ().

decorate the decorator when the decorator is defined.

def decorator_with_args(name):
    def decorator_maker(decorator):
      def mydecorator(f):
        org_decorator_i = decorator(f)
 
        def wrap_f(function_arg1, function_arg2):
          return org_decorator_i(function_arg1, function_arg2) + name
        return wrap_f  
 
      def decorator_wrapper(f):
        return mydecorator(f)
      return decorator_wrapper
    return decorator_maker
 
@decorator_with_args(' is good!') 
def decorator(func): 
    def wrapper(function_arg1, function_arg2):
        return func(function_arg1, function_arg2)
    return wrapper
 
# Then you decorate the functions you wish with your brand new decorated decorator.
 
@decorator
def f(function_arg1, function_arg2):
    return ' '.join(["Hello", function_arg1, function_arg2])
 
print f("Universe and", "everything")
# outputs:
# Hello Universe and everything is good!

chain decorators

an interesting question from http://stackoverflow.com/questions/5952641/decorating-decorators-try-to-get-my-head-around-understanding-it

suppose you have

  • decorator log(), it logs result
  • decorator upper(), it converts result to upper string
  • decorator lower(), it converts result to lower string

if there is a function f() that returns string.
if decorate it as :

@log
@upper
def f():
  ...

or

@log
@lower
def f():
  ...

This is fine. but it is inconvenient, what if you want the log is applied in either case. basically you want this format:

@log
def upper():
  ...
 
@log
def lower():
  ...
 
@upper
def f():
  ...

Here is a approach that will work:

def deco_decorator(log):
  def decorator_maker(upper_decorator):
    def mydecorator(f):
      upper_decorator_i = upper_decorator(f)
      wrap_decorator_i = log(upper_decorator_i)
      return wrap_decorator_i  
 
    def decorator_wrapper(f):
      return mydecorator(f)
    return decorator_wrapper
 
  return decorator_maker
 
def log(f):
    def wrapper():
        print "*".join(f())
    return wrapper
 
@deco_decorator(log)
def upper(f):
    def uppercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.upper(), b.upper()
    return uppercase
 
@deco_decorator(log)
def lower(f):
    def lowercase(*args, **kargs):
        a,b = f(*args, **kargs)
        return a.lower(), b.lower()
    return lowercase
 
@upper
def byebye():
    return "bYe", "byE"
 
@lower
def byebye2():
    return "bYe", "byE"
 
if __name__ == '__main__':
    byebye()
    byebye2()
 
# the output will be
# BYE*BYE
# bye*bye
Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License