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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | # This code was taken and modified from Alex Martelli's
# "determine the name of the calling function" recipe (Thanks, Alex!)
#
# This code also benefits from a useful enhancement from Gary Robinson, allowing
# only the arguments to __init__ to be copied, if that is desired.
#
# use sys._getframe() -- it returns a frame object, whose attribute
# f_locals is the list of local variables. Before any processing goes on,
# will be the list of parameters passed in.
import sys
# By calling sys._getframe(1), you can get this information
# for the *caller* of the current function. So you can package
# this functionality up into your own handy functions:
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'):
# any local variables added here will be assigned to the object
# as if they were parameters
if name in ('Dog','Cat'):
pet=True
initFromArgs(self)
# modify things here
if __name__ == '__main__':
dog=Animal()
octopus = Animal('Octopus',8,'Aquatic')
print [i.__dict__.items() for i in (dog,octopus)]
|
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.
It's easy enough to do this "by hand" The following shows how:
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!)
Ignoring locals. 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:
Didn't know about locals(). 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 :-).
Thanks for that modification! That is really cool... I'll add that to the recipe...
use the actual function name of the caller. 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
Like your's, this code is also inspired by Alex Martelli (as far as I remember).
The code object can then be found by
The full function text would then look like this:
missing line of code. 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:
Fixed! The new code has that line added back in...