Foo as follows.
>>> class Foo( ):
... def __init__(self, stuff = [ ]):
... self.stuff = stuff
...
Then instantiate two instances of
Foo and append a number to f1 and f2.
>>> f1 = Foo( )
>>> f2 = Foo( )
>>> f1.stuff.append(17)
>>> f2.stuff.append(18)
So, what do
>>> f1.stuff
and
>>> f2.stuff
return?
Before we get to the answer, stop and consider that
f1 and f2 are totally separate instances Foo. Attributes that we assign to f1 and f2 are, usually, completely independent.
>>> f1.flavor = 'cherry'
>>> f2.flavor = 'lime'
>>> f1.flavor
'cherry'
>>> f2.flavor
'lime'
And so we would hope that
f1 will maintain one list of stuff while f2 will maintain another.But, perhaps surprisingly,
f1 and f2 share a single list of stuff.
>>> f1.stuff
[17, 18]
>>> f2.stuff
[17, 18]
What's going on here?
The answer has to do with order of evaluation in Python. And also with the difference between class definition time and instance instantiation time.
Immediately following our definition of
Foo ...
>>> class Foo( ):
... def __init__(self, stuff = [ ]):
... self.stuff = stuff
...
... the Python interpreter defines
Foo. And it is at precisely this moment of class definition that Python first evaluates and then stores default values for all class methods.This means that
stuff to an empty list and then that __init__ stores a reference to that list.Later -- after class definition -- we instantiate first one and then another instance of
Foo. At these moments of instance instantiation, Python calls Foo's initializer ... but refuses to reevaluate default arguments in __init__.What this means is that Python evaluates default arguments once and only once at least under normal conditions. And the consequence here is that
f1 and f2 wind up, frustratingly, sharing a reference to the same list of stuff.One good solution substitutes
stuff = None for stuff = [ ].
>>> class Foo( ):
... def __init__(self, stuff = None):
... if stuff is None:
... stuff = [ ]
... self.stuff = stuff
And this highlights a principle that's may not really be a best practice in Python but might as well ought to be: set function defaults to immutable types, never to mutables. Lists, of course, are mutable, which helps explain why
stuff = [ ] gets us into trouble. None is immutable and works great.For a follow-up bit of exotica, stop and ask yourself where it is, exactly, that our first,
list = [ ] definition of Foo stores its reference to its single list of stuff.Turns out the the answer is here ...
>>> Foo.__init__.im_func.func_defaults
... buried down a couple of layers, but still open for inspection.
No comments:
Post a Comment