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

Reply via email to