class constructors: class vs. instance defaults

bruno modulix onurb at xiludom.gro
Sat Oct 9 15:55:34 CEST 2004


Erik Johnson <ej <at> wellkeeper a écrit :
>     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

Woops ! What this does is probably not what you'd think, and I guess 
that you will complain that all instances of Bar share the same dict...

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

Bingo !-)

>     I certainly was.

I'm not. But I certainly was first time I stumbled upon this !-)
This is a FAQ. The correct idiom is :

class Bar:
   def __init__(self, foo_d=None):
     if foo_d is None:
       self.foo_d = {}
     else :
       self.foo_d = foo_d

>     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, 

Exactly.

> 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 = {}

You've almost got it. You just want to test against None, not again 
False, since an empty dict evaluate to False in a boolean test.

Python 2.3.3 (#2, Feb 17 2004, 11:45:40)
[GCC 3.3.2 (Mandrake Linux 10.0 3.3.2-6mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
 >>> d = {}
 >>> if d:
...     print "d"
... else:
...     print "woops"
...
woops
 >>> d is None
False

>      This works. 

No. There's still a bug, but more subtle. Guess what will happen if you 
try something like :

 >>> b1 = Bar()
 >>> b2 = Bar(b1.foo_d)
 >>> b1.foo_d['foo1'] = Foo()
 >>> b2.foo_d['foo1']


> 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"

(snip)

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

For mutable default args, testing against None is the default pythonic 
way. Now there are a lot of tricks when it comes to dynamically doing 
thing with Python, and you will probably discover'em sooner or later.

The python cookbook at o'reilly.com may be a good starting point, even 
if some recipes are somewhat out of date.

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

You're welcome.



More information about the Python-list mailing list