class constructors: class vs. instance defaults

Erik Johnson ej
Wed Oct 6 18:39:50 EDT 2004


    I am rather new to Python and still learning the finer points. I wanted
to have a class which has a member dictionary of other class instances, a
constructor that would initiate that dict with an empty dict if you didn't
provide one, and a member function to stick more other classes into that
dictionary.

I wrote something akin to this:

#! /usr/bin/python

#=======================================================================
class Bar:

    def __init__(self, foo_d={}):
        self.foo_d = foo_d

    def add_foo(self, foo):
        self.foo_d[foo.key] = foo
#=======================================================================


#=======================================================================
class Foo:
    pass
#=======================================================================


    You might be surprised to find out that all the Bar objects end up
sharing the same foo_d dictionary.
    I certainly was.


>>> from Test import *
>>> b1 = Bar()
>>> b2 = Bar()
>>> dir(b1)
['__doc__', '__init__', '__module__', 'add_foo', 'foo_d']
>>> b1.foo_d
{}
>>> b2.foo_d
{}
>>> f = Foo()
>>> f.key = 'key'
>>> b1.add_foo(f)
>>> b1.foo_d
{'key': <Test.Foo instance at 0x817821c>}
>>> b2.foo_d
{'key': <Test.Foo instance at 0x817821c>}
>>>

    A single, shared dictionary is definitely not what I wanted or expected
and didn't see why it was happening.  So... I struggled with that for a
while and eventually reasoned that the {} in the argument list to my
constructor is executed at the time of class definition, and is essentially
a reference to an instantiated dictionary object and therefor there is a
little empty dictionary sitting in memory somewhere prior to me ever
instantiating a Bar object. New Bar objects constructed without providing
their own foo_d end up sharing that one.  I think this makes sense enough,
now  (if I'm missing something, speak up).

    So, one work-around is to do this:

#=======================================================================
class Bar:

    def __init__(self, foo_d=None)
        if foo_d:
            self.foo_d = foo_d
        else:
            self.foo_d = {}
#=======================================================================


     This works. I reasoned that, unlike above, the {} in this definition is
not executed until a Bar object is instantiated. But there is a little voice
in my head saying "This is clunky. There must be some smarter, more Pythonic
way to do this sort of default handling without ending up with a single
shared object"  As I said, I am pretty new to Python and I am pretty pleased
with Python so far and expect to be using it more and more, (currently
porting and re-writing a lot of clunky PHP). I figured I might as well take
the time to learn this finer point now.

    This example is trivial and it may seem silly to waste time considering
the 5 lines in my constructor, but this is just a pared down skeleton of the
problem I ran into and I am about to write lots of classes with lots of
members and want to call constructors with different numbers of arguments
and have different sorts of defaults (not necessarily shared, but maybe) for
the arguments not passed explicitly. And so, if my workaround above is not a
very clean one, I want to learn to do it the right way before I amplify it
1000X.

    So... Yes? Is there a better way to do this sort of thing, or is that
perfectly reasonable code?

    Thanks for taking the time to read my post! :)

-ej





More information about the Python-list mailing list