Re: Tuples and immutability

2014-03-12 Thread Steven D'Aprano
On Tue, 11 Mar 2014 23:25:19 -0400, Terry Reedy wrote:

 On 3/11/2014 10:01 PM, Rick Johnson wrote:

 On Thursday, February 27, 2014 4:18:01 PM UTC-6, Ian wrote:
 x += y is meant to be equivalent, except possibly in-place and more
 efficient, than x = x + y.
 
 The manual actually says An augmented assignment expression like x += 1
 can be rewritten as x = x + 1 to achieve a similar, but not exactly
 equal effect. In the augmented version, x is only evaluated once. Also,
 when possible, the actual operation is performed in-place, meaning that
 rather than creating a new object and assigning that to the target, the
 old object is modified instead.
 
 
 In an ideal world, the speed of these two codes should be the same,
 
 Nope, 'similar' is not 'equivalent'. Evaluating x twice instead of once
 and possibly allocating a new object versus not take extra time. In a
 statement like x.y.z[3*n+m] += 1, calculating the target dominates the
 time to increment, so this form should be nearly twice as fast.

Excellent point Terry!

I always forget that the target of an augmented assignment may not be a 
simple name like x but can be an arbitrary complex reference, anything 
that is a legal assignment target. Because += is documented as only 
evaluating the expression once it can behave quite differently to the 
`spam = spam + 1` case. Evaluating the right hand side may have side-
effects that change what the left hand side evaluates to. This is not the 
case with the augmented assignment.

-- 
Steven
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Ethan Furman

On 03/11/2014 08:25 PM, Terry Reedy wrote:

On 3/11/2014 10:01 PM, Rick Johnson wrote:


On Thursday, February 27, 2014 4:18:01 PM UTC-6, Ian wrote:

x += y is meant to be equivalent, except possibly in-place and
more efficient, than x = x + y.


The manual actually says An augmented assignment expression like x += 1 can be 
rewritten as x = x + 1 to achieve a
similar, but not exactly equal effect. In the augmented version, x is only 
evaluated once. Also, when possible, the
actual operation is performed in-place, meaning that rather than creating a new 
object and assigning that to the target,
the old object is modified instead.


... and reassigned to the target.  :)  (If it doesn't say that last part, it 
should.)

--
~Ethan~
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Antoon Pardon
Op 12-03-14 07:28, Steven D'Aprano schreef:
 On Tue, 11 Mar 2014 23:25:19 -0400, Terry Reedy wrote:

 Nope, 'similar' is not 'equivalent'. Evaluating x twice instead of once
 and possibly allocating a new object versus not take extra time. In a
 statement like x.y.z[3*n+m] += 1, calculating the target dominates the
 time to increment, so this form should be nearly twice as fast.
 Excellent point Terry!

 I always forget that the target of an augmented assignment may not be a 
 simple name like x but can be an arbitrary complex reference, anything 
 that is a legal assignment target. Because += is documented as only 
 evaluating the expression once it can behave quite differently to the 
 `spam = spam + 1` case. Evaluating the right hand side may have side-
 effects that change what the left hand side evaluates to. This is not the 
 case with the augmented assignment.

The documentation is wrong at that point as the following code illustrates.

| import sys
| write = sys.stdout.write
|
| class logdict:
| def __init__(self):
| self.lst = {}
|
| def __setitem__(self, key, value):
| write('[%s] = %s\n' % (key, value))
| self.lst[key] = value
|
| def __getitem__(self, key):
| value = self.lst[key]
| write('[%s] = %s\n' % (key, value))
| return value
|
| tab = logdict()
| tab['key'] = 'value'
| tab['key'] += ' with extra tail'
| write('\n')
| tab = logdict()
| tab['key'] = 'value'
| tab['key'] = tab['key'] + ' with extra tail'

If you run this code, you get the following result:

| [key] = value
| [key] = value
| [key] = value with extra tail
| 
| [key] = value
| [key] = value
| [key] = value with extra tail

As you can see there is no difference here in the evaluations done
between using

| tab['key'] += ' with extra tail'

or

| tab['key'] = tab['key'] + ' with extra tail'
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Ian Kelly
On Wed, Mar 12, 2014 at 12:28 AM, Steven D'Aprano st...@pearwood.info wrote:
 On Tue, 11 Mar 2014 23:25:19 -0400, Terry Reedy wrote:
 Nope, 'similar' is not 'equivalent'. Evaluating x twice instead of once
 and possibly allocating a new object versus not take extra time. In a
 statement like x.y.z[3*n+m] += 1, calculating the target dominates the
 time to increment, so this form should be nearly twice as fast.

 Excellent point Terry!

 I always forget that the target of an augmented assignment may not be a
 simple name like x but can be an arbitrary complex reference, anything
 that is a legal assignment target. Because += is documented as only
 evaluating the expression once it can behave quite differently to the
 `spam = spam + 1` case. Evaluating the right hand side may have side-
 effects that change what the left hand side evaluates to. This is not the
 case with the augmented assignment.

Of course one could also do:

z = x.y.z
i = 3*n+m
z[i] = z[i] + 1

which reduces the duplicated work down to storing and loading a couple
of locals, and also prevents side-effects from affecting the LHS
evaluation.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Ian Kelly
On Wed, Mar 12, 2014 at 3:39 AM, Antoon Pardon
antoon.par...@rece.vub.ac.be wrote:
 The documentation is wrong at that point as the following code illustrates.

Either way it still has to do a getitem and a setitem, but if you have
a more nested structure then the extra getitems are not repeated.  For
example, using your logdict class:

 tab = logdict()
 tab[1] = logdict()
[1] = __main__.logdict object at 0x02A2E430
 tab[1][2] = logdict()
[1] = __main__.logdict object at 0x02A2E430
[2] = __main__.logdict object at 0x02A2EB10
 tab[1][2][3] = ['value']
[1] = __main__.logdict object at 0x02A2E430
[2] = __main__.logdict object at 0x02A2EB10
[3] = ['value']
 tab[1][2][3] += [' with extra tail']
[1] = __main__.logdict object at 0x02A2E430
[2] = __main__.logdict object at 0x02A2EB10
[3] = ['value']
[3] = ['value', ' with extra tail']

versus:

 tab[1][2][3] = ['value']
[1] = __main__.logdict object at 0x02A2E430
[2] = __main__.logdict object at 0x02A2EB10
[3] = ['value']
 tab[1][2][3] = tab[1][2][3] + [' with extra tail']
[1] = __main__.logdict object at 0x02A2E430
[2] = __main__.logdict object at 0x02A2EB10
[3] = ['value']
[1] = __main__.logdict object at 0x02A2E430
[2] = __main__.logdict object at 0x02A2EB10
[3] = ['value', ' with extra tail']

As you can see the += version does two fewer getitem calls in this case.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Oscar Benjamin
On 12 March 2014 03:25, Terry Reedy tjre...@udel.edu wrote:
 On 3/11/2014 10:01 PM, Rick Johnson wrote:


 On Thursday, February 27, 2014 4:18:01 PM UTC-6, Ian wrote:

 x += y is meant to be equivalent, except possibly in-place and
 more efficient, than x = x + y.


 The manual actually says An augmented assignment expression like x += 1 can
 be rewritten as x = x + 1 to achieve a similar, but not exactly equal
 effect. In the augmented version, x is only evaluated once. Also, when
 possible, the actual operation is performed in-place, meaning that rather
 than creating a new object and assigning that to the target, the old object
 is modified instead.



 In an ideal world, the speed of these two codes should be the same,


 Nope, 'similar' is not 'equivalent'. Evaluating x twice instead of once and
 possibly allocating a new object versus not take extra time. In a statement
 like x.y.z[3*n+m] += 1, calculating the target dominates the time to
 increment, so this form should be nearly twice as fast.

An example where the result is semantically different:

 from numpy import array
 a = array([1, 2, 3], dtype=int)
 a
array([1, 2, 3])
 a + 1.1
array([ 2.1,  3.1,  4.1])
 a += 1.1
 a
array([2, 3, 4])

The reason is that numpy arrays are both mutable and have statically
determined elementary data type:

 (a + 1.1).dtype
dtype('float64')
 a.dtype
dtype('int64')


Oscar
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Antoon Pardon
Op 12-03-14 10:51, Ian Kelly schreef:
 On Wed, Mar 12, 2014 at 3:39 AM, Antoon Pardon
 antoon.par...@rece.vub.ac.be wrote:
 The documentation is wrong at that point as the following code illustrates.
 Either way it still has to do a getitem and a setitem, but if you have
 a more nested structure then the extra getitems are not repeated.  For
 example, using your logdict class:

Sure, but the documentation doesn't say for sufficiently nested structures
some evaluations are not repeated.

Take the following example.

| tab['key'] = [1]
| tab['key'] += [2]

Now let us rewrite that second statment in two different ways.

| tab['key'] = tab['key'] + [2]

or

| tmp = tab['key']
| tmp += [2]

Now which of these two rewritings is closer to the augmented concatenation?
A natural reading of the documentation would conclude the second one, because
in that case we only need to evaluate tab['key'] once as righthand sided.
However it turns out the augmented concantenation is closer to the first
rewriting here, evaluating tab['key'] twice once a lefthand sided and once
as right hand sided, which IMO will surprise people that rely on the 
documentation.

-- 
Antoon Pardon

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Steven D'Aprano
On Tue, 11 Mar 2014 17:06:43 -0600, Ian Kelly wrote:

 On Tue, Mar 11, 2014 at 10:46 AM, Steven D'Aprano
 steve+comp.lang.pyt...@pearwood.info wrote:
 There are a number of possible solutions.  One possibility would be to
 copy the Circle as an Ellipse and return the new object instead of
 mutating it. Then you have the situation where, given a mutable object
 x that satisfies isinstance(x, Ellipse), the stretch method *may* be
 able to update the object in-place, or it *may* not.

 That is a really lousy design. Of course we are free to create classes
 with ugly, inconsistent, stupid or unworkable APIs if we like. Python
 won't stop us:
 
 That's true but irrelevant to my point, which was to counter the
 assertion that mutable types can always be assumed to be able to perform
 operations in-place.

Always? Not so fast.

This is Python. We have freedom to abuse nearly everything, and if you 
want to shoot yourself in the foot, you can. With the exception of a 
handful of things which cannot be overridden (e.g. None, numeric 
literals, syntax) you cannot strictly assume anything about anything. 
Python does not enforce that iterators raise StopIteration when empty, or 
that indexing beyond the boundaries of a sequence raises IndexError, or 
that __setitem__ of a mapping sets the key and value, or that __len__ 
returns a length.

Augmented assignment is no different. The docs describe the intention of 
the designers and the behaviour of the classes that they control, so with 
standard built-in classes like int, str, list, tuple etc. you can safely 
assume that mutable types will perform the operation in place and 
immutable types won't, but with arbitrary types from some arbitrarily 
eccentric or twisted programmer, who knows what it will do?


-- 
Steven D'Aprano
http://import-that.dreamwidth.org/
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Ian Kelly
On Wed, Mar 12, 2014 at 5:20 PM, Steven D'Aprano
steve+comp.lang.pyt...@pearwood.info wrote:
 On Tue, 11 Mar 2014 17:06:43 -0600, Ian Kelly wrote:

 That's true but irrelevant to my point, which was to counter the
 assertion that mutable types can always be assumed to be able to perform
 operations in-place.

 Always? Not so fast.

 This is Python. We have freedom to abuse nearly everything, and if you
 want to shoot yourself in the foot, you can. With the exception of a
 handful of things which cannot be overridden (e.g. None, numeric
 literals, syntax) you cannot strictly assume anything about anything.
 Python does not enforce that iterators raise StopIteration when empty, or
 that indexing beyond the boundaries of a sequence raises IndexError, or
 that __setitem__ of a mapping sets the key and value, or that __len__
 returns a length.

Thank you; you've stated my point more succinctly than I did.

 Augmented assignment is no different. The docs describe the intention of
 the designers and the behaviour of the classes that they control, so with
 standard built-in classes like int, str, list, tuple etc. you can safely
 assume that mutable types will perform the operation in place and
 immutable types won't, but with arbitrary types from some arbitrarily
 eccentric or twisted programmer, who knows what it will do?

This got me curious about how consistent the standard library is about
this exactly, so I did some grepping.  In the standard library there
are 5 mutable types that support concatenation that I was able to
find: list, deque, array, bytearray, and Counter.  There are none that
support addition, which I find interesting in that the language
provides hooks for in-place addition but never uses them itself.

All of the classes above appear to follow the rule that if you can
concatenate an operand, you can in-place concatenate the same operand.
 The converse however does not hold:  list.__iadd__ and
Counter.__iadd__ are both more permissive in what types they will
accept than their __add__ counterparts, and especially interesting to
me is that deque implements __iadd__ but does not implement __add__ at
all.  This last in particular seems to support the assertion that +=
should be viewed more as a shorthand for an in-place operation, less
as an equivalent for x = x + y.

 l = [1,2,3]
 l + (4,5,6)
Traceback (most recent call last):
  File stdin, line 1, in module
TypeError: can only concatenate list (not tuple) to list
 l += (4,5,6)
 l
[1, 2, 3, 4, 5, 6]

 c = collections.Counter('mississippi')
 c + collections.Counter('alabama')
Counter({'s': 4, 'a': 4, 'i': 4, 'p': 2, 'm': 2, 'b': 1, 'l': 1})
 c + dict({'a': 4, 'l': 1, 'b': 1, 'm': 1})
Traceback (most recent call last):
  File stdin, line 1, in module
TypeError: unsupported operand type(s) for +: 'Counter' and 'dict'
 c += dict({'a': 4, 'l': 1, 'b': 1, 'm': 1})
 c
Counter({'s': 4, 'a': 4, 'i': 4, 'p': 2, 'm': 2, 'b': 1, 'l': 1})

 d = collections.deque([1,2,3])
 d += [4,5,6]
 d
deque([1, 2, 3, 4, 5, 6])
 d + [7,8,9]
Traceback (most recent call last):
  File stdin, line 1, in module
TypeError: unsupported operand type(s) for +: 'collections.deque' and 'list'
 d.__add__
Traceback (most recent call last):
  File stdin, line 1, in module
AttributeError: 'collections.deque' object has no attribute '__add__'
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-12 Thread Terry Reedy

On 3/12/2014 9:35 PM, Ian Kelly wrote:

On Wed, Mar 12, 2014 at 5:20 PM, Steven D'Aprano
steve+comp.lang.pyt...@pearwood.info wrote:

On Tue, 11 Mar 2014 17:06:43 -0600, Ian Kelly wrote:


That's true but irrelevant to my point, which was to counter the
assertion that mutable types can always be assumed to be able to perform
operations in-place.


Always? Not so fast.

This is Python. We have freedom to abuse nearly everything, and if you
want to shoot yourself in the foot, you can. With the exception of a
handful of things which cannot be overridden (e.g. None, numeric
literals, syntax) you cannot strictly assume anything about anything.
Python does not enforce that iterators raise StopIteration when empty, or
that indexing beyond the boundaries of a sequence raises IndexError, or
that __setitem__ of a mapping sets the key and value, or that __len__
returns a length.


Thank you; you've stated my point more succinctly than I did.


Augmented assignment is no different. The docs describe the intention of
the designers and the behaviour of the classes that they control, so with
standard built-in classes like int, str, list, tuple etc. you can safely
assume that mutable types will perform the operation in place and
immutable types won't, but with arbitrary types from some arbitrarily
eccentric or twisted programmer, who knows what it will do?


This got me curious about how consistent the standard library is about
this exactly, so I did some grepping.  In the standard library there
are 5 mutable types that support concatenation that I was able to
find: list, deque, array, bytearray, and Counter.  There are none that
support addition, which I find interesting in that the language
provides hooks for in-place addition but never uses them itself.

All of the classes above appear to follow the rule that if you can
concatenate an operand, you can in-place concatenate the same operand.
  The converse however does not hold:  list.__iadd__ and
Counter.__iadd__ are both more permissive in what types they will
accept than their __add__ counterparts, and especially interesting to
me is that deque implements __iadd__ but does not implement __add__ at
all.  This last in particular seems to support the assertion that +=
should be viewed more as a shorthand for an in-place operation, less
as an equivalent for x = x + y.


l = [1,2,3]
l + (4,5,6)

Traceback (most recent call last):
   File stdin, line 1, in module
TypeError: can only concatenate list (not tuple) to list

l += (4,5,6)
l

[1, 2, 3, 4, 5, 6]


Like it or not, one should actually think of 'somelist += iterable' as 
equivalent to 'somelist.extend(iterable)'.  Without looking at the C 
code, I  suspect that the latter is the internal implementation. 
Collections.deque also has .extend. Collections.Counter has .update and 
that is += seems to be doing.




c = collections.Counter('mississippi')
c + collections.Counter('alabama')

Counter({'s': 4, 'a': 4, 'i': 4, 'p': 2, 'm': 2, 'b': 1, 'l': 1})

c + dict({'a': 4, 'l': 1, 'b': 1, 'm': 1})

Traceback (most recent call last):
   File stdin, line 1, in module
TypeError: unsupported operand type(s) for +: 'Counter' and 'dict'

c += dict({'a': 4, 'l': 1, 'b': 1, 'm': 1})
c

Counter({'s': 4, 'a': 4, 'i': 4, 'p': 2, 'm': 2, 'b': 1, 'l': 1})


d = collections.deque([1,2,3])
d += [4,5,6]
d

deque([1, 2, 3, 4, 5, 6])

d + [7,8,9]

Traceback (most recent call last):
   File stdin, line 1, in module
TypeError: unsupported operand type(s) for +: 'collections.deque' and 'list'

d.__add__

Traceback (most recent call last):
   File stdin, line 1, in module
AttributeError: 'collections.deque' object has no attribute '__add__'




--
Terry Jan Reedy

--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-11 Thread Ian Kelly
On Mon, Mar 10, 2014 at 11:03 PM, Gregory Ewing
greg.ew...@canterbury.ac.nz wrote:
 As far as observable effects are concerned, it's
 quite clear: mutable objects can *always* be updated
 in-place, and immutable objects can *never* be.

Hm. Consider the circle-ellipse problem.  Briefly, a circle is-an
ellipse, so in an inheritance hierarchy it is natural to make Circle a
subclass of Ellipse.  Now suppose the Ellipse has a stretch method
that mutates the ellipse by changing the length of one of its axes
while preserving the other.  To avoid violating LSP, the Circle class
must support all the methods of its ancestor.  However it cannot,
because the stretch method would invalidate the invariant of the
Circle class that both of its axes must always be equal.

There are a number of possible solutions.  One possibility would be to
copy the Circle as an Ellipse and return the new object instead of
mutating it.  Then you have the situation where, given a mutable
object x that satisfies isinstance(x, Ellipse), the stretch method
*may* be able to update the object in-place, or it *may* not.

I can't think of a reasonable example that would replace the stretch
method here with an augmented assignment, but then it is rather late.

 It might be the obvious way for that particular operation on
 that particular type. But what about all the others?
 What's the obvious way to spell in-place set intersection,
 for example? (Quickly -- no peeking at the docs!)

You mean set.intersection_update?  The in-place set methods are not
hard to remember, because they all end in _update.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-11 Thread Steven D'Aprano
On Tue, 11 Mar 2014 04:39:39 -0600, Ian Kelly wrote:

 On Mon, Mar 10, 2014 at 11:03 PM, Gregory Ewing
 greg.ew...@canterbury.ac.nz wrote:
 As far as observable effects are concerned, it's quite clear: mutable
 objects can *always* be updated in-place, and immutable objects can
 *never* be.
 
 Hm. Consider the circle-ellipse problem.

Oh, not that old chestnut! The circle-ellipse problem demonstrates one 
limitation of OOP (specifically subtype polymorphism), as well as a 
general difficulty with hierarchical taxonomies. Mammals have hair -- 
what do you make of hairless[1] dolphins? Circle/Ellipse is not a good 
place to start from in order to critic augmented assignment in Python. 
You're starting from a place where inheritance itself is problematic, so 
naturally you're going to find problems.


 Briefly, a circle is-an ellipse, so in an inheritance hierarchy it is
 natural to make Circle a subclass of Ellipse.

Natural and wrong. It's only natural if you don't think through the 
consequences. As you go on to say:

 Now suppose the Ellipse has a stretch method that
 mutates the ellipse by changing the length of one of its axes while
 preserving the other.  To avoid violating LSP, the Circle class must
 support all the methods of its ancestor.  However it cannot, because the
 stretch method would invalidate the invariant of the Circle class that
 both of its axes must always be equal.

Right. So *Circles cannot be Ellipses*, not without violating the Liskov 
Substitution Principle. If I think that they are, I haven't thought it 
through. Nor can Ellipses be Circles. That's the problem of the Circle/
Ellipse relationship.

(Aside: the LSP is not a Law Of Physics that cannot be touched. There are 
other OOP models that don't require LSP.)

Even in the case of immutable shapes, one might not wish to inherit 
Circle from Ellipsis. Ellipses have five degrees of freedom:

2 x position
size (scale)
orientation
shape

while circles only have three:

2 x position
size

Orientation and shape are meaningless for circles! So they should not 
inherit from a class where they are meaningful: given the LSP, a subclass 
cannot be more restrictive than a superclass.


 There are a number of possible solutions.  One possibility would be to
 copy the Circle as an Ellipse and return the new object instead of
 mutating it. Then you have the situation where, given a mutable object
 x that satisfies isinstance(x, Ellipse), the stretch method *may* be
 able to update the object in-place, or it *may* not.

That is a really lousy design. Of course we are free to create classes 
with ugly, inconsistent, stupid or unworkable APIs if we like. Python 
won't stop us:

class MyList(list):
def append(self, obj):
if len(self) % 17 == 3:
return self + [obj]
super(MyList, self).append(obj)



 I can't think of a reasonable example that would replace the stretch
 method here with an augmented assignment, but then it is rather late.
 
 It might be the obvious way for that particular operation on that
 particular type. 

Um, yes? Nobody suggests that a method of type X has to be the most 
obvious way for *any operation* on *any type*. What's your point?


 But what about all the others? What's the obvious way
 to spell in-place set intersection, for example? 

I would expect it to be =, let's find out if I'm right:

py a = set(abcde)
py b = a  # save a reference to it
py c = set(cdefg)
py a = c
py a, b
({'c', 'd', 'e'}, {'c', 'd', 'e'})
py a is b
True


 (Quickly -- no peeking at the docs!)

The only reason I'd need to look at the docs is because I always forget 
whether  is intersection and | is union, or the other way around. But 
having remembered which is which, going from  to = was easy.


 You mean set.intersection_update?  The in-place set methods are not hard
 to remember, because they all end in _update.

And hard to spell.




[1] Technically they aren't *entirely* hairless. They may have a few 
hairs around the blowhole, and baby dolphins are born with whiskers which 
they soon lose. But from a functional perspective, they are hairless.


-- 
Steven D'Aprano

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-11 Thread Gregory Ewing

Steven D'Aprano wrote:

On Tue, 11 Mar 2014 04:39:39 -0600, Ian Kelly wrote:


On Mon, Mar 10, 2014 at 11:03 PM, Gregory Ewing



What's the obvious way
to spell in-place set intersection, for example? 


I would expect it to be =,


That's my point -- once you know the binary operator for
an operation, the corresponding in-place operator is
obvious. But there's no established standard set of
method names for in-place operations -- each type
does its own thing.


You mean set.intersection_update?  The in-place set methods are not hard
to remember, because they all end in _update.


For that particular type, maybe, but I wouldn't trust
that rule to extend to other types.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-11 Thread Chris Angelico
On Wed, Mar 12, 2014 at 1:01 PM, Rick Johnson
rantingrickjohn...@gmail.com wrote:
 On Thursday, February 27, 2014 4:18:01 PM UTC-6, Ian wrote:
 x += y is meant to be equivalent, except possibly in-place and more
 efficient, than x = x + y.

 In an ideal world, the speed of these two codes should be the same, of course 
 i'm assuming that most competent language designers would optimise the 
 slower version.


Except that they don't mean the same thing, so it's not an optimization target.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-11 Thread Ian Kelly
On Tue, Mar 11, 2014 at 10:46 AM, Steven D'Aprano
steve+comp.lang.pyt...@pearwood.info wrote:
 There are a number of possible solutions.  One possibility would be to
 copy the Circle as an Ellipse and return the new object instead of
 mutating it. Then you have the situation where, given a mutable object
 x that satisfies isinstance(x, Ellipse), the stretch method *may* be
 able to update the object in-place, or it *may* not.

 That is a really lousy design. Of course we are free to create classes
 with ugly, inconsistent, stupid or unworkable APIs if we like. Python
 won't stop us:

That's true but irrelevant to my point, which was to counter the
assertion that mutable types can always be assumed to be able to
perform operations in-place.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-11 Thread Rick Johnson

On Thursday, February 27, 2014 4:18:01 PM UTC-6, Ian wrote:
 x += y is meant to be equivalent, except possibly in-place and more
 efficient, than x = x + y.  

In an ideal world, the speed of these two codes should be the same, of course 
i'm assuming that most competent language designers would optimise the slower 
version. 

But Rick, Python is an interpreted language and does not benefit from a 
compile stage.

Even if the bytecode can't be optimized on the current run, it CAN be optimized 
by updating the .pyo file for future runs without degrading current (or future) 
runtime performance.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-11 Thread Terry Reedy

On 3/11/2014 10:01 PM, Rick Johnson wrote:


On Thursday, February 27, 2014 4:18:01 PM UTC-6, Ian wrote:

x += y is meant to be equivalent, except possibly in-place and
more efficient, than x = x + y.


The manual actually says An augmented assignment expression like x += 1 
can be rewritten as x = x + 1 to achieve a similar, but not exactly 
equal effect. In the augmented version, x is only evaluated once. Also, 
when possible, the actual operation is performed in-place, meaning that 
rather than creating a new object and assigning that to the target, the 
old object is modified instead.




In an ideal world, the speed of these two codes should be the same,


Nope, 'similar' is not 'equivalent'. Evaluating x twice instead of once 
and possibly allocating a new object versus not take extra time. In a 
statement like x.y.z[3*n+m] += 1, calculating the target dominates the 
time to increment, so this form should be nearly twice as fast.


--
Terry Jan Reedy

--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-10 Thread Ian Kelly
On Sun, Mar 9, 2014 at 8:37 PM, Steven D'Aprano
steve+comp.lang.pyt...@pearwood.info wrote:
 On Sun, 09 Mar 2014 17:42:42 -0600, Ian Kelly wrote:

 On Sun, Mar 9, 2014 at 4:03 PM, Gregory Ewing
 greg.ew...@canterbury.ac.nz wrote:

 Note that it says when possible, not if the implementation feels
 like it.

 That's quite vague, and not much stronger a guarantee than maybe. It's
 technically possible for this augmented assignment to be performed in
 place:

 x = 12
 x += 4

 But it's not done in-place, because ints are meant to be immutable.

 That's incorrect. Ints aren't merely meant to be immutable, which
 implies that's it's optional, they are defined by the language
 specification and the reference implementation as immutable. Any
 interpreter where ints are mutable *is not Python*.

That's true, but is beside the point, which is that when possible is
not very meaningful.

 In any case, this means that whether the operation is actually performed
 in-place is an implementation detail -- if not of the Python
 implementation then at least of the class -- and not something the user
 should take for granted.

 Whether += operates in place or not is part of the interface of the
 class, not the implementation.

 Would you say that whether list.append operates in place or creates a new
 list is an implementation detail? Whether str.upper() creates a new
 string or modifies the existing one in place?

Of course not.  list.append is documented as modifying the list.
str.upper is documented as returning a copy of the string.

 Mutability versus
 immutability is part of the interface, not implementation, not
 withstanding that somebody could create an alternative class with the
 opposite behaviour: a MutableStr, or ImmutableList.

If the in-place behavior of += is held to be part of the interface,
then we must accept that += is not polymorphic across mutable and
immutable types, which in my mind largely* defeats the purpose of
having it.  After all, there should be one -- and preferably only one
-- obvious way to do it.  If you want in-place concatenation, the
obvious way to do it is by calling extend.  If you want copy
concatenation, the obvious way to do it is with the + operator.  Why
then should not just mutable sequences but immutable sequences as well
even offer the += operator?

* The one exception I can think of is numpy, where there is no more
obvious way to do in-place addition, and in that context I would
consider the in-place behavior to be part of the interface.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-10 Thread Steven D'Aprano
On Mon, 10 Mar 2014 02:35:36 -0600, Ian Kelly wrote:

 On Sun, Mar 9, 2014 at 8:37 PM, Steven D'Aprano
 steve+comp.lang.pyt...@pearwood.info wrote:
 On Sun, 09 Mar 2014 17:42:42 -0600, Ian Kelly wrote:

 On Sun, Mar 9, 2014 at 4:03 PM, Gregory Ewing
 greg.ew...@canterbury.ac.nz wrote:

 Note that it says when possible, not if the implementation feels
 like it.

 That's quite vague, and not much stronger a guarantee than maybe.
 It's technically possible for this augmented assignment to be
 performed in place:

 x = 12
 x += 4

 But it's not done in-place, because ints are meant to be immutable.

 That's incorrect. Ints aren't merely meant to be immutable, which
 implies that's it's optional, they are defined by the language
 specification and the reference implementation as immutable. Any
 interpreter where ints are mutable *is not Python*.
 
 That's true, but is beside the point, which is that when possible is
 not very meaningful.

It's meaningful. It refers not to ints, but the infinite number of 
possible classes which might include augmented assignment. Some of them 
will be immutable, in which case it is not possible for += etc. to be 
performed in-place. Some of them will be mutable, but there won't be any 
reasonable (or even unreasonable) way to perform += in-place.

But for some mutable classes, it will be possible to perform += in place, 
in which case the docs say that they have to do so.


 In any case, this means that whether the operation is actually
 performed in-place is an implementation detail -- if not of the Python
 implementation then at least of the class -- and not something the
 user should take for granted.

 Whether += operates in place or not is part of the interface of the
 class, not the implementation.

 Would you say that whether list.append operates in place or creates a
 new list is an implementation detail? Whether str.upper() creates a new
 string or modifies the existing one in place?
 
 Of course not.  list.append is documented as modifying the list.
 str.upper is documented as returning a copy of the string.

Right. And += is documented as modifying the list too.


 Mutability versus
 immutability is part of the interface, not implementation, not
 withstanding that somebody could create an alternative class with the
 opposite behaviour: a MutableStr, or ImmutableList.
 
 If the in-place behavior of += is held to be part of the interface, then
 we must accept that += is not polymorphic across mutable and immutable
 types, 

I'm fine with that.


 which in my mind largely* defeats the purpose of having it. 

Not to my mind. I think that having augmented assignment is worthwhile 
even if it behaves differently and incompatibly for different classes. 
After all, so does * (multiplication):

py x = 24
py x*x
576

but: 

py x = []
py x*x
Traceback (most recent call last):
  File stdin, line 1, in module
TypeError: can't multiply sequence by non-int of type 'list'


I don't think that we ought to throw away * and I don't think we ought to 
throw away *= either.


 After all, there should be one -- and preferably only one -- obvious way
 to do it.  If you want in-place concatenation, the obvious way to do it
 is by calling extend.  If you want copy concatenation, the obvious way
 to do it is with the + operator.  Why then should not just mutable
 sequences but immutable sequences as well even offer the += operator?

*shrug*

I can see that each individual operation:

list.extend
list +
list += 

makes good sense in isolation, but I can also see that the combination 
don't quite gel together as smoothly as we might hope.



-- 
Steven D'Aprano
http://import-that.dreamwidth.org/
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-10 Thread Gregory Ewing

Ian Kelly wrote:

It's technically possible for this augmented assignment to be
performed in place:

x = 12
x += 4

But it's not done in-place, because ints are meant to be immutable.


Which means it's *not* possible, because doing so
would violate the documented properties of the int
type.


In any case, this means that whether the operation is actually
performed in-place is an implementation detail


The implementation could theoretically perform this
optimisation if there are no other references to the
object. But this will be completely invisible, because
to even find out whether it's the same object, you need
to keep another reference to the original object,
preventing the optimisation from being performed.

As far as observable effects are concerned, it's
quite clear: mutable objects can *always* be updated
in-place, and immutable objects can *never* be.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-10 Thread Gregory Ewing

Ian Kelly wrote:

If the in-place behavior of += is held to be part of the interface,
then we must accept that += is not polymorphic across mutable and
immutable types,


That's quite correct, it's not. As I said, it's one
notation doing double duty.

Usually there isn't any confusion, because you know
whether any particular instance of it is intended to
operate on a mutable or immutable type.

If that's not the case -- if you're writing a function
intended to operate on a variety of types, some
mutable and some not -- then using in-place operators
would not be appropriate.


If you want in-place concatenation, the
obvious way to do it is by calling extend.


It might be the obvious way for that particular operation on
that particular type. But what about all the others?
What's the obvious way to spell in-place set intersection,
for example? (Quickly -- no peeking at the docs!)

The in-place operators provide a standardised spelling
for in-place versions of all the binary operations.
That's a useful thing to have, I think.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Marko Rauhamaa
Ian Kelly ian.g.ke...@gmail.com:

 In my view the second one is wrong. a += b should be understood as
 being equivalent to a = a + b, but with the *possible* and by no means
 guaranteed optimization that the operation may be performed in-place.

Some call it an optimization, others call it a side effect.

Anyway, that's how it has been explicitly defined in the language
specification so that's the reality whether one likes it or not.


Marko
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Joshua Landau
On 28 February 2014 14:43, Chris Angelico ros...@gmail.com wrote:
 On Sat, Mar 1, 2014 at 1:41 AM, Joshua Landau jos...@landau.ws wrote:
 Would it be better to add a check here, such that if this gets raised
 to the top-level it includes a warning (Addition was inplace;
 variable probably mutated despite assignment failure)?

 That'd require figuring out whether or not the variable was actually
 mutated, and that's pretty hard to work out.

It does not. First, the warning is actually an attachment to the
exception so is only shown if the exception is uncaught. This should
basically never happen in working code. The warning exists only to
remove likely misunderstanding in these odd cases.

Even if x = (1,); x[0] += 1 warned addition was inplace; possible
mutation occurred or whatever phrasing you wish, this would only
cause a quick check of the results.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Chris Angelico
On Mon, Mar 10, 2014 at 4:54 AM, Joshua Landau jos...@landau.ws wrote:
 On 28 February 2014 14:43, Chris Angelico ros...@gmail.com wrote:
 On Sat, Mar 1, 2014 at 1:41 AM, Joshua Landau jos...@landau.ws wrote:
 Would it be better to add a check here, such that if this gets raised
 to the top-level it includes a warning (Addition was inplace;
 variable probably mutated despite assignment failure)?

 That'd require figuring out whether or not the variable was actually
 mutated, and that's pretty hard to work out.

 It does not. First, the warning is actually an attachment to the
 exception so is only shown if the exception is uncaught. This should
 basically never happen in working code. The warning exists only to
 remove likely misunderstanding in these odd cases.

 Even if x = (1,); x[0] += 1 warned addition was inplace; possible
 mutation occurred or whatever phrasing you wish, this would only
 cause a quick check of the results.

I think I see what you're saying here. But ignore top-level; this
should just be a part of the exception message, no matter what.
Otherwise things that recreate the REPL (like IDLE) or that isolate
two sections of the code (like a web server framework) wouldn't get
the benefit, because the exception's not caught at the true top-level.

 x=1,
 x[0]+=0
Traceback (most recent call last):
  File pyshell#1, line 1, in module
x[0]+=0
TypeError: 'tuple' object does not support item assignment

What you're saying is that this should notice that it's doing an
augmented assignment and give some more text. This can be done; all
you need to do is catch the error and reraise it with more info:

 try:
x[0]+=0
except TypeError as e:
if 'does not support item assignment' in e.args[0]:
raise TypeError(e.args[0]+\nAugmented assignment used;
mutation may have occurred.) from None
raise

Traceback (most recent call last):
  File pyshell#16, line 5, in module
raise TypeError(e.args[0]+\nAugmented assignment used; mutation
may have occurred.) from None
TypeError: 'tuple' object does not support item assignment
Augmented assignment used; mutation may have occurred.

Now you can look at writing an import hook that does an AST transform,
locating every instance of item assignment and wrapping it like that.
It's certainly possible. I'm not sure how much benefit you'd get, but
it could be done.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Joshua Landau
On 9 March 2014 18:13, Chris Angelico ros...@gmail.com wrote:
 I think I see what you're saying here. But ignore top-level; this
 should just be a part of the exception message, no matter what.

I don't think I was clear, but yes. That.

 What you're saying is that this should notice that it's doing an
 augmented assignment and give some more text. This can be done; all
 you need to do is catch the error and reraise it with more info:
 [...]
 Now you can look at writing an import hook that does an AST transform,
 locating every instance of item assignment and wrapping it like that.
 It's certainly possible. I'm not sure how much benefit you'd get, but
 it could be done.

I would probably implement it closer to home. Inside
tuple.__getitem__, there would be something like

if context_is_augmented_assignment():
raise TypeError(message+warning)
else:
raise TypeError(message)

which would have much lower technical costs. It does depend on how
costly context_is_augmented_assignment is, though. A speed argument
is totally valid, but I'd hope it's possible to work around.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Chris Angelico
On Mon, Mar 10, 2014 at 6:57 AM, Joshua Landau jos...@landau.ws wrote:
 I would probably implement it closer to home. Inside
 tuple.__getitem__, there would be something like

 if context_is_augmented_assignment():
 raise TypeError(message+warning)
 else:
 raise TypeError(message)

 which would have much lower technical costs. It does depend on how
 costly context_is_augmented_assignment is, though. A speed argument
 is totally valid, but I'd hope it's possible to work around.

Yeah, I'm not sure there's an easy way to tell the tuple that it's an
augmented assignment. You might be able to look at the backtrace, but
that's tricky.

In theory, you could catch TypeError after any augmented assignment,
and check several things:
1) The target object does not have __setitem__
2) The object being manipulated does have __iadd__ (or corresponding for others)
3) The error is that item assignment is not possible

and if all three are the case, then add a tag to the message. But this
is best done by catching the exception. Otherwise you'd be limiting
this to tuples and lists; not to mention that this is really only an
issue of error reporting, and correct code shouldn't be tripping this
at all. So put a check like that at the place where you display the
error, if you can. The AST transform that I described would also work.

But I really think it's not worth the effort. If you get this error,
understand that it may have consequences.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Gregory Ewing

Ian Kelly wrote:

In my view the second one is wrong.  a += b should be understood as
being equivalent to a = a + b, but with the *possible* and by no means
guaranteed optimization that the operation may be performed in-place.


This interpretation is at odds with the Language Reference,
section 6.2.1, Augmented Assignment Statements:

An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to
achieve a similar, but not exactly equal effect... when possible, the actual 
operation is performed

in-place, meaning that rather than creating a new object and assigning that to
the target, the old object is modified instead.

Note that it says when possible, not if the implementation
feels like it.


In fact, if you read the documentation for lists, you may notice that
while they clearly cover the + operator and the extend method, they do
not explicitly document the list class's += operator.


The when possible clause, together with the fact that lists
are mutable, implies that it *will* be done in-place. There
is no need to document all the in-place operators explicitly
for every type.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Terry Reedy

On 3/9/2014 6:03 PM, Gregory Ewing wrote:

Ian Kelly wrote:

In my view the second one is wrong.  a += b should be understood as
being equivalent to a = a + b, but with the *possible* and by no means
guaranteed optimization that the operation may be performed in-place.


This interpretation is at odds with the Language Reference,
section 6.2.1, Augmented Assignment Statements:

An augmented assignment expression like x += 1 can be rewritten as x =
x + 1 to
achieve a similar, but not exactly equal effect... when possible, the
actual operation is performed
in-place, meaning that rather than creating a new object and assigning
that to
the target, the old object is modified instead.

Note that it says when possible, not if the implementation
feels like it.


In fact, if you read the documentation for lists, you may notice that
while they clearly cover the + operator and the extend method, they do
not explicitly document the list class's += operator.


The when possible clause, together with the fact that lists
are mutable, implies that it *will* be done in-place. There
is no need to document all the in-place operators explicitly
for every type.


The discussion of .__iop__ in the datamodel chapter was just revised 
slightly.

http://bugs.python.org/issue19953

--
Terry Jan Reedy

--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Ian Kelly
On Sun, Mar 9, 2014 at 4:03 PM, Gregory Ewing
greg.ew...@canterbury.ac.nz wrote:
 Ian Kelly wrote:

 In my view the second one is wrong.  a += b should be understood as
 being equivalent to a = a + b, but with the *possible* and by no means
 guaranteed optimization that the operation may be performed in-place.


 This interpretation is at odds with the Language Reference,
 section 6.2.1, Augmented Assignment Statements:

 An augmented assignment expression like x += 1 can be rewritten as x = x +
 1 to
 achieve a similar, but not exactly equal effect... when possible, the actual
 operation is performed

 in-place, meaning that rather than creating a new object and assigning that
 to
 the target, the old object is modified instead.

 Note that it says when possible, not if the implementation
 feels like it.

That's quite vague, and not much stronger a guarantee than maybe.
It's technically possible for this augmented assignment to be
performed in place:

x = 12
x += 4

But it's not done in-place, because ints are meant to be immutable.
In any case, this means that whether the operation is actually
performed in-place is an implementation detail -- if not of the Python
implementation then at least of the class -- and not something the
user should take for granted.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-09 Thread Steven D'Aprano
On Sun, 09 Mar 2014 17:42:42 -0600, Ian Kelly wrote:

 On Sun, Mar 9, 2014 at 4:03 PM, Gregory Ewing
 greg.ew...@canterbury.ac.nz wrote:

 Note that it says when possible, not if the implementation feels
 like it.
 
 That's quite vague, and not much stronger a guarantee than maybe. It's
 technically possible for this augmented assignment to be performed in
 place:
 
 x = 12
 x += 4

 But it's not done in-place, because ints are meant to be immutable. 

That's incorrect. Ints aren't merely meant to be immutable, which 
implies that's it's optional, they are defined by the language 
specification and the reference implementation as immutable. Any 
interpreter where ints are mutable *is not Python*.


 In any case, this means that whether the operation is actually performed
 in-place is an implementation detail -- if not of the Python
 implementation then at least of the class -- and not something the user
 should take for granted.

Whether += operates in place or not is part of the interface of the 
class, not the implementation.

Would you say that whether list.append operates in place or creates a new 
list is an implementation detail? Whether str.upper() creates a new 
string or modifies the existing one in place? Mutability versus 
immutability is part of the interface, not implementation, not 
withstanding that somebody could create an alternative class with the 
opposite behaviour: a MutableStr, or ImmutableList.




-- 
Steven D'Aprano
http://import-that.dreamwidth.org/
-- 
https://mail.python.org/mailman/listinfo/python-list


Balanced trees (was: Re: Tuples and immutability)

2014-03-08 Thread Marko Rauhamaa
Ian Kelly ian.g.ke...@gmail.com:

 I already mentioned this earlier in the thread, but a balanced binary
 tree might implement += as node insertion and then return a different
 object if the balancing causes the root node to change.

True.

Speaking of which, are there plans to add a balanced tree to the
batteries of Python? Timers, cache aging and the like need it. I'm
using my own AVL tree implementation, but I'm wondering why Python
still doesn't have one.

In fact, since asyncio has timers but Python doesn't have balanced
trees, I'm led to wonder how good the asyncio implementation can be.

Note that Java batteries include TreeMap.


Marko
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Balanced trees (was: Re: Tuples and immutability)

2014-03-08 Thread Ian Kelly
On Sat, Mar 8, 2014 at 1:34 AM, Marko Rauhamaa ma...@pacujo.net wrote:
 Speaking of which, are there plans to add a balanced tree to the
 batteries of Python? Timers, cache aging and the like need it. I'm
 using my own AVL tree implementation, but I'm wondering why Python
 still doesn't have one.

None currently that I'm aware of.  If you want to propose adding one,
I suggest reading:

http://docs.python.org/devguide/stdlibchanges.html

 In fact, since asyncio has timers but Python doesn't have balanced
 trees, I'm led to wonder how good the asyncio implementation can be.

Peeking at the code, it appears to use a heapq-based priority queue.
Why would a balanced binary tree be better?
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Balanced trees (was: Re: Tuples and immutability)

2014-03-08 Thread Dan Stromberg
On Sat, Mar 8, 2014 at 12:34 AM, Marko Rauhamaa ma...@pacujo.net wrote:
 Ian Kelly ian.g.ke...@gmail.com:

 I already mentioned this earlier in the thread, but a balanced binary
 tree might implement += as node insertion and then return a different
 object if the balancing causes the root node to change.

 True.

 Speaking of which, are there plans to add a balanced tree to the
 batteries of Python? Timers, cache aging and the like need it. I'm
 using my own AVL tree implementation, but I'm wondering why Python
 still doesn't have one.

I think it'd probably be a good idea to add one or more balanced
binary trees to the standard library.  But I suspect it's been tried
before, and didn't happen.  It might be good to add an _un_balanced
tree too, since they do quite well with random keys.

Here's a performance comparison I did of a bunch of tree types in Python:
http://stromberg.dnsalias.org/~strombrg/python-tree-and-heap-comparison/2014-01/
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-08 Thread Gregory Ewing

Ian Kelly wrote:

class LessThanFilter:

def __init__(self, the_list):
self._the_list = the_list

def __getitem__(self, bound):
return [x for x in self._the_list if x  bound]


filter = LessThanFilter([10, 20, 30, 40, 50])
filter[25] += [15, 17, 23]

Should that last line not raise an exception?


In this case it will fail to catch what is probably an error,
but you can't expect the language to find all your bugs for
you. If you wrote the same bug this way:

   filter[25].extend([15, 17, 23])

it wouldn't be caught either.

What's happening is that we're trying to use the syntax
a += b to mean two different things:

1) Shorthand for a = a + b

2) A way of expressing an in-place modification, such
   as a.extend(b)

Case (2) is not really an assignment at all, so arguably
it shouldn't require the LHS to support assignment.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-08 Thread Gregory Ewing

Ian Kelly wrote:


I already mentioned this earlier in the thread, but a balanced binary
tree might implement += as node insertion and then return a different
object if the balancing causes the root node to change.


That would be a really bad way to design a binary tree
implementation. What if there is another reference to
the tree somewhere? It's still going to be referring to
the old root object, and will have an incoherent view
of the data -- partly old and partly new.

If you're going to have a mutable tree, it needs to be
encapsulated in a stable top-level object.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-08 Thread Ian Kelly
On Sat, Mar 8, 2014 at 5:40 PM, Gregory Ewing
greg.ew...@canterbury.ac.nz wrote:
 Ian Kelly wrote:

 class LessThanFilter:

 def __init__(self, the_list):
 self._the_list = the_list

 def __getitem__(self, bound):
 return [x for x in self._the_list if x  bound]


 filter = LessThanFilter([10, 20, 30, 40, 50])
 filter[25] += [15, 17, 23]

 Should that last line not raise an exception?


 In this case it will fail to catch what is probably an error,
 but you can't expect the language to find all your bugs for
 you. If you wrote the same bug this way:

filter[25].extend([15, 17, 23])

 it wouldn't be caught either.

 What's happening is that we're trying to use the syntax
 a += b to mean two different things:

 1) Shorthand for a = a + b

 2) A way of expressing an in-place modification, such
as a.extend(b)

 Case (2) is not really an assignment at all, so arguably
 it shouldn't require the LHS to support assignment.

In my view the second one is wrong.  a += b should be understood as
being equivalent to a = a + b, but with the *possible* and by no means
guaranteed optimization that the operation may be performed in-place.

In fact, if you read the documentation for lists, you may notice that
while they clearly cover the + operator and the extend method, they do
not explicitly document the list class's += operator.  So although I'm
not entirely sure whether it is intentional or not, and I would be
quite surprised if some implementation were actually to differ on this
point, the language does *not* from what I can see guarantee that the
+= operator on lists is equivalent to calling .extend.

That having been said, code that uses += and relies on the operation
to be performed in-place should be considered buggy.  If you need the
operation to be performed in-place, then use in-place methods like
list.extend.  If you need the operation not to be performed in-place,
then use a = a + b.  If you're ambivalent on the in-place issue and
just want to write polymorphic code, that's when you should consider
using +=.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-08 Thread Ian Kelly
On Sat, Mar 8, 2014 at 5:45 PM, Gregory Ewing
greg.ew...@canterbury.ac.nz wrote:
 Ian Kelly wrote:

 I already mentioned this earlier in the thread, but a balanced binary
 tree might implement += as node insertion and then return a different
 object if the balancing causes the root node to change.


 That would be a really bad way to design a binary tree
 implementation. What if there is another reference to
 the tree somewhere? It's still going to be referring to
 the old root object, and will have an incoherent view
 of the data -- partly old and partly new.

 If you're going to have a mutable tree, it needs to be
 encapsulated in a stable top-level object.

Well, as I parenthetically noted the first time I brought it up,
whether this is good design is tangential; it's a possible design.
The language shouldn't be written such that only those designs deemed
good by some external committee can be implemented.  What you
dismiss as really bad may be exactly what somebody else needs, and
maybe they intend that there won't be other references.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Duncan Booth
Chris Angelico ros...@gmail.com wrote:

 On Sat, Mar 1, 2014 at 1:41 AM, Joshua Landau jos...@landau.ws
 wrote: 
 Would it be better to add a check here, such that if this gets raised
 to the top-level it includes a warning (Addition was inplace;
 variable probably mutated despite assignment failure)?
 
 That'd require figuring out whether or not the variable was actually
 mutated, and that's pretty hard to work out. So there's a FAQ entry,
 which Zachary already posted:
 
 http://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-r
 aise-an-exception-when-the-addition-works 
 
 Also, we just answer this question every now and then :) Presumably
 more often on -tutor than here.
 
 ChrisA
Another take on this that I haven't seen discussed in this thread:

Is there any reason why tuples need to throw an exception on assigning to 
the element if the old value and new value are the same object?

If I say:

a = (spam, [10, 30], eggs)

then

a[0] = a[0]

won't actually mutate the object. So tuples could let that silently pass. 
Then you would be able to safely do:

a[1] += [50]

but this would still throw an exception:

a[0] += x



-- 
Duncan Booth http://kupuguy.blogspot.com
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Ben Finney
Duncan Booth duncan.booth@invalid.invalid writes:

 Is there any reason why tuples need to throw an exception on assigning
 to the element if the old value and new value are the same object?

Special cases aren't special enough to break the rules.

-- 
 \   “I do not believe in forgiveness as it is preached by the |
  `\church. We do not need the forgiveness of God, but of each |
_o__)other and of ourselves.” —Robert G. Ingersoll |
Ben Finney

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Chris Angelico
On Fri, Mar 7, 2014 at 8:33 PM, Duncan Booth
duncan.booth@invalid.invalid wrote:
 Is there any reason why tuples need to throw an exception on assigning to
 the element if the old value and new value are the same object?

It'd be easy enough to implement your own tuple subclass that behaves
that way. Try it! See how many situations it actually helps.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Peter Otten
Chris Angelico wrote:

 On Fri, Mar 7, 2014 at 8:33 PM, Duncan Booth
 duncan.booth@invalid.invalid wrote:
 Is there any reason why tuples need to throw an exception on assigning to
 the element if the old value and new value are the same object?
 
 It'd be easy enough to implement your own tuple subclass that behaves
 that way. Try it! See how many situations it actually helps.

 class T(tuple):
... def __setitem__(self, index, value):
... if value is not self[index]:
... raise TypeError({} is not {}.format(value, 
self[index]))
... 
 for i, k in zip(range(250, 260), range(250, 260)):
... T([i])[0] = k
... 
Traceback (most recent call last):
  File stdin, line 2, in module
  File stdin, line 4, in __setitem__
TypeError: 257 is not 257

I'm not sure help is the right word here ;)

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Chris Angelico
On Fri, Mar 7, 2014 at 10:38 PM, Peter Otten __pete...@web.de wrote:
 TypeError: 257 is not 257

 I'm not sure help is the right word here ;)

It doesn't help with non-small integers, yes, but the original case
was a list. Personally, I don't think there are many situations that
would benefit from it, plus it'd be confusing (I can use += with a
list but not a number, why not?!).

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Alister
On Fri, 07 Mar 2014 09:33:49 +, Duncan Booth wrote:

 Chris Angelico ros...@gmail.com wrote:
 
 On Sat, Mar 1, 2014 at 1:41 AM, Joshua Landau jos...@landau.ws wrote:
 Would it be better to add a check here, such that if this gets raised
 to the top-level it includes a warning (Addition was inplace;
 variable probably mutated despite assignment failure)?
 
 That'd require figuring out whether or not the variable was actually
 mutated, and that's pretty hard to work out. So there's a FAQ entry,
 which Zachary already posted:
 
 http://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-r
 aise-an-exception-when-the-addition-works
 
 Also, we just answer this question every now and then :) Presumably
 more often on -tutor than here.
 
 ChrisA
 Another take on this that I haven't seen discussed in this thread:
 
 Is there any reason why tuples need to throw an exception on assigning
 to the element if the old value and new value are the same object?
 
 If I say:
 
 a = (spam, [10, 30], eggs)
 
 then
 
 a[0] = a[0]
 
 won't actually mutate the object. So tuples could let that silently
 pass.
 Then you would be able to safely do:
 
 a[1] += [50]
 
 but this would still throw an exception:
 
 a[0] += x

I would think it would be better if the exception was thrown before the 
assignment to the list took place
simply seeing that a modification action was being applied to a tupple 
should be enough.
this would alert the programmer to the fact that he was trying something 
that may have undesired consequences
 



-- 
Old age is the harbor of all ills.
-- Bion
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Roy Smith
In article XnsA2E95FA1E1EB6duncanbooth@127.0.0.1,
 Duncan Booth duncan.booth@invalid.invalid wrote:

 Is there any reason why tuples need to throw an exception on assigning to 
 the element if the old value and new value are the same object?
 
 If I say:
 
 a = (spam, [10, 30], eggs)
 
 then
 
 a[0] = a[0]
 
 won't actually mutate the object. So tuples could let that silently pass. 

But, why would you want them to?  What a way to introduce bugs which are 
difficult to test for.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Ian Kelly
On Fri, Mar 7, 2014 at 4:51 AM, Alister alister.w...@ntlworld.com wrote:
 I would think it would be better if the exception was thrown before the
 assignment to the list took place
 simply seeing that a modification action was being applied to a tupple
 should be enough.
 this would alert the programmer to the fact that he was trying something
 that may have undesired consequences

Then the behavior of tuples would be inconsistent with other immutable
types.  This can't be applied generally, because the Python
interpreter doesn't generally know whether a given type is supposed to
be immutable or not.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Gregory Ewing

Duncan Booth wrote:
Is there any reason why tuples need to throw an exception on assigning to 
the element if the old value and new value are the same object?


It would make introspection misleading, because tuples
would have a __setitem__ method event though they don't
actually support item assignment.

Also, it would solve the problem for tuples in particular,
but not for any other immutable type -- they would all
have to implement the same behaviour independently to
enjoy the benefit.

Here's another idea: If the __iadd__ method returns the
same object, *and* the LHS doesn't have a __setitem__
method, then do nothing instead of raising an exception.

Peter Otten wrote:

 Traceback (most recent call last):
   File stdin, line 2, in module
   File stdin, line 4, in __setitem__
 TypeError: 257 is not 257

 I'm not sure help is the right word here ;)

I don't think that's a problem, because the use case
being addressed is where the object performs in-place
modification and always returns itself. Any object that
doesn't return itself is not modifying in-place, even
if the returned object happens to be equal to the
original one.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-07 Thread Ian Kelly
On Fri, Mar 7, 2014 at 7:17 PM, Gregory Ewing
greg.ew...@canterbury.ac.nz wrote:
 Here's another idea: If the __iadd__ method returns the
 same object, *and* the LHS doesn't have a __setitem__
 method, then do nothing instead of raising an exception.

Maybe it doesn't have a __setitem__ because the object that was
retrieved is computed rather than stored, and the result of the
__iadd__ will simply be discarded.  Somewhat contrived example:

class LessThanFilter:

def __init__(self, the_list):
self._the_list = the_list

def __getitem__(self, bound):
return [x for x in self._the_list if x  bound]


filter = LessThanFilter([10, 20, 30, 40, 50])
filter[25] += [15, 17, 23]

Should that last line not raise an exception?  The __iadd__ call will
return the same object, and the LHS doesn't have a __setitem__ method.

 I don't think that's a problem, because the use case
 being addressed is where the object performs in-place
 modification and always returns itself. Any object that
 doesn't return itself is not modifying in-place, even
 if the returned object happens to be equal to the
 original one.

I already mentioned this earlier in the thread, but a balanced binary
tree might implement += as node insertion and then return a different
object if the balancing causes the root node to change.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-02 Thread Ian Kelly
On Sat, Mar 1, 2014 at 7:04 PM, Eric Jacoboni eric.jacob...@gmail.com wrote:
 In fact, i think i'm gonna forget += on lists :)

Well, do what you want, but I think you're taking the wrong lesson
from this.  Don't forget about using += on lists.  Instead, forget
about using assignments, augmented or otherwise, on tuple elements.
Would you expect this to work?

tup = (1, 2, 3)
tup[1] += 42

I'm guessing probably not.  So I have a hard time understanding why it
should be expected to work any differently when the tuple element
happens to be a list.  The fact that the list is actually modified I
think is a distraction from the main point: the error indicates that
something illegal happened, not that nothing has changed.  Assignments
just are not allowed on tuple elements, and it doesn't matter what
type they happen to be.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-02 Thread Eric Jacoboni
Le 02/03/2014 13:32, Ian Kelly a écrit :
 On Sat, Mar 1, 2014 at 7:04 PM, Eric Jacoboni eric.jacob...@gmail.com wrote:
 In fact, i think i'm gonna forget += on lists :)
 
 Well, do what you want, but I think you're taking the wrong lesson
 from this.  Don't forget about using += on lists.  Instead, forget
 about using assignments, augmented or otherwise, on tuple elements.
 Would you expect this to work?

Well, the thing about += on lists that makes me forget it, like i said
in my previous post, is that its behaviour is not consistent with +.

Don't get me wrong: i don't expect that modifying a tuple element works.
That's exactly my point: my initial question was, why it half works :
it should not work at all. I was thinking that += on lists worked like
update() or extend() : modifying lists in place... It was my mistake

So, yes, i still don't get the point using a += operation, which is not
even consistent with the + operation (see my exemple on spam in my
previous post). The + operator to return the modified list and the
update() or extend() methods to do in place replacements are well enough
for my present needs. Maybe, in the future, i will find a use case of +=
for lists which is not covered by others methods, though... Who knows? I
don't doubt that Python designers have made this choice and this
behavior for a good reason: i've just not get it yet.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-02 Thread Mark Lawrence

On 02/03/2014 13:38, Eric Jacoboni wrote:

Le 02/03/2014 13:32, Ian Kelly a écrit :

On Sat, Mar 1, 2014 at 7:04 PM, Eric Jacoboni eric.jacob...@gmail.com wrote:

In fact, i think i'm gonna forget += on lists :)


Well, do what you want, but I think you're taking the wrong lesson
from this.  Don't forget about using += on lists.  Instead, forget
about using assignments, augmented or otherwise, on tuple elements.
Would you expect this to work?


Well, the thing about += on lists that makes me forget it, like i said
in my previous post, is that its behaviour is not consistent with +.


The behaviour is consistent except when you try to modify a tuple.



Don't get me wrong: i don't expect that modifying a tuple element works.
That's exactly my point: my initial question was, why it half works :
it should not work at all. I was thinking that += on lists worked like
update() or extend() : modifying lists in place... It was my mistake


I'd like to see you get update to work on a list.  This shows how 
confused you are over this issue.




So, yes, i still don't get the point using a += operation, which is not
even consistent with the + operation (see my exemple on spam in my
previous post). The + operator to return the modified list and the
update() or extend() methods to do in place replacements are well enough
for my present needs. Maybe, in the future, i will find a use case of +=
for lists which is not covered by others methods, though... Who knows? I
don't doubt that Python designers have made this choice and this
behavior for a good reason: i've just not get it yet.



All of this comes about because of the known issue with tuples.  Take 
them out of the equation and the behaviour of =, +=, extend and append 
is consistent.


--
My fellow Pythonistas, ask not what our language can do for you, ask 
what you can do for our language.


Mark Lawrence

---
This email is free from viruses and malware because avast! Antivirus protection 
is active.
http://www.avast.com


--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-02 Thread Eric Jacoboni
Le 02/03/2014 15:05, Mark Lawrence a écrit :

 The behaviour is consistent except when you try to modify a tuple.
 

Not in my opinion...

li = [10, 30]
li = li + spam   -- TypeError: can only concatenate list (not str)
li += spam   -- Ok

So, not, that's not what i call consistent.

And it's not related to tuples...

 I'd like to see you get update to work on a list.  This shows how
 confused you are over this issue.

Well, sorry, i wanted to write .append()


-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-02 Thread albert visser
On Sun, 02 Mar 2014 15:17:11 +0100, Eric Jacoboni  
eric.jacob...@gmail.com wrote:



Le 02/03/2014 15:05, Mark Lawrence a écrit :


The behaviour is consistent except when you try to modify a tuple.



Not in my opinion...

li = [10, 30]
li = li + spam   -- TypeError: can only concatenate list (not str)
li += spam   -- Ok



possibly because you expect += to take spam as a string, but have you  
looked at the result?


In [1]: mylist = ['1', '2']

In [2]: mylist += 'spam'

In [3]: mylist
Out[3]: ['1', '2', 's', 'p', 'a', 'm']

consequently, try adding something that can not be interpreted as a  
sequence:


In [4]: mylist += 3
---
TypeError Traceback (most recent call last)
ipython-input-4-782b544a29d1 in module()
 1 mylist += 3

TypeError: 'int' object is not iterable


--
Vriendelijke groeten / Kind regards,

Albert Visser

Using Opera's mail client: http://www.opera.com/mail/
--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Mark Lawrence

On 01/03/2014 06:16, Mark H. Harris wrote:

On Friday, February 28, 2014 11:16:18 PM UTC-6, Ian wrote:


How would you propose doing that?  Bear in mind that while Python
knows that tuples specifically are immutable, it doesn't generally
know whether a type is immutable.



hi Ian,   consider the problem... its a cake and eat it too scenario...   
you can't have your cake and eat it too

This is what I mean...   the error message is telling the user that it cannot do what he 
has requested, and yet IT DID.  You have one of two scenarios:   1) the message is 
arbitrarily lying and it really can assign an immutable's item...  (and it did!)  or 2)  
It really should NOT assign an immutables item (the message is truth) but the action was 
allowed anyway despite the protocol and accepted policy...  in which case the 
two scenarios add up to a giant logical inconsistency...  QED   a bug.

There really is no way to get around this from the average user's perspective. 
And therefore, this is going to come up again and again and again... because 
coders don't like unexpected logical inconsistencies.

It is not helpful either to explain it away by knowing how the policy works 
under the covers... the average user of the python language should not have to 
understand the policy of the underlying substructure...

1) either they can't assign the list item of an immutable tuple type (and the 
action is a flaw, say bug

of

2) they really CAN set the immutable's list item type (which it did!) and the 
message is bogus.


Its one way or the other  we can't have our cake and eat it too... this is 
(one way or the other) a bug.

IMHO



would you please be kind enough to use plain English...  your use of 
sentences like this...  is starting to annoy me...


you're also using google groups...  it doesn't wrap paragraphs 
correctly...  please read and action this 
https://wiki.python.org/moin/GoogleGroupsPython...  it does wrap 
paragraphs correctly...  it also prevents us seeing double line spacing...


do you get my drift???

--
My fellow Pythonistas, ask not what our language can do for you, ask 
what you can do for our language.


Mark Lawrence

---
This email is free from viruses and malware because avast! Antivirus protection 
is active.
http://www.avast.com


--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Ned Batchelder

On 3/1/14 12:50 AM, Mark H. Harris wrote:

On Friday, February 28, 2014 11:34:56 PM UTC-6, Ian wrote:


One very common example of tuples containing lists is when lists are
passed to any function that accepts *args, because the extra arguments
are passed in a tuple.  A similarly common example is when returning
multiple objects from a function, and one of them happens to be a
list, because again they are returned in a tuple.



 def f(*args):
 print(args)
 return (args[1:]

  result = f(1, 2, 3, [4, 5])
 (1, 2, 3, [4, 5])
  print(result)
 (2, 3, [4, 5])


I agree Ian... good points all.   ... again, I'm not arguing with anyone... 
just saying that an error (whatever we mean by that) should not 
half-way-fail   we are only pointing out the problem... we have not idea 
what the solution is yet.

Intuitively everyone can see that there is a problem here...  the debate cannot 
be answered either because of the inherent design of python (almost all of 
which we love). So, as they say, what is a mother to do?  ... I mean, some 
people's kids...

I don't know how I propose to handle the problem... I think the first step is 
getting everyone to agree that there IS a problem... then debate how to tackle 
the solution proposals.

marcus



Everyone can agree that it is not great to raise an exception and also 
leave the list modified.  But I very much doubt that anything will be 
done to change the situation.  All of the solutions are too extreme, and 
bring their own infelicities, and the actual problems caused by this 
scenario are vanishingly small.


BTW: I also am mystified why you uses ellipses to end your sentences, 
surely one period would be enough? :)


--
Ned Batchelder, http://nedbatchelder.com

--
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Oscar Benjamin
On 27 February 2014 21:47, Nick Timkovich prometheus...@gmail.com wrote:
 On Thu, Feb 27, 2014 at 10:33 AM, Chris Angelico ros...@gmail.com wrote:

 It's unintuitive, but it's a consequence of the way += is defined. If
 you don't want assignment, don't use assignment :)

 Where is `.__iadd__()` called outside of `list += X`?

Numpy uses it for in-place operations with numpy arrays:

 import numpy
 a = numpy.arange(10)
 a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
 a[::2] += 10
 a
array([10,  1, 12,  3, 14,  5, 16,  7, 18,  9])

It makes sense for any mutable type that supports +. The stdlib
doesn't have many of these. The obvious one is list but there's also
MutableString (in 2.x):

 from UserString import MutableString
 a = MutableString(qwe)
 a
'qwe'
 b = a
 b += asd
 a
'qweasd'

 If the only difference from `.extend()` is that it returns `self`, but the 
 list was
 already modified anyway, why bother with reassignment?

I don't know of any situation where __iadd__ is defined but doesn't
return self and I can't think of a use case apart from operator abuse.

I had thought that the other difference was that .extend would accept
any iterable but it seems += does also. Perhaps that was changed at
some point.

 l = [1, 2, 3]
 l + (1, 2, 3)
Traceback (most recent call last):
  File stdin, line 1, in module
TypeError: can only concatenate list (not tuple) to list
 l += (1, 2, 3)
 l
[1, 2, 3, 1, 2, 3]


Oscar
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Ian Kelly
On Fri, Feb 28, 2014 at 11:25 PM, Mark H. Harris harrismh...@gmail.com wrote:
 On Friday, February 28, 2014 11:16:18 PM UTC-6, Ian wrote:

 How would you propose doing that?  Bear in mind that while Python
 knows that tuples specifically are immutable, it doesn't generally
 know whether a type is immutable.

 I was going to bed, and then thought of the solution.  Allow the action.

 Hold the error pending until the action either succeeds or fails for the 
 item, in this case a list, and then post the error for the tuple 
 depending... this would make the whole mess consistent, and even compatible 
 with the idea of dynamic name and type binding...

 So, in other words, the action should be permitted, and actually was allowed, 
 and the message is incorrectly reporting something that for this particular 
 item is NOT true. The error message (and timing of same) is the bug  
 (philosophically).

 Under the covers it may be (and probably is) more complicated.

Indeed.  For one thing, __iadd__ is not required to return the object
it was called on; it could return some other object.  Imagine a
balanced binary tree that is stored by referencing the root node
object (whether this is good design is tangential; it's a possible
design).  If the __iadd__ operator is implemented to insert a node,
and it causes the root node to change, then it might return that new
root node, expecting it to be assigned.  If Python simply ignores the
exception from the assignment, then the data structure has been
modified in-place, but the tuple does not contain the correct object,
and worst of all no exception has been raised to alert anybody of this
failure condition.  So at best you could only ignore these exceptions
when the object returned by __iadd__ is self, which to be fair is
probably most of the time.

Additionally, recall my statement up-thread that If you skip the
assignment, and that assignment is meaningful to whatever the left
side may be (e.g. assigning to a descriptor or something that invokes
__setitem__ or __setattr__), then the operation is not equivalent.
If you quash the exception from the assignment because the __iadd__
call succeeded, then you have effectively skipped the assignment and
then lied to the programmer about it.  For example, imagine a list
subclass that contains other lists but only permits itself to be
nested 1-deep; the contained lists are required to be flat.  The
__setitem__ method for the class might enforce the constraint and
perhaps also update a count of the total combined length of the nested
lists.  Then suppose we have this code:

lol = ListOfLists([1,2,3], [4], [6, 7, 8, 9])
lol[1] += [5, [5.25, 5.5, 5.75]]

The effect of this is that the middle sublist has been extended to
include a doubly-nested list.  __setitem__ was then called, which
noticed that the list it was passed includes another nested list, so
it raised an exception instead of performing the assignment and
updating the count.  The ListOfLists is now in an invalid state, but
if Python swallows the exception raised by __setitem__, then there is
nothing to warn the programmer of this (until something else
mysteriously fails to process the ListOfLists later on).  The result
is code that is hard to debug.

Now, we might notice that the above code would probably not raise a
TypeError, which is the exception we would expect if trying to mutate
an immutable object, so we might revise the rule again: we only
silence the exception if __iadd__ returns self and the exception is a
TypeError.  But even then, we have no guarantee that this is what the
TypeError was meant to convey.  A TypeError may have simply propagated
up from other buggy code that was indirectly called by __setitem__, a
legitimate error that should be brought to the programmer's attention.

tldr: Swallowing exceptions is a risky practice unless you're quite
sure that you know where the exception is coming from and that it's
safe to ignore, and the language runtime should practically never do
this.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Mark H. Harris
On Saturday, March 1, 2014 8:54:43 AM UTC-6, Mark Lawrence wrote:

 you're also using google groups...  it doesn't wrap paragraphs 
 
 correctly...  please read and action this 
 
 https://wiki.python.org/moin/GoogleGroupsPython...  it does wrap 
 
 paragraphs correctly...  it also prevents us seeing double line spacing...
 
 
 
 do you get my drift???
 

hi Mark,  yes, I understand. I am using google groups (for the moment) and it 
does not wrap correctly, at least not on my end.  It also adds additional 
carrots  that make replying difficult.

The elipses thing is a bad habit which I 'picked up' in academia lately.  We 
use elipses frequently in papers (also this---which also annoys people) and 
there is no problem.  In a writing context the elipses seem to function for 
some people (intentionally) as 'um'.  When humans speak they use the word 'um' 
to pause between though context and its usually unintentional at the surface.  
Often when I write the elipses  (...)  show up for the same reason.  I really 
don't know that I'm doing it... it just happens. So, I am working on it.  No, 
you're not the first to mention it. Its one of my many flaws.

Thanks for keeping me accountable.

Enjoy the day!

marcus
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Mark H. Harris
On Friday, February 28, 2014 11:16:18 PM UTC-6, Ian wrote:

 How would you propose doing that?  Bear in mind that while Python
 knows that tuples specifically are immutable, it doesn't generally
 know whether a type is immutable.  

hi Ian,  I thought of something else after I slept on it, so to speak. Consider 
this:

 s=(2,3,[42,43,44],7)
 s[2]
[42, 43, 44]
 

s[2] is a list. We should be able to change s[2] even though its within a 
tuple, like the:

[42, 43, 44]
 s[2].append(45)
 s[2]
[42, 43, 44, 45]   = no error
 

or like this:

 s[2]
[42, 43, 44, 45]
 s[2]=s[2]+[46]
Traceback (most recent call last):
  File pyshell#8, line 1, in module
s[2]=s[2]+[46]==error,  but look below==
TypeError: 'tuple' object does not support item assignment
 
 s[2]
[42, 43, 44, 45]=also no change to list  !!
 

The point I'm trying to make with this post is that  s[2]+=[46]   and  
s[2]=s[2]+[46]  are handled inconsistently.  In each case (whether the list is 
part of a tuple or not)  s[2]  is a valid list and should be mutable.   In 
every case, in fact, it is.  In each case, in fact, the change might occur.  
But, the change occurs in the first case (append) without an error.  The change 
occurs in the next case too (+=) but with an error not related to the tuple.  
The change does not occur in the third case (s=s+ with an error) but the list 
is not changed ! 

We all know this is not good. We also all know that its because of 
implementation details the user/coder should not need to know.  QED:  a bug.

Having slept on it, I am remembering my Zen of Python:  Errors should never 
pass silently, unless explicitly silenced.  It looks to me like  (+=)  in this 
case is not being handled correctly under the covers.  At any rate the three 
scenarios of trying to change a mutable list as an immutable tuple item should 
be handled consistently.  Either we can change the tuple item (if its a list) 
or we can't.  Also, if we cannot then the expected error should be consistent.

Wow. I think I got that whole thing out without one elipses.

Cheers
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Eric Jacoboni
Le 01/03/2014 22:21, Mark H. Harris a écrit :
 The point I'm trying to make with this post is that  s[2]+=[46]   and
 s[2]=s[2]+[46]  are handled inconsistently.  

For my own, the fact that, in Python, a_liste += e_elt gives a different
result than a_list = a_list + e_elt is a big source of trouble...
I don't really find a good reason to justify that : a_list += spam
gives a valid result, when a_list = a_list + spam is not allowed. Why
the former is like a_list.extend() in the first place ? (well, it's not
exactly an extend() as extend returns None, not +=).

And if both operations were equivalent, i think my initial question
about tuples will be clear and the behavio(u)r would me more consistent
for me :

tu = (10, [10, 30])
tu[1] = tu[1] + [20]-- Big mistake!
TypeError: 'tuple' object does not support item assignment  -- ok
tu
(10, [10, 30])  -- ok too...

In fact, i think i'm gonna forget += on lists :)

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Mark H. Harris
On Saturday, March 1, 2014 3:21:44 PM UTC-6, Mark H. Harris wrote:
 On Friday, February 28, 2014 11:16:18 PM UTC-6, Ian wrote:

hi Ian,  I thought of something else after I slept on it, so to speak. 

Consider this: 

 s=(2,3,[42,43,44],7) 
 s[2] 
[42, 43, 44] 
 

s[2] is a list. We should be able to change s[2] even though its within a 
tuple, 
like this: 

[42, 43, 44] 
 s[2].append(45) 
 s[2] 
[42, 43, 44, 45]   = no error 
 

or like this: 

 s[2] 
[42, 43, 44, 45] 
 s[2]=s[2]+[46] 
Traceback (most recent call last): 
  File pyshell#8, line 1, in module 
s[2]=s[2]+[46]==error,  but look below== 
TypeError: 'tuple' object does not support item assignment 
 
 s[2] 
[42, 43, 44, 45]=also no change to list  !! 
 

The point I'm trying to make with this post is that  s[2]+=[46]  
and  s[2]=s[2]+[46]  are handled inconsistently.  In each case 
(whether the list is part of a tuple or not)  s[2]  is a valid 
list and should be mutable.   In every case, in fact, it is.  
In each case, in fact, the change might occur.  But, the change 
occurs in the first case (append) without an error.  The change 
occurs in the next case too (+=) but with an error not related 
to the tuple.  The change does not occur in the third case
(s=s+ with an error) but the list is not changed ! 

We all know this is not good. We also all know that its because 
of implementation details the user/coder should not need to know.  

QED:  a bug. 

Having slept on it, I am remembering my Zen of Python:  Errors 
should never pass silently, unless explicitly silenced.  It looks 
to me like  (+=)  in this case is not being handled correctly 
under the covers.  At any rate the three scenarios of trying to 
change a mutable list as an immutable tuple item should be handled 
consistently.  
Either we can change the tuple item (if its a list) or we can't.  
Also, if we cannot then the expected error should 
be consistent. 

Cheers 


Hi ,  I'm experimenting a bit with TextWrangler and gg to see if I can 
at least solve my double spacing and wrap problems with cut
a paste for the moment.

Looks ok?

Thanks
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-03-01 Thread Mark H. Harris
On Saturday, March 1, 2014 8:04:32 PM UTC-6, Eric Jacoboni wrote:
 
 
 In fact, i think i'm gonna forget += on lists :)

hi Eric, well, that might be extreme, but you certainly want to avoid
trying to change an immutable. Something you might want to consider
is trying something like creating a new immutable. In the example we've 
been looking at I think I would change the tuple list item with an append();
however, you might try building a new tuple instead of trying to change 
an existing tuple item.
I must admit that I would never have found this bug myself, because I don't
try to change tuples (at all). Anyway, I'm glad you found it -- we all learn
and that's the good news.

Cheers
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Joshua Landau
On 27 February 2014 16:14, Chris Angelico ros...@gmail.com wrote:
 On Fri, Feb 28, 2014 at 3:01 AM, Eric Jacoboni eric.jacob...@gmail.com 
 wrote:

 a_tuple = (spam, [10, 30], eggs)
 a_tuple[1] += [20]
 Traceback (most recent call last):
   File stdin, line 1, in module
 TypeError: 'tuple' object does not support item assignment

 Ok... I accept this message as += is a reassignment of a_tuple[1] and a
 tuple is immutable...

 But, then, why a_tuple is still modified?

 This is a common confusion.

 The += operator does two things. First, it asks the target to please
 do an in-place addition of the other operand. Then, it takes whatever
 result the target gives back, and assigns it back into the target. So
 with a list, it goes like this:

 foo = [10, 30]
 foo.__iadd__([20])
 [10, 30, 20]
 foo = _

Would it be better to add a check here, such that if this gets raised
to the top-level it includes a warning (Addition was inplace;
variable probably mutated despite assignment failure)?
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Chris Angelico
On Sat, Mar 1, 2014 at 1:41 AM, Joshua Landau jos...@landau.ws wrote:
 Would it be better to add a check here, such that if this gets raised
 to the top-level it includes a warning (Addition was inplace;
 variable probably mutated despite assignment failure)?

That'd require figuring out whether or not the variable was actually
mutated, and that's pretty hard to work out. So there's a FAQ entry,
which Zachary already posted:

http://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works

Also, we just answer this question every now and then :) Presumably
more often on -tutor than here.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Mark H. Harris
On Thursday, February 27, 2014 10:01:39 AM UTC-6, Eric Jacoboni wrote:

 
 ('spam', [10, 30, 20], 'eggs')
 

 I get a TypeError for an illegal operation, but this operation is still
 
 completed?

hi Eric,  others have answered your question specifically, but the issue (which 
is recurring) begs a couple of questions, which will also be recurring...  ehem.

This little snag (if it can be called that) is a side effect from two python 
inherent design considerations:

1)  not to be a nudge, but dynamic typing is the first...

2)  and the concept of immutability is the second.

I'll address the second first by asking a question...  should an immutable type 
(object) be able to hold (contain) mutable objects ... should tuples be allowed 
to hold lists? 

lists within a tuple should be converted to tuples.If you want a tuple to 
hold a list,  make it a list in the first place.  Tuples should not be 
changed... and as you point out... half changing a tuple is not a good 
condition if there is an exception...

Which brings me to my second point... dynamic binding...  (and everything 
associated with that) is at play here too  please, don't get me wrong,  
this is not flame bait and I'm not advocating static binding, nor do I want 
static binding, nor do I want to open that can of worms again... just saying.

I really think this is a bug; honestly.  IMHO it should be an error to use  +=  
with an immutable type and that means not at all.  In other words,  the list 
should not even be considered, because we're talking about changing a tuple... 
which should not be changed (nor should its members be changed). 

S   the type does not support item assignment, yet the item got 
assigned.  This is at least inconsistent... and to my small mind means... bug 
report.

:)
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Eric Jacoboni
Le 01/03/2014 01:22, Mark H. Harris a écrit :

 I'll address the second first by asking a question...  should an immutable 
 type (object) be able to hold (contain) mutable objects ... should tuples be 
 allowed to hold lists? 
 
 lists within a tuple should be converted to tuples.If you want a tuple to 
 hold a list,  make it a list in the first place.  

You're perfectly right and that why i've corrected my code to use a list
of lists instead of tuple of list. I was hoping Python would prevent me
for such a mistake (because such a situation can't be cleary
intentional, isn't ?). Now, i will use tuple only with immutable items.


IMHO it should be an error to use  +=  with an immutable type and that
means not at all.

In other words,  the list should not even be considered, because we're
talking about changing a tuple...

which should not be changed (nor should its members be changed).

I agree with that too... My error was to first consider the list, then
the tuple... I should have considered the tuple first...
Anyway, the TypeError should rollback, not commit the mistake.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Chris Angelico
On Sat, Mar 1, 2014 at 11:22 AM, Mark H. Harris harrismh...@gmail.com wrote:
 lists within a tuple should be converted to tuples.If you want a tuple to 
 hold a list,  make it a list in the first place.  Tuples should not be 
 changed... and as you point out... half changing a tuple is not a good 
 condition if there is an exception...


A tuple is perfectly fine containing a list. If you want a tuple to be
recursively immutable, then you're talking about hashability, and
*then* yes, you need to convert everything into tuples - but a tuple
is not just an immutable list. The two are quite different in pupose.

 I really think this is a bug; honestly.  IMHO it should be an error to use  
 +=  with an immutable type and that means not at all.  In other words,  the 
 list should not even be considered, because we're talking about changing a 
 tuple... which should not be changed (nor should its members be changed).


Definitely not! Incrementing an integer with += is a perfectly normal
thing to do:

x = 5
x += 1

It's just a matter of knowing the language and understanding what's going on.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Mark H. Harris
On Friday, February 28, 2014 7:27:17 PM UTC-6, Eric Jacoboni wrote:

 I agree with that too... My error was to first consider the list, then
 the tuple... I should have considered the tuple first...
 Anyway, the TypeError should rollback, not commit the mistake.

I believe so too,  but I'm not one of the core devs.  And they do not agree. 

Ever since day one with me and python I have questioned whether a tuple even 
makes sense.  Well, certainly it does, because it has so much less overhead and 
yet it acts like a list (which for so many situations thats really what we want 
anyway... a list, that never changes).  Tuples are great, for what they are 
designed to do.

But now consider,  why would I purposely want to place a mutable object within 
an immutable list?  A valid question of high importance.  Why indeed?  

I really believe IMHO that the error should have come when you made the list an 
item of a tuple.  An immutable object should have NO REASON to contain a 
mutable  object like list... I mean the whole point is to eliminate the 
overhead of a list ... why would the python interpreter allow you to place a 
mutable object within an immutable list in the first place.   This is just 
philosophical, and yes, the core dev's are not going to agree with me on this 
either.

I think the situation is interesting for sure... and it will surface again, you 
can count on that.

Cheers
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Ian Kelly
On Fri, Feb 28, 2014 at 5:22 PM, Mark H. Harris harrismh...@gmail.com wrote:
 I really think this is a bug; honestly.  IMHO it should be an error to use  
 +=  with an immutable type and that means not at all.  In other words,  the 
 list should not even be considered, because we're talking about changing a 
 tuple... which should not be changed (nor should its members be changed).

How would you propose doing that?  Bear in mind that while Python
knows that tuples specifically are immutable, it doesn't generally
know whether a type is immutable.  The best it can do is try the
operation and raise a TypeError if it fails.  So you can special-case
the code for += to fail earlier if the left side of the assignment is
an tuple index, but it's not currently possible to do the same for
arbitrary immutable user types.

Even if that is solved, how you do propose preventing the simple workaround:
tup = (0, [1, 2, 3], 4)
thelist = tup[1]
thelist += [5, 6]
which currently works just fine.

I don't think that entirely disallowing mutable objects inside tuples
is an acceptable solution.  For one, it would mean never allowing
instances of custom classes.  They could then basically only contain
strings, numbers, frozensets, and other tuples.  For another, my
experience suggests that if I'm putting a list inside a tuple in the
first place, I am generally not relying on the immutability of that
tuple anyway, so I don't really see this as fixing an actual problem.
The usual reason for wanting an immutable object is to hash it, and a
tuple containing a list already can't be hashed.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Ian Kelly
On Fri, Feb 28, 2014 at 6:27 PM, Eric Jacoboni eric.jacob...@gmail.com wrote:
 Anyway, the TypeError should rollback, not commit the mistake.

The Python interpreter isn't a database.  It can't rollback the object
because the operation that was performed may not be reversible.
Consider for example a socket class where the += operator is
overloaded to send a message on the socket.  The message can't be
unsent.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Ian Kelly
On Fri, Feb 28, 2014 at 9:45 PM, Mark H. Harris harrismh...@gmail.com wrote:
 I really believe IMHO that the error should have come when you made the list 
 an item of a tuple.  An immutable object should have NO REASON to contain a 
 mutable  object like list... I mean the whole point is to eliminate the 
 overhead of a list ... why would the python interpreter allow you to place a 
 mutable object within an immutable list in the first place.   This is just 
 philosophical, and yes, the core dev's are not going to agree with me on this 
 either.

One very common example of tuples containing lists is when lists are
passed to any function that accepts *args, because the extra arguments
are passed in a tuple.  A similarly common example is when returning
multiple objects from a function, and one of them happens to be a
list, because again they are returned in a tuple.

def f(*args):
print(args)
return (args[1:])

 result = f(1, 2, 3, [4, 5])
(1, 2, 3, [4, 5])
 print(result)
(2, 3, [4, 5])

Both of these things are quite handy, and if you restrict tuples from
containing lists, then you lose both of them (or you switch to lists
and lose the optimization for no good reason).
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Mark H. Harris
On Friday, February 28, 2014 11:34:56 PM UTC-6, Ian wrote:

 One very common example of tuples containing lists is when lists are
 passed to any function that accepts *args, because the extra arguments
 are passed in a tuple.  A similarly common example is when returning
 multiple objects from a function, and one of them happens to be a
 list, because again they are returned in a tuple.

 def f(*args):
 print(args)
 return (args[1:]
 
  result = f(1, 2, 3, [4, 5])
 (1, 2, 3, [4, 5])
  print(result)
 (2, 3, [4, 5])

I agree Ian... good points all.   ... again, I'm not arguing with anyone... 
just saying that an error (whatever we mean by that) should not 
half-way-fail   we are only pointing out the problem... we have not idea 
what the solution is yet. 

Intuitively everyone can see that there is a problem here...  the debate cannot 
be answered either because of the inherent design of python (almost all of 
which we love). So, as they say, what is a mother to do?  ... I mean, some 
people's kids... 

I don't know how I propose to handle the problem... I think the first step is 
getting everyone to agree that there IS a problem... then debate how to tackle 
the solution proposals.

marcus
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Mark H. Harris
On Friday, February 28, 2014 11:16:18 PM UTC-6, Ian wrote:

 How would you propose doing that?  Bear in mind that while Python
 knows that tuples specifically are immutable, it doesn't generally
 know whether a type is immutable.  


hi Ian,   consider the problem... its a cake and eat it too scenario...   
you can't have your cake and eat it too

This is what I mean...   the error message is telling the user that it cannot 
do what he has requested, and yet IT DID.  You have one of two scenarios:   1) 
the message is arbitrarily lying and it really can assign an immutable's 
item...  (and it did!)  or 2)  It really should NOT assign an immutables item 
(the message is truth) but the action was allowed anyway despite the protocol 
and accepted policy...  in which case the two scenarios add up to a giant 
logical inconsistency...  QED   a bug.

There really is no way to get around this from the average user's perspective. 
And therefore, this is going to come up again and again and again... because 
coders don't like unexpected logical inconsistencies.

It is not helpful either to explain it away by knowing how the policy works 
under the covers... the average user of the python language should not have to 
understand the policy of the underlying substructure... 

1) either they can't assign the list item of an immutable tuple type (and the 
action is a flaw, say bug

of

2) they really CAN set the immutable's list item type (which it did!) and the 
message is bogus.


Its one way or the other  we can't have our cake and eat it too... this is 
(one way or the other) a bug.

IMHO
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Chris Angelico
On Sat, Mar 1, 2014 at 5:16 PM, Mark H. Harris harrismh...@gmail.com wrote:
 This is what I mean...   the error message is telling the user that it cannot 
 do what he has requested, and yet IT DID.  You have one of two scenarios:   
 1) the message is arbitrarily lying and it really can assign an immutable's 
 item...  (and it did!)  or 2)  It really should NOT assign an immutables item 
 (the message is truth) but the action was allowed anyway despite the 
 protocol and accepted policy...  in which case the two scenarios add up to a 
 giant logical inconsistency...  QED   a bug.


It did not assign anything. The error was raised because the tuple
rejects item assignment. It's that simple.

The object was mutated before the assignment was attempted.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-28 Thread Mark H. Harris
On Friday, February 28, 2014 11:16:18 PM UTC-6, Ian wrote:

 How would you propose doing that?  Bear in mind that while Python
 knows that tuples specifically are immutable, it doesn't generally
 know whether a type is immutable.  

I was going to bed, and then thought of the solution.  Allow the action.

Hold the error pending until the action either succeeds or fails for the 
item, in this case a list, and then post the error for the tuple depending... 
this would make the whole mess consistent, and even compatible with the idea of 
dynamic name and type binding...

So, in other words, the action should be permitted, and actually was allowed, 
and the message is incorrectly reporting something that for this particular 
item is NOT true. The error message (and timing of same) is the bug  
(philosophically).

Under the covers it may be (and probably is) more complicated.

Good night everyone.

Cheers
-- 
https://mail.python.org/mailman/listinfo/python-list


Tuples and immutability

2014-02-27 Thread Eric Jacoboni
Hi,

I'm using Python 3.3 and i have a problem for which i've still not found
any reasonable explanation...

 a_tuple = (spam, [10, 30], eggs)
 a_tuple[1] += [20]
Traceback (most recent call last):
  File stdin, line 1, in module
TypeError: 'tuple' object does not support item assignment

Ok... I accept this message as += is a reassignment of a_tuple[1] and a
tuple is immutable...

But, then, why a_tuple is still modified?

 a_tuple
('spam', [10, 30, 20], 'eggs')

I get a TypeError for an illegal operation, but this operation is still
completed?
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Zachary Ware
On Thu, Feb 27, 2014 at 10:01 AM, Eric Jacoboni eric.jacob...@gmail.com wrote:
 Hi,

 I'm using Python 3.3 and i have a problem for which i've still not found
 any reasonable explanation...

 a_tuple = (spam, [10, 30], eggs)
 a_tuple[1] += [20]
 Traceback (most recent call last):
   File stdin, line 1, in module
 TypeError: 'tuple' object does not support item assignment

 Ok... I accept this message as += is a reassignment of a_tuple[1] and a
 tuple is immutable...

 But, then, why a_tuple is still modified?

 a_tuple
 ('spam', [10, 30, 20], 'eggs')

 I get a TypeError for an illegal operation, but this operation is still
 completed?

You're not the first person to have this question :)

http://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works

-- 
Zach
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Chris Angelico
On Fri, Feb 28, 2014 at 3:01 AM, Eric Jacoboni eric.jacob...@gmail.com wrote:
 I'm using Python 3.3 and i have a problem for which i've still not found
 any reasonable explanation...

 a_tuple = (spam, [10, 30], eggs)
 a_tuple[1] += [20]
 Traceback (most recent call last):
   File stdin, line 1, in module
 TypeError: 'tuple' object does not support item assignment

 Ok... I accept this message as += is a reassignment of a_tuple[1] and a
 tuple is immutable...

 But, then, why a_tuple is still modified?

 a_tuple
 ('spam', [10, 30, 20], 'eggs')

 I get a TypeError for an illegal operation, but this operation is still
 completed?

This is a common confusion.

The += operator does two things. First, it asks the target to please
do an in-place addition of the other operand. Then, it takes whatever
result the target gives back, and assigns it back into the target. So
with a list, it goes like this:

 foo = [10, 30]
 foo.__iadd__([20])
[10, 30, 20]
 foo = _

Note that the second step returned a three-element list. Then the +=
operator stuffs that back into the source. In the case of a list, it
does that addition by extending the list, and then returning itself.

The putting-back-into-the-target step fails with a tuple, because a
tuple's members can't be reassigned. But the list has already been
changed by that point. So, when you go to look at it, you see a
changed list.

You can call on this behaviour more safely by using the append() or
extend() methods.

 a_tuple = (spam, [10, 30], eggs)
 a_tuple[1].extend([20])
 a_tuple
('spam', [10, 30, 20], 'eggs')

 a_tuple = (spam, [10, 30], eggs)
 a_tuple[1].append(20)
 a_tuple
('spam', [10, 30, 20], 'eggs')

(append will add one more element, extend is roughly the same as you
had). Then you're not trying to assign to the tuple, but you're still
mutating the list.

Hope that helps!

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Marko Rauhamaa
Eric Jacoboni eric.jacob...@gmail.com:

 a_tuple[1] += [20]
 Traceback (most recent call last):
   File stdin, line 1, in module
 TypeError: 'tuple' object does not support item assignment

 [...]

 But, then, why a_tuple is still modified?

That's because the += operator

 1. modifies the list object in place

 2. tries to replace the tuple slot with the list (even though the list
hasn't changed)

It's Step 2 that raises the exception, but the damage has been done
already.

One may ask why the += operator modifies the list instead of creating a
new object. That's just how it is with lists.

BTW, try:

a_tuple[1].append(20)
a_tuple[1].extend([20])

Try also:

a_tuple[1] = a_tuple[1]


Marko
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Eric Jacoboni
Le 27/02/2014 17:13, Zachary Ware a écrit :

 You're not the first person to have this question :)
 
 http://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
 

Oh yes, i was aware of this explanation (thanks to Chris for his answer,
too)... and that's why i wrote reasonable :)
I know i should use append() or extend() and i understand the tricky
implications of += in this context. But, imho, it's far from being a
intuitive result, to say the least.

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Chris Angelico
On Fri, Feb 28, 2014 at 3:27 AM, Eric Jacoboni eric.jacob...@gmail.com wrote:
 But, imho, it's far from being a intuitive result, to say the least.

It's unintuitive, but it's a consequence of the way += is defined. If
you don't want assignment, don't use assignment :)

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Zachary Ware
On Thu, Feb 27, 2014 at 10:27 AM, Eric Jacoboni eric.jacob...@gmail.com wrote:
 Le 27/02/2014 17:13, Zachary Ware a écrit :

 You're not the first person to have this question :)

 http://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works


 Oh yes, i was aware of this explanation (thanks to Chris for his answer,
 too)... and that's why i wrote reasonable :)
 I know i should use append() or extend() and i understand the tricky
 implications of += in this context. But, imho, it's far from being a
 intuitive result, to say the least.

Well, once you understand what's actually going on, it's the result
that you should expect.  The FAQ entry I linked to exists to help
people get to that point.

To answer your specific questions:

 But, then, why a_tuple is still modified?

It is not.  a_tuple is still ('spam', list object at specific
address, 'eggs'), exactly the same before and after the attempted
a_tuple[1] += [20].  The change is internal to list object at
specific address.

 I get a TypeError for an illegal operation, but this operation is still
 completed?

Half completed.  The extension of list object at specific address
happened as expected, but the assignment of list object at specific
address to a_tuple[1] didn't.  It looks like it did, though, because
the assignment was just trying to assign the same object to the same
index.

-- 
Zach
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Nick Timkovich
On Thu, Feb 27, 2014 at 10:33 AM, Chris Angelico ros...@gmail.com wrote:

 On Fri, Feb 28, 2014 at 3:27 AM, Eric Jacoboni eric.jacob...@gmail.com
 wrote:
  But, imho, it's far from being a intuitive result, to say the least.

 It's unintuitive, but it's a consequence of the way += is defined. If
 you don't want assignment, don't use assignment :)

 ChrisA


Where is `.__iadd__()` called outside of `list += X`?  If the only
difference from `.extend()` is that it returns `self`, but the list was
already modified anyway, why bother with reassignment?
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Chris Angelico
On Fri, Feb 28, 2014 at 8:47 AM, Nick Timkovich prometheus...@gmail.com wrote:
 On Thu, Feb 27, 2014 at 10:33 AM, Chris Angelico ros...@gmail.com wrote:

 On Fri, Feb 28, 2014 at 3:27 AM, Eric Jacoboni eric.jacob...@gmail.com
 wrote:
  But, imho, it's far from being a intuitive result, to say the least.

 It's unintuitive, but it's a consequence of the way += is defined. If
 you don't want assignment, don't use assignment :)

 ChrisA


 Where is `.__iadd__()` called outside of `list += X`?  If the only
 difference from `.extend()` is that it returns `self`, but the list was
 already modified anyway, why bother with reassignment?

Not everything handles += that way. You can't mutate the integer 5
into 7 because someone had 5 in x and wrote x += 2. So it has to
reassign.

Actually, integers just don't define __iadd__, but the principle applies.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Ian Kelly
On Thu, Feb 27, 2014 at 2:47 PM, Nick Timkovich prometheus...@gmail.com wrote:
 On Thu, Feb 27, 2014 at 10:33 AM, Chris Angelico ros...@gmail.com wrote:

 On Fri, Feb 28, 2014 at 3:27 AM, Eric Jacoboni eric.jacob...@gmail.com
 wrote:
  But, imho, it's far from being a intuitive result, to say the least.

 It's unintuitive, but it's a consequence of the way += is defined. If
 you don't want assignment, don't use assignment :)

 ChrisA


 Where is `.__iadd__()` called outside of `list += X`?  If the only
 difference from `.extend()` is that it returns `self`, but the list was
 already modified anyway, why bother with reassignment?

x += y is meant to be equivalent, except possibly in-place and more
efficient, than x = x + y.  If you skip the assignment, and that
assignment is meaningful to whatever the left side may be (e.g.
assigning to a descriptor or something that invokes __setitem__ or
__setattr__), then the operation is not equivalent.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread John O'Hagan
On Thu, 27 Feb 2014 18:19:09 +0200
Marko Rauhamaa ma...@pacujo.net wrote:

 Eric Jacoboni eric.jacob...@gmail.com:
 
  a_tuple[1] += [20]
  Traceback (most recent call last):
File stdin, line 1, in module
  TypeError: 'tuple' object does not support item assignment
 
  [...]
 
  But, then, why a_tuple is still modified?
 
 That's because the += operator
 
  1. modifies the list object in place
 
  2. tries to replace the tuple slot with the list (even though the
 list hasn't changed)
 
 It's Step 2 that raises the exception, but the damage has been done
 already.
 
 One may ask why the += operator modifies the list instead of creating
 a new object. That's just how it is with lists.
 
 BTW, try:
 
 a_tuple[1].append(20)
 a_tuple[1].extend([20])
 
 Try also:
 
 a_tuple[1] = a_tuple[1]
 

[...]

Also try:

x = a_tuple[1] #What's in a name?
x is a_tuple[1] #Obviously, but:
x += [1] #No error
a_tuple[1] += [1] #Error

Same object, just a different name - but a different result. I get why,
but still find that odd.

--

John




-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Tuples and immutability

2014-02-27 Thread Marko Rauhamaa
John O'Hagan resea...@johnohagan.com:

 Same object, just a different name - but a different result. I get
 why, but still find that odd.

The general principle is stated in the language specification:

   URL: http://docs.python.org/3.2/reference/simple_stmts.html
   #augmented-assignment-statements:

   Also, when possible, the actual operation is performed in-place,
   meaning that rather than creating a new object and assigning that to
   the target, the old object is modified instead.

   [...] with the exception of the possible in-place behavior, the
   binary operation performed by augmented assignment [x += y] is the
   same as the normal binary operations [x = x + y].

We should call += dual-use technology.


Marko
-- 
https://mail.python.org/mailman/listinfo/python-list