Decorator fix

Decorator implementation correction

Again, I would like to thank Brian McErlean who pointed out some issues in the Params2attribs decorator code (see his first two comments on part three).

I reworked the implementation of the __call__ method as shown below and it now handles parameter defaults as well as keyword parameters properly.

 1  #!/usr/bin/env python
 2  """
 3  Module with a decorator class for initialiser (__init__()) methods. The
 4  decorator wraps initialiser methods in functions that copy the initialiser
 5  parameter values to attributes of the respective object.
 6  """
 7
 8  # Copyright: (c) 2006 Muharem Hrnjadovic
 9  # created: 16/10/2006 06:49:38
10
11  __version__ = "$Id$"
12  # $HeadURL $
13
14  import inspect
15
16  class Params2attribs(object):
17      """Decorator class for initialiser (__init__()) methods, wraps them
18      in functions that copy the initialiser parameter values to attributes
19      of the respective object."""
20      def __init__(self, exclude_params=None):
21          """what parameters should we exclude from copying (if any)?"""
22          self.exclude_params = exclude_params
23      def __call__(self, f):
24          """define and return the wrapper for the decorated __init__() method"""
25          # introspect f() for parameter data, 'argspec' becomes part of
26          # the wrapper() closure
27          argspec = inspect.getargspec(f)
28          def wrapper(*args, **kwargs):
29              """wrapper function for a decorated __init__ method"""
30              # the first parameter is the initialiser's object reference
31              obj = args[0]
32              # initialise parameter value list
33              pvalues = [None] * (len(argspec[0]) - 1)
34              # first superimpose the default values (if any)
35              if argspec[3]: pvalues[len(pvalues)-len(argspec[3]):] = argspec[3]
36              # now superimpose the positional parameter values
37              pvalues[0:len(args)-1] = args[1:]
38              # combine the initialiser's parameter names with their
39              # respective values (excluding the object reference)
40              argvl = zip(argspec[0][1:], pvalues)
41              # finally, superimpose the keyword parameter values 
42              for k in kwargs: argvl[argspec[0].index(k)-1] = (k, kwargs[k])
43              # copy the initialiser parameters to attributes of its object
44              self.copy2attribs(obj, argvl)
45              # finally, invoke the wrapped initialiser method
46              f(*args, **kwargs)
47          # assign the wrapped method's doc string to the wrapper
48          wrapper.func_doc = f.func_doc
49          # .. and eventually return the wrapper function
50          return wrapper
51      def copy2attribs(self, obj, argvl):
52          """copy the attribute/value pairs in 'argvl' to attributes of object
53             'obj'. Resolve any embedded attribute/value pair lists through
54             recursive calls"""
55          for argn, argv in argvl:
56              # resolve embedded parameters via a recursive call
57              if isinstance(argn, list):
58                  self.copy2attribs(obj, zip(argn, argv))
59              # skip unwanted initialiser parameters
60              elif self.exclude_params and argn in self.exclude_params:
61                  continue
62              else: setattr(obj, argn, argv)

Please note: only lines 32 – 42 (above) changed. The rest of the code is the same. Speaking of code: now the source files are available as well. Please find them here:

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s