Programmer's Python - Decorators
Written by Mike James   
Monday, 28 May 2018
Article Index
Programmer's Python - Decorators
Decorator Factories

Multiple Decorators

You can use more than one decorator and they are applied starting with the decorator closest to the function definition – which might not be what you expect because it goes against the usual top-to-bottom order.

That is:

@decA
@decB
@decC
def myfunction():
    pass

is equivalent to:

myfunction=decA(decB(decC(myfunction)))

This is a better order because it corresponds to the idea that the inner decorator gets to modify the function first.

Decorator Factories

The final variation is that what follows the @ in a decorator can be a function that is called to provide the function that is the decorator.

That is, we can use a decorator factory.

Other accounts of how this all works tend to refer to this as a "decorator with a parameter", but this isn’t really accurate as the function isn’t the decorator it really is a decorator factory.

The key idea to notice is that is you write @dec then dec is a decorator but if you write @dec() then the dec function is called and it is expected to return

For example:

@decA()
    def myfunction():
        return mydecorator

is equivalent to:

myfunction=decA()(myfunction)

that is. decA is called and it returns a function, mydecorator, which is applied to myfunction.

The main reason for using this function factory approach is to provide parameters to customize the decorator.

For example, if you want to add a string to the docstring of a function you might use something like:

def name(name):
    def wrapper(f):
        f.__doc__=name
        return f
    return wrapper

This simply returns a decorator which adds name to the docstring.

For example:

@name("bad programmer")
    def myfunc():
        pass print(myfunc.__doc__)

prints bad programmer.

Notice that this is a simpler form of decorator because it simply modifies an attribute of the function and doesn’t therefore actually wrap the function with another function.

As another example the following decorator will add an attribute and its value to any function:

def attrib(name,value):
    def wrapper(f):
        setattr(f,name,value)
        return f
    return wrapper

To use it simply add as many @attrib decorators are you need:

@attrib("myAttribute",1)
def myfunc():
    pass

Now myfunc has an attribute called myAttribute set to 1. Notice that this isn’t particularly efficient as it creates a wrapper function for each attribute it adds.

Finally, it is worth pointing out that while decorators are powerful and very attractive features it is easy to create something fragile.

Consider the idea of adding a self reference to a function, see Chapter 4. This would allow the function to be written with a self parameter but then used without it.

That is:

@selfRef
@attrib("myAttribute",0)
def myfunc(self):
    self.myAttribute+=1
    return self.myAttribute

After this the function can be called as

myfunc()

and the self parameter would be automatically set. This is fairly easy using the decorator’s wrapper to form a closure and then calling the original function:

def selfRef(f):
    self=f
    def wrapper(*pos,**names):
        result=f(self, *pos,**names)
        return result
return wrapper

you can see the idea; self keeps track of the original function object which is called with self as the first parameter. In fact, you don’t actually need to use self as f is also in the closure, but using self looks better.

If you try this out you will find it works, but if you make a small change to the use of the decorators by reversing their order

@attrib("myAttribute",0)
@selfRef
def myfunc(self):
    self.myAttribute+=1
    return self.myAttribute

you will find it doesn’t work.

The reason is that the order in which the decorators are called is from closest to the def. This means that selfRef keeps track of the original function object, but returns its wrapper function. The attrib decorator then adds the attribute to the wrapper function. When the function is called the function object that self references doesn’t have the attribute.

One solution is to add:

wrapper.__dict__=f.__dict__

which makes the wrapper’s attribute Dictionary use the original function’s attribute Dictionary. Now it doesn’t matter what order attributes are added because both function object share the same attribute Dictionary.

The problem is that when you do things like adding attributes to functions with decorators you have to ask yourself which function object are the attributes being added to, and with more decorators there are more function objects to be confused about.

Decorators can also be applied to more than just functions and we return to them in Chapter 8.

 

Programmer's Python
Everything is an Object
Second Edition

Is now available as a print book: Amazon

pythonObject2e360

Contents

  1. Get Ready For The Python Difference
  2. Variables, Objects and Attributes
  3. The Function Object
  4. Scope, Lifetime and Closure
      Extract 1: Local and Global ***NEW!
  5. Advanced Functions
  6. Decorators
  7. Class, Methods and Constructors
      Extract 1: Objects Become Classes 
  8. Inside Class
  9. Meeting Metaclasses
  10. Advanced Attributes
  11. Custom Attribute Access
  12. Single Inheritance
  13. Multiple Inheritance
  14. Class and Type
  15. Type Annotation
  16. Operator Overloading
  17. Python In Visual Studio Code

 Extracts from the first edition

<ASIN:1871962749>

<ASIN:1871962595>

<ASIN:1871962765>


espbook

 

Comments




or email your comment to: comments@i-programmer.info

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner

 



Last Updated ( Monday, 28 May 2018 )