Welcome, guest | Sign In | My Account | Store | Cart

Did you ever have to write an initializer whose body consisted of little more than a suite of self.attr = attr style assignments? Here's a utility function that factors out this task.

Python, 17 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def attributesFromDict(d, obj=None, objName="self"):
    if obj is None:
        obj = d.pop(objName)
    for n, v in d.iteritems():
        setattr(obj, n, v)

class Before:
    def __init__(self, foo, bar, baz, boom=1, bang=2):
        self.foo = foo
        self.bar = bar
        self.baz = baz
        self.boom = boom
        self.bang = bang

class After:
    def __init__(self, foo, bar, baz, boom=1, bang=2):
        attributesFromDict(locals())

As there is no additional logic in After.__init__(), the locals() dictionary contains only the parameters provided. attributesFromDict() by default extracts self and interprets all other items in the dictionary as attribute name/value pairs which are then set. This is similar to the technique of using only keyword parameters

def __init__(self, **kw): self.__dict__.update(kw)

but the latter lacks an easy way to specify the mandatory/optional parameters and has often proved a showstopper when reading other people's code.

Limitations: As there is no easy way to specify a subset of the local variables, you cannot conveniently use attributesFromDict() in a method with complex logic that needs temporary variables. For the same reason it is not easily applicable in a subclass, that propagates a subset of its parameters to the baseclass initializer.

Credits: John J. Lee originally posted the problem on comp.lang.python, and if you are interested in the strange detours that led to the solution presented above, have a look at http://mail.python.org/pipermail/python-list/2004-April/216871.html

3 comments

Gary Robinson 19 years, 12 months ago  # | flag

A more robust approach? The following version works even in __init__ methods which have other logic and temporary variables:

def assignAttr(obj, dctLocals):
    codeObject = obj.__class__.__init__.im_func.func_code
    tupNames = codeObject.co_varnames[1:codeObject.co_argcount]
    for strName in tupNames:
       obj.__dict__[strName] = dctLocals[strName]

class  After:
    def __init__(self, foo, bar, baz, boom=1, bang=2):
        assignAttr(self, locals())
Alain Pointdexter 17 years, 7 months ago  # | flag

a simplification (no parameter needed). import inspect

def init_attributes():

......d=inspect.currentframe().f_back.f_locals

......obj = d.pop("self")

......for n, v in d.iteritems():

............setattr(obj, n, v)

class After:

......def __init__(self, foo, bar, baz, boom=4, bang=5):

...........init_attributes()

Gary Robinson 13 years, 2 months ago  # | flag

Note that init_attributes() relies on the fact that the first method argument is called "self". While that's true in virtually all code I've seen or written, it's still a dependency. (Also, init_attributes() needs to be called before any local variables are assigned in __init__().)

For anyone who may be care about those admittedly very small issues, here's a version of assignAttr that still takes self, but doesn't need locals() to be passed in (it uses the inspect module as taught by init_attributes()):

def assignAttr(self): import inspect name2Var = d=inspect.currentframe().f_back.f_locals codeObject = self.__class__.__init__.im_func.func_code tupNames = codeObject.co_varnames[1:codeObject.co_argcount] for name in tupNames: setattr(self, name, name2Var[name])

Created by Peter Otten on Tue, 27 Apr 2004 (PSF)
Python recipes (4591)
Peter Otten's recipes (3)

Required Modules

  • (none specified)

Other Information and Tasks