|
|
 |
|
Title: Automatically initializing instance variables from __init__ parameters
Submitter: Henry Crutcher
(other recipes)
Last Updated: 2005/09/21
Version no: 1.2
Category:
OOP
|
|
4 vote(s)
|
|
|
|
Description:
This recipe assigns each parameter to an instance variable of the same name, automating a common pattern of object initialization, and making class definitions more compact.
Source: Text Source
import sys
def initFromArgs(beingInitted, bJustArgs=False):
import sys
codeObject = beingInitted.__class__.__init__.im_func.func_code
for k,v in sys._getframe(1).f_locals.items():
if k!='self' and ((not bJustArgs) or k in codeObject.co_varnames[1:codeObject.co_argcount]):
setattr(beingInitted,k,v)
class Animal:
def __init__(self,name='Dog',numberOfLegs=4,habitat='Temperate'):
if name in ('Dog','Cat'):
pet=True
initFromArgs(self)
if __name__ == '__main__':
dog=Animal()
octopus = Animal('Octopus',8,'Aquatic')
print [i.__dict__.items() for i in (dog,octopus)]
Discussion:
Consider the implementation of the Animal class without this recipe.
class Animal:
def __init__(self, name='Dog',numberOfLegs=4,habitat='Temperate'):
self.name=name
self.numberOfLegs=numberOfLegs
self.habitat=habitat
if name in ('Dog','Cat'):
self.pet=True
Notice that the information of what data members are in the Animal class is duplicated, once in the parameter list, and once in the initialization code. Also notice that the "application logic", such as it is, is buried in the initialization code. One could use the Bunch class to store the Animal data, but having the default values presumably provides structure, and illustrates which values are going to be used in "application logic" elsewhere in the program.
|
|
Add comment
|
|
Number of comments: 7
It's easy enough to do this "by hand", Paul Moore, 2004/07/02
The following shows how:
>>> class C:
... def __init__(self, a=1, b=2, **kw):
... c = 3
... self.__dict__.update(kw)
... del kw # We don't want this in attrs
... self.__dict__.update(locals())
... del self.self # We don't need this either
... def dump(self):
... print repr(self.__dict__)
...
>>> c = C(d=4)
>>> c.dump()
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> c.a
1
>>> c.c
3
Note that the commented lines are there purely to tidy up the instance namespace, and if you don't need arbitrary keywords, the **kw stuff can go, leaving just self.__dict__.update(locals()).
But idiom or function, I agree, it's something that I need quite often. (And hadn't really thought about the best approach for, until now - so thanks!)
Add comment
Didn't know about locals(), Henry Crutcher, 2004/07/05
THanks! I didn't know about the locals function. That is cool! I just like having it in a function, so I only have to look at one copy...., but that idiom is much cleaner than what was in my code :-).
Add comment
Ignoring locals, Gary Robinson, 2004/07/03
Often, I want to ignore locals when I set attributes. The following modification adds a parm to tell initFromArgs to do that. If bJustArgs is True, only parms to the __init__ method are turned into instance attributes.
So, for example, in the Animal class, the "pet" local would be ignored:
def initFromArgs(beingInitted, bJustArgs=True):
codeObject = beingInitted.__class__.__init__.im_func.func_code
tupNames = codeObject.co_varnames[1:codeObject.co_argcount]
for k,v in sys._getframe(1).f_locals.items():
if (not bJustArgs) or k in codeObject.co_varnames[1:codeObject.co_argcount]:
setattr(beingInitted,k,v)
Add comment
Thanks for that modification!, Henry Crutcher, 2004/07/05
That is really cool... I'll add that to the recipe...
Thanks!
Add comment
use the actual function name of the caller, Peter Schwalm, 2004/07/07
Very nice.
As an enhancement I would suggest using the actual name of the caller (instead of the hard-wired name "__init__").
The name of the caller can be found by
callerName = sys._getframe(1).f_code.co_name
Like your's, this code is also inspired by Alex Martelli (as far as I remember).
The code object can then be found by
codeObject = beingInitted.__class__.__dict__[callerName].
The full function text would then look like this:
def initFromArgs(beingInitted, bJustArgs = True):
callerName = sys._getframe(1).f_code.co_name
codeObject = beingInitted.__class__.__dict__[callerName].func_code
for key, value in sys._getframe(1).f_locals.items():
if (not bJustArgs) or key in codeObject.co_varnames[1:codeObject.co_argcount]:
setattr(beingInitted, key, value)
Add comment
missing line of code, Martin Miller,Martin Miller, 2005/03/07
The following line is missing in version 1.1 of the code and should be inserted near the beginning of the body of the initFromArgs() function -- i.e. before the for loop where it is used:
codeObject = beingInitted.__class__.__init__.im_func.func_code
Add comment
Fixed!, Henry Crutcher, 2005/09/21
The new code has that line added back in...
Add comment
|
|
|
|
|
 |
|