Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

pitfall for your amusement

0 views
Skip to first unread message

Andrew Koenig

unread,
Nov 12, 2002, 12:39:27 PM11/12/02
to
This note is mostly for entertainment purposes.

>>> x = (1, 2, 3)
>>> y = x

>>> x1 = [1, 2, 3]
>>> y1 = x1

>>> x += (4, 5, 6)
>>> x1 += [4, 5, 6]

>>> print y
(1, 2, 3)
>>> print y1
[1, 2, 3, 4, 5, 6]

Yes, I understand why this happens.
No, I'm not proposing to change it.

I do think that people teaching Python should
be sure that their students understand this example.

You may know that I've written fairly extensively
about pitfalls in C and C++; finding examples such
as this one is part of how I understand the essence
of a language.

--
Andrew Koenig, a...@research.att.com, http://www.research.att.com/info/ark

Gustavo Niemeyer

unread,
Nov 12, 2002, 1:49:33 PM11/12/02
to
> This note is mostly for entertainment purposes.
>
> >>> x = (1, 2, 3)
[...]

> >>> x += (4, 5, 6)

Hey, shouldn't this raise an exception?

--
Gustavo Niemeyer

[ 2AAC 7928 0FBF 0299 5EB5 60E2 2253 B29A 6664 3A0C ]

Andrew Koenig

unread,
Nov 12, 2002, 1:52:41 PM11/12/02
to
>> >>> x = (1, 2, 3)

>> >>> x += (4, 5, 6)

Gustavo> Hey, shouldn't this raise an exception?

Perhaps it should, but it doesn't: If x is a tuple,

x += y

is equivalent to

x = x + y

which creates a brand-new object and (re)binds x to it.

Russell E. Owen

unread,
Nov 12, 2002, 2:19:21 PM11/12/02
to
In my own learning of Python I found a fair number of warnings about the
problem you mentioned, so I feel it is pretty well documented. On the
other hand, it was a familiar problem from Smalltalk and I may just have
gotten lucky in my reading.

If you are collecting pitfalls, here's my least favorite: "never use a
mutable object as a default value". A classic example:

def badfunc(alist=[]):
...

The default value of alist will not stay [] (an empty list) but instead
is affected by whatever you pass in for "alist". Very tricky and
unpleasant.

Here's my usual solution:
def okfunc(alist=None):
alist = alist or []
...

-- Russell

Martin v. Loewis

unread,
Nov 12, 2002, 3:39:52 PM11/12/02
to
Gustavo Niemeyer <niem...@conectiva.com> writes:

> > >>> x = (1, 2, 3)
> [...]
> > >>> x += (4, 5, 6)
>
> Hey, shouldn't this raise an exception?

Because the tuple is immutable? Then would you expect

x += 1

to raise an exception also?

Regards,
Martin

Bryan Richard

unread,
Nov 12, 2002, 3:21:51 PM11/12/02
to
Andrew Koenig wrote:

>>> >>> x = (1, 2, 3)
>
>>> >>> x += (4, 5, 6)
>
> Gustavo> Hey, shouldn't this raise an exception?
>
> Perhaps it should, but it doesn't: If x is a tuple,
>
> x += y
>
> is equivalent to
>
> x = x + y
>
> which creates a brand-new object and (re)binds x to it.
>

I had never heard of this pitfall, so I had to run it.

>>> id(x)
135374140
>>> id(y)
135321812
>>> id (x1)
135373676
>>> id(y1)
135373676

Stupid question: Is object 135321812 destroyed if I now run

y += (4, 5, 6)

- Bryan

Dave Brueck

unread,
Nov 12, 2002, 4:55:00 PM11/12/02
to
On Tue, 12 Nov 2002, Russell E. Owen wrote:

> In my own learning of Python I found a fair number of warnings about the
> problem you mentioned, so I feel it is pretty well documented. On the
> other hand, it was a familiar problem from Smalltalk and I may just have
> gotten lucky in my reading.
>
> If you are collecting pitfalls, here's my least favorite: "never use a
> mutable object as a default value". A classic example:
>
> def badfunc(alist=[]):
> ...
>
> The default value of alist will not stay [] (an empty list) but instead
> is affected by whatever you pass in for "alist". Very tricky and
> unpleasant.

Anyone know off the top of their head if PyChecker warns you about this?


Erik Max Francis

unread,
Nov 12, 2002, 4:27:12 PM11/12/02
to
Gustavo Niemeyer wrote:

> > This note is mostly for entertainment purposes.
> >
> > >>> x = (1, 2, 3)
> [...]
> > >>> x += (4, 5, 6)
>
> Hey, shouldn't this raise an exception?

No, not anymore than

x = 1
x += 1

should. += (and the other assignment combination operators) will use
the same object when it's mutable, but actually does a rebinding when
immutable. For instance,

L = [1, 2, 3]
L += [4, 5, 6]

is the equivalent of

L = [1, 2, 3]
L.extend([4, 5, 6])

but

x = 1
x += 1

actually does the equivalent of

x = 1
x = x + 1

Since tuples are immutable, using the += operator on them does the
latter (resulting an a rebinding), not the former.

--
Erik Max Francis / m...@alcyone.com / http://www.alcyone.com/max/
__ San Jose, CA, USA / 37 20 N 121 53 W / &tSftDotIotE
/ \ If a thing is worth doing, then it is worth doing badly.
\__/ G.K. Chesterton
ZOE / http://www.alcyone.com/pyos/zoe/
A simple Python OpenGL rendering engine.

Erik Max Francis

unread,
Nov 12, 2002, 4:28:25 PM11/12/02
to
Bryan Richard wrote:

> Stupid question: Is object 135321812 destroyed if I now run
>
> y += (4, 5, 6)

If that removes the last reference to it, yes.

Erik Max Francis

unread,
Nov 12, 2002, 4:30:02 PM11/12/02
to
"Russell E. Owen" wrote:

> The default value of alist will not stay [] (an empty list) but
> instead
> is affected by whatever you pass in for "alist". Very tricky and
> unpleasant.
>
> Here's my usual solution:
> def okfunc(alist=None):
> alist = alist or []

If you're using None as a sentinel value, it's probably better to test
for None-ness (via alist is None) rather than test for truth, since many
things can be false which are not None.

It's unlikely that in this particular example it would pose a problem,
but in the more general case it could. The idiom _I_ use is:

def f(l=None):
if l is None:
l = []
...

Erik Max Francis

unread,
Nov 12, 2002, 4:30:55 PM11/12/02
to
Dave Brueck wrote:

> Anyone know off the top of their head if PyChecker warns you about
> this?

Just checked with 0.8.11 and the standard settings, and it doesn't emit
a warning.

Brian Quinlan

unread,
Nov 12, 2002, 4:47:42 PM11/12/02
to
I don't like the augmented operations because, depending on the object,
the operation may or may not be done in-place.

I think that it is better to be explicit and use assignment or method
call. That way you know what you are getting.

Cheers,
Brian


Gustavo Niemeyer

unread,
Nov 12, 2002, 4:07:18 PM11/12/02
to
> > > >>> x = (1, 2, 3)
> > [...]
> > > >>> x += (4, 5, 6)
> >
> > Hey, shouldn't this raise an exception?
>
> Because the tuple is immutable? Then would you expect
>
> x += 1
>
> to raise an exception also?

Good point. I was wrongly interpreting it like [].extend.

Grant Edwards

unread,
Nov 12, 2002, 5:14:43 PM11/12/02
to

If it matters which you get, then that's probably a good idea.

I use it quite a bit, and I don't think I don't remember a case
where it has mattered.

--
Grant Edwards grante Yow! I will establish
at the first SHOPPING MALL in
visi.com NUTLEY, New Jersey...

Mel Wilson

unread,
Nov 12, 2002, 5:31:14 PM11/12/02
to
In article <aqrka3$1emk$1...@nntp6.u.washington.edu>,
"Russell E. Owen" <ow...@nospam.invalid> wrote:
> [ ... ]

>If you are collecting pitfalls, here's my least favorite: "never use a
>mutable object as a default value". A classic example:
>
>def badfunc(alist=[]):
> ...
>
>The default value of alist will not stay [] (an empty list) but instead
>is affected by whatever you pass in for "alist". Very tricky and
>unpleasant.

I'm not finding that. Am I misunderstanding something? See below ..

Regards. Mel.

Python 2.1.3 (#35, Apr 8 2002, 17:47:50) [MSC 32 bit (Intel)] on win32
Type "copyright", "credits" or "license" for more information.
>>> def f2(dflt=[]):
... return dflt
...
>>> f2()
[]
>>> f2(3)
3
>>> f2()
[]
>>> f2([1,2])
[1, 2]
>>> f2()
[]
>>>

Gonçalo Rodrigues

unread,
Nov 12, 2002, 6:30:23 PM11/12/02
to
On Tue, 12 Nov 2002 13:30:02 -0800, Erik Max Francis <m...@alcyone.com>
wrote:

>"Russell E. Owen" wrote:
>
>> The default value of alist will not stay [] (an empty list) but
>> instead
>> is affected by whatever you pass in for "alist". Very tricky and
>> unpleasant.
>>
>> Here's my usual solution:
>> def okfunc(alist=None):
>> alist = alist or []
>
>If you're using None as a sentinel value, it's probably better to test
>for None-ness (via alist is None) rather than test for truth, since many
>things can be false which are not None.
>
>It's unlikely that in this particular example it would pose a problem,
>but in the more general case it could. The idiom _I_ use is:
>
> def f(l=None):
> if l is None:
> l = []
> ...

And when I want to use None *also* as viable input I use something like

DEFAULTARG = []

def f(l = DEFAULTARG):
if l is DEFAULTARG:
<whatever>

With my best regards,
G. Rodrigues

Terry Hancock

unread,
Nov 12, 2002, 9:52:02 PM11/12/02
to
On Tuesday 12 November 2002 12:46 pm, python-li...@python.org wrote:
> If you are collecting pitfalls, here's my least favorite: "never use a
> mutable object as a default value". A classic example:
>
> def badfunc(alist=[]):
>    ...
>
> The default value of alist will not stay [] (an empty list) but instead
> is affected by whatever you pass in for "alist". Very tricky and
> unpleasant.
>
> Here's my usual solution:
> def okfunc(alist=None):
>    alist = alist or []
>    ...

Hmm. I don't see this:

>>> def spam(eggs, ham=[]):
... print "%s and %s" % (eggs, ham)
...
>>> spam('eggs')
eggs and []
>>> spam('eggs', 'ham')
eggs and ham
>>> spam('eggs')
eggs and []
>>> spam('eggs', ham=['more ham'])
eggs and ['more ham']
>>> spam('eggs')
eggs and []
>>> spam('eggs', ham=['more ham'])

What would I have to do for this to be a problem? (as it happens, I have used
this kind of default before, so I would like to understand the pitfall!)

Cheers,
Terry

--
Terry Hancock ( hancock at anansispaceworks.com )
Anansi Spaceworks http://www.anansispaceworks.com

Bengt Richter

unread,
Nov 12, 2002, 10:07:47 PM11/12/02
to

You're not using it as a mutable. Suppose it's normally a caller's
list, that the function appends some output to, and and empty list
is supposed to be a convenience default to start a new list. Here
the problem is obvious, but with more code it can be more subtle.

>>> def f3(x, dflt=[]):
... dflt.append(x)
... return dflt
...
>>> f3('a',['something added:'])
['something added:', 'a']
>>> f3('b',['something added:'])
['something added:', 'b']
>>> f3('c')
['c']
>>> f3('d')
['c', 'd']


Regards,
Bengt Richter

Erik Max Francis

unread,
Nov 12, 2002, 10:11:58 PM11/12/02
to
Terry Hancock wrote:

> Hmm. I don't see this:
>
> >>> def spam(eggs, ham=[]):
> ... print "%s and %s" % (eggs, ham)
> ...

...


> What would I have to do for this to be a problem? (as it happens, I
> have used
> this kind of default before, so I would like to understand the
> pitfall!)

If that list is manipulated in some way, or is returned to the caller
(where it can be manipulated), then that makes the difference:

>>> def f(x, l=[]):
... l.append(x)
... print l
...
>>> f(1)
[1]
>>> f(2)
[1, 2]
>>> f(3)
[1, 2, 3]
>>> f(4)
[1, 2, 3, 4]

It's the same list object even that's probably not what you meant.

--
Erik Max Francis / m...@alcyone.com / http://www.alcyone.com/max/
__ San Jose, CA, USA / 37 20 N 121 53 W / &tSftDotIotE

/ \ I will always remember / This moment
\__/ Sade
Kepler's laws / http://www.alcyone.com/max/physics/kepler/
A proof of Kepler's laws.

Jeff Epler

unread,
Nov 12, 2002, 10:01:05 PM11/12/02
to
On Tue, Nov 12, 2002 at 06:52:02PM -0800, Terry Hancock wrote:
> What would I have to do for this to be a problem? (as it happens, I have used
> this kind of default before, so I would like to understand the pitfall!)

Mutate the list in the function, as
def stupid(i, l=[]):
l.append(i)
return l

>>> stupid(1, [])
[1]
>>> stupid(1)
[1]
>>> stupid(2, [])
[2]
>>> stupid(2)
[1, 2]

Jeff

Bernhard Herzog

unread,
Nov 13, 2002, 6:01:08 AM11/13/02
to
Erik Max Francis <m...@alcyone.com> writes:

>
> L = [1, 2, 3]
> L += [4, 5, 6]
>
> is the equivalent of
>
> L = [1, 2, 3]
> L.extend([4, 5, 6])

Not quite. It's more like

L = [1, 2, 3]

L = L.__iadd__([4, 5, 6])

The rebinding as always done. So += can sometimes fail for a list:

>>> t = ([1,2,3],)
>>> t[0] += [4, 5, 6]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment

Of course, the list already has been modified in place:
>>> t
([1, 2, 3, 4, 5, 6],)


Bernhard

--
Intevation GmbH http://intevation.de/
Sketch http://sketch.sourceforge.net/
MapIt! http://www.mapit.de/

Michael Hudson

unread,
Nov 13, 2002, 6:07:09 AM11/13/02
to
Brian Quinlan <br...@sweetapp.com> writes:

> I don't like the augmented operations because, depending on the object,
> the operation may or may not be done in-place.

I can't actually think of the last time I used augmented assignment on
something that wasn't an integer (or maybe a number).

> I think that it is better to be explicit and use assignment or method
> call. That way you know what you are getting.

Certainly I'd write

alist.extend(another)

over

alist += another

Seems to be an unnecessary -- maybe even unwise -- bit of polymorphism.

Cheers,
M.

--
Or here's an even simpler indicator of how much C++ sucks: Print
out the C++ Public Review Document. Have someone hold it about
three feet above your head and then drop it. Thus you will be
enlightened. -- Thant Tessman

Neil Schemenauer

unread,
Nov 13, 2002, 8:52:13 AM11/13/02
to
Gustavo Niemeyer wrote:
> > This note is mostly for entertainment purposes.
> >
> > >>> x = (1, 2, 3)
> [...]

> > >>> x += (4, 5, 6)
>
> Hey, shouldn't this raise an exception?

Should:

x = 1
x += 1

raise an exception?

Neil

0 new messages