Introduction

In part one of this article series I introduced the Params2attribs decorator that copies object initialiser parameters to object attributes.
In part two we took a closer look at the Python decorator mechanics.

This is the final part in which we are going to dissect the Params2attribs decorator.

Overview

From the code below you can glean that the decorator is implemented as a class with three methods:

initialiser (lines 20 – 22)

the special __call__ method that gets invoked when you treat an object like a function (lines 23 – 42)

an auxilliary method (copy2attribs) that’s in charge of the parameter copying business

Before diving into the code, please bear in mind that the Params2attribs::__call__() method gets executed whenever Python loads any code decorated with Params2attribs (e.g. lines 19 – 20 in part one). At that point the wrapper function is only defined and returned to Python as a replacement for the decorated function.

In contrast, the wrapper function is executed whenever the decorated function is called (e.g. line 27 in part one).

If any of this is unclear please refer back to part two where the workings of Python decorators were explained in greater detail.

What we are after here are the names of the parameters (passed to the decorated function) and they are all contained in the first element of the argspec tuple. Please note how the names of embedded parameters are returned in an embedded list which is quite handy as we will see later.

The wrapper

At the point of its definition the wrapper function has access to the argspec tuple due to lexical scoping but argspec will be available to it at execution time as well because it is part of its closure.

When a CoolApproach object is instantiated as shown on line 27 in part one, the wrapper function is called and the value of its args parameter is as follows:

Subsequently, the argvl list is passed (along with the object reference) to the copy2attribs helper function which will do the copying for us (line 36 above).
Last but not least, the wrapped function itself is invoked on line 38 (above).

The helper function

Eventually, the code below copies the parameter name/value pairs to object attributes. While doing so, it observes the list of parameters that are not to be copied (see line 52).

How is this to be understood? The first invocation is from the wrapper function (line 36). The second invocation occurs through a recursive call (line 50) when the top-level copy2attribs invocation processes the embedded list (second element of argvl: (['arg_2', 'arg_3'], ('E1', 'E2'))).

Conclusion

This concludes the Python decorator mini-study. I hope you liked it and would appreciate your comments. Also, I will be making the sources available as soon as I have figured out how I can upload something that is not a picture to WordPress.

Addendum

Please note: the fix for the issues pointed out in the comments as well as the links to the source code can be found here.

Introduction

In the previous part of this article I introduced the Params2attribs decorator that copies object initialiser parameters to object attributes.

In this part I aim to introduce the Python decorator mechanisms needed to drill down into Params2attribs in the forthcoming third part of this series.

Python decorator 101

Python decorators come in two flavours: simple and not so simple :-) The Params2attribs decorator which is the subject of this article series falls into the second category. But, as they say, first things first..

Simple decorators

The simple decorator variety is in essence a function that returns a wrapper function that is to be invoked instead of the wrapped function. Confused? You should be :-)

Let’s have a look at an example. In the code below the wrapper function (lines 13 – 16) merely logs the invocation of the wrapped function.

Just in case you haven’t seen this before: the *args and **kwargs constructs (on lines 13 and 15) are the Python way to pass positional and keyword function parameters in a generic fashion.

is invoked by Python whenever it loads any code decorated with it (lines 19 – 20)

defines (lines 13 – 16) and returns (line 17) another function (wrapper()) that is to be invoked (line 23) instead of the decorated one (plf())

As you may have guessed already, running the code above results in the following output:

mhr@playground2:~/src/published$ python simpledeco.py
Hey plf(), you have been wrapped..
Help! I am only a poor little function!
.. resistance is futile

Complex decorators

Complex decorators may be implemented as classes or using nested functions. The latter variant was suggested by a reader and is shown after the section on decorator classes.

Decorator classes

When using decorator classes any data beyond the function to be wrapped is passed to the decorator class initialiser (see e.g. the before and after parameters of initialiser interceptor::__init__(), lines 13 – 15).

Nested functions

As a reader pointed out after the first draft of this article was posted (see also the first two comments on this weblog entry) a similar effect can be achieved using nested functions. The nested functions variant of the decorator looks as follows:

Introduction

On quite a few occasions I would find myself coding Python object initialiser methods where I would manually copy the method parameters to object attributes in order to make use of them later (see e.g. TheHardWay::__init__(), lines 13 – 17 below).

This is an unnecessary laborious, error-prone and unpythonic activity :-) In this article I hence present an alternative and more pythonic solution: a function decorator that copies initialiser method parameters to object attributes (see lines 19 – 24 below for an example showing how that decorator is used).

Last but not least, the decorator to be shown in (part 3 of) this article is most useful in conjunction with initialiser methods but there is nothing to prevent you from using it with any object method.

Warm-up exercise

Again, the code section below (lines 13 – 17) shows how to copy the parameters to object attributes in a manual fashion.

The alternative solution below uses a python function decorator (on line 19) which is given a (possibly empty) tuple of parameter names for which no copying should occur.
In this particular example we don’t want arg_3 to be copied and hence specify it in the tuple passed to the decorator’s object initialiser.

Please note also that the decorator is capable of handling embedded parameter lists (like e.g. parameters arg_2 and arg_3 which are passed to the decorated initialiser via such a list (on line 20)).

Finally, the block below merely instantiates a CoolApproach object (line 27) in order to demonstrate the described decorator behaviour. Line 28 sorts the attributes of the newly instantiated object for clarity.