class constructors: class vs. instance defaults

Larry Bates lbates at syscononline.com
Thu Oct 7 01:19:51 CEST 2004


Erik Johnson <ej <at> wellkeeper wrote:
>     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
> 
> 

I write this as:

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

     def __init__(self, foo_d=None)
         self.foo_d = foo_d or {}
#=======================================================================

but it is basically the same thing as others have suggested.  Seems to
make it into a "one-liner" and I think the overhead is quite small to
evaluate the boolean 'or'.

Larry Bates



More information about the Python-list mailing list