class constructors: class vs. instance defaults
bruno modulix
onurb at xiludom.gro
Sat Oct 9 09:55:34 EDT 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