initializing with empty list as default causes freaky problems

R

Reckoner

Hi,

Observe the following:

In [202]: class Foo():
.....: def __init__(self,h=[]):
.....: self.h=h
.....:
.....:

In [203]: f=Foo()

In [204]: g=Foo()

In [205]: g.h
Out[205]: []

In [206]: f.h
Out[206]: []

In [207]: f.h.append(10)

In [208]: f.h
Out[208]: [10]

In [209]: g.h
Out[209]: [10]

The question is: why is g.h updated when I append to f.h? Shouldn't
g.h stay []?

What am I missing here?

I'm using Python 2.5.1.

Thanks in advance.
 
M

MRAB

Reckoner said:
Hi,
X-Antispam: NO; Spamcatcher 5.2.1. Score 50

Observe the following:

In [202]: class Foo():
.....: def __init__(self,h=[]):
.....: self.h=h
.....:
.....:

In [203]: f=Foo()

In [204]: g=Foo()

In [205]: g.h
Out[205]: []

In [206]: f.h
Out[206]: []

In [207]: f.h.append(10)

In [208]: f.h
Out[208]: [10]

In [209]: g.h
Out[209]: [10]

The question is: why is g.h updated when I append to f.h? Shouldn't
g.h stay []?

What am I missing here?

I'm using Python 2.5.1.
Default arguments are evaluated once and then shared, so don't use them
with mutable objects like lists. Do this instead:

class Foo():
def __init__(self, h=None):
if h is None:
self.h = []
else:
self.h = h
 
L

Luis Alberto Zarrabeitia Gomez

Quoting Reckoner said:
Hi,

Observe the following:

In [202]: class Foo():
.....: def __init__(self,h=[]):
.....: self.h=h [...]
In [207]: f.h.append(10)

In [208]: f.h
Out[208]: [10]

In [209]: g.h
Out[209]: [10]

The question is: why is g.h updated when I append to f.h? Shouldn't
g.h stay []?

What you are seeing is basically the same that happens here:

===
In [1]: def f(l=[]):
...: l.append(1)
...: print l
...:

In [2]: f()
[1]

In [3]: f()
[1, 1]

In [4]: f()
[1, 1, 1]
===

The problem is that the default value "[]" is evaluated only once, at function
creation time, and not at invocation. Thus, every call to the function shares
the same default object. That is consistent with python's function type: the
"def" construct just creates a function object and initializes it with the code,
argument list and default values. That means that the default value are part of
the function object itself, regardless of when/if it is called:

===
In [5]: f.func_defaults
Out[5]: ([1, 1, 1],)
===

The recommended way of dealing with this case (mutable default arguments) is:

def f(l = None):
if l is None:
l = []
# the code goes here.

(in your case, your g.__init__ and f.__init__ methods share the same
Foo.__init__ function, and thus, share the same default value [])

That is a very common python gotcha, and I think it is well documented in the
standard doc (but I can't find it right now, sorry). Unfortunately, it doesn't
become intuitive until you've spent a while understanding python's execution
model (and then you suddenly realize that it just makes sense).

[And now I wonder... how other languages do it? I've spent so much time with
python that reevaluating the default argument on invocation feels clumsy, but
I'm obviously tainted now...]

Regards,
 
B

Benjamin Kaplan

Hi,

Observe the following:

In [202]: class Foo():
  .....:     def __init__(self,h=[]):
  .....:         self.h=h
  .....:
  .....:

In [203]: f=Foo()

In [204]: g=Foo()

In [205]: g.h
Out[205]: []

In [206]: f.h
Out[206]: []

In [207]: f.h.append(10)

In [208]: f.h
Out[208]: [10]

In [209]: g.h
Out[209]: [10]

The question is: why is g.h updated when I append to f.h?  Shouldn't
g.h stay []?

What am I missing here?

I'm using Python 2.5.1.

Thanks in advance.


.. It has to do with the way Python handles functions and class
methods. Basically, when the parser creates the class Foo, it also
creates all of Foo's class variables and functions. It's at this
point, and only at this point, that the default parameter is
evaluated.

When you make an instance of Foo, just wraps the function __init__ by
filling in the initial parameter. Because both f and g are really
using the Foo.__init__ function (as opposed to separate f.__init__ and
g.__init__ methods), they share the same default parameter, a list
that was created at the very beginning.

Since f.h and g.h both refer to the same list, updating f.h will also
update g.h (and the default parameter). That's why it's a bad idea to
use mutable default parameters- use either None

class Foo(object) :
def __init__(self, h=None) :
if h is None:
h = []

or, if None is an acceptable value, make a sentinel value

sentinel = object()
class Foo(object) :
def __init__(self, h = sentinel) :
if h is sentinel:
h = []
 
G

Gary Herron

Reckoner said:
Hi,

Observe the following:

In [202]: class Foo():
.....: def __init__(self,h=[]):
.....: self.h=h
.....:
.....:

In [203]: f=Foo()

In [204]: g=Foo()

In [205]: g.h
Out[205]: []

In [206]: f.h
Out[206]: []

In [207]: f.h.append(10)

In [208]: f.h
Out[208]: [10]

In [209]: g.h
Out[209]: [10]

The question is: why is g.h updated when I append to f.h? Shouldn't
g.h stay []?

What am I missing here?

I'm using Python 2.5.1.

Thanks in advance.

This is a FAQ (and *very* common newbie trap)! See:


http://www.python.org/doc/faq/general/#why-are-default-values-shared-between-objects

Gary Herron
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top