Recipe 6.18. Automatically Initializing Instance Variables from _ _init_ _ Arguments
Credit: Peter Otten, Gary Robinson, Henry Crutcher, Paul
Moore, Peter Schwalm, Holger Krekel
Problem
You want to avoid writing and maintaining _ _init_
_ methods that consist of almost nothing but a series of
self.something = something assignments.
Solution
You can "factor out" the
attribute-assignment task to an auxiliary function:
def attributesFromDict(d):Now, the typical boilerplate code for an _ _init_
self = d.pop('self')
for n, v in d.iteritems( ):
setattr(self, n, v)
_ method such as:
def _ _init_ _(self, foo, bar, baz, boom=1, bang=2):can become a short, crystal-clear one-liner:
self.foo = foo
self.bar = bar
self.baz = baz
self.boom = boom
self.bang = bang
def _ _init_ _(self, foo, bar, baz, boom=1, bang=2):
attributesFromDict(locals( ))
Discussion
As long as no additional logic is in the body of _ _init_
_, the dict returned by calling the
built-in function locals contains only the
arguments that were passed to _ _init_ _ (plus
those arguments that were not passed but have default values).
Function attributesFromDict extracts the object,
relying on the convention that the object is always an argument named
'self', and then interprets all other items in the
dictionary as names and values of attributes to set. A similar but
simpler technique, not requiring an auxiliary function,
is:
def _ _init_ _(self, foo, bar, baz, boom=1, bang=2):However, this latter technique has a serious defect when compared to
self._ _dict_ _.update(locals( ))
del self.self
the one presented in this recipe's Solution: by
setting attributes directly into self._ _dict_ _
(through the latter's update
method), it does not play well with properties and other advanced
descriptors, while the approach in this recipe's
Solution, using built-in setattr, is impeccable in
this respect.attributesFromDict is not meant for use in an
_ _init_ _ method that contains more code, and
specifically one that uses some local variables, because
attributesFromDict cannot easily distinguish, in the
dictionary that is passed as its only argument d,
between arguments of _ _init_ _ and other local
variables of _ _init_ _. If
you're willing to insert a little introspection in
the auxiliary function, this limitation may be overcome:
def attributesFromArguments(d):By extracting the code object of the _ _init_ _
self = d.pop('self')
codeObject = self._ _init_ _.im_func.func_code
argumentNames = codeObject.co_varnames[1:codeObject.co_argcount]
for n in argumentNames:
setattr(self, n, d[n])
method, function attributesFromArguments
is able to limit itself to the names of
_ _init_ _'s arguments. Your
_ _init_ _ method can then call
attributesFromArguments(locals( )), instead of
attributesFromDict(locals( )), if and when it
needs to continue, after the call, with more code that may define
other local variables.The key limitation of attributesFromArguments is
that it does not support _ _init_
_ having a last special argument of the
**kw kind. Such support can be added, with yet
more introspection, but it would require more black magic and
complication than the functionality is probably worth. If you
nevertheless want to explore this possibility, you can use the
inspect module of the standard library, rather
than the roll-your-own approach used in function
attributeFromArguments, for introspection purposes.
inspect.getargspec(self._ _init_ _) gives you both
the argument names and the indication of whether self._
_init_ _ accepts a **kw form. See Recipe 6.19 for more information
about function inspect.getargspec. Remember the
golden rule of Python programming: "Let the standard
library do it!"
See Also
Library Reference and Python in a
Nutshell docs for the built-in function
locals, methods of type dict,
special method _ _init_ _, and introspection
techniques (including module inspect).