>>> 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
Hey, shouldn't this raise an exception?
--
Gustavo Niemeyer
[ 2AAC 7928 0FBF 0299 5EB5 60E2 2253 B29A 6664 3A0C ]
>> >>> 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.
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
> > >>> 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
>>> >>> 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
> 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?
> > 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.
> Stupid question: Is object 135321812 destroyed if I now run
>
> y += (4, 5, 6)
If that removes the last reference to it, yes.
> 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 = []
...
> 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.
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
Good point. I was wrongly interpreting it like [].extend.
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...
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()
[]
>>>
>"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
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
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
> 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.
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
>
> 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/
> 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
Should:
x = 1
x += 1
raise an exception?
Neil