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

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.

Python, 34 lines
 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.

7 comments

Paul Moore 12 years, 10 months ago  # | flag

It's easy enough to do this "by hand" 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!)

Gary Robinson 12 years, 10 months ago  # | flag

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:

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)
Henry Crutcher (author) 12 years, 10 months ago  # | flag

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 :-).

Henry Crutcher (author) 12 years, 10 months ago  # | flag

Thanks for that modification! That is really cool... I'll add that to the recipe...

Thanks!
Peter Schwalm 12 years, 10 months ago  # | flag

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

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)
Martin Miller 12 years, 1 month ago  # | flag

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:

codeObject = beingInitted.__class__.__init__.im_func.func_code
Henry Crutcher (author) 11 years, 7 months ago  # | flag

Fixed! The new code has that line added back in...

Add a comment

Sign in to comment