Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

the first really wtf thing on there is https://github.com/satwikkansal/wtfpython/blob/master/README... where both effect and exception happen; up to that point it's business as usual with garden variety corner cases or implementation details.


"Effect and exception" is bad, but its a special case of a more general bad design - the fact that `a += b` is sometimes equivalent to `a = a + b` and sometimes changes in-place.


I find the explanation lacking? sort also "changes the list in-place", but you can do ([3, 1, 2],)[0].sort()


`a += b` does something along the lines of `a = a.__iadd__(b)`. Here `__iadd__` performs the actual append, but then the assignment fails.


Does it though? So why doesn't a += b change the object id id(a) ?


As OP noted, given the existence of type(a).__iadd__[0] `a += b` essentially desugars to: `a = a.__iadd__(b)`.

list.__iadd__ is (sadly) defined as something like

    def __iadd__(self, other):
        self.extend(other)
        return self
So it's possible to have __iadd__ itself succeed modifying the list in place but then the "no-op" reassignment fail.

[0] like many data model operations there's really a bunch of fallbacks depending on what is and is not implemented


I'd almost consider this a subtle bug? None of the other += operators returns a value.


> I'd almost consider this a subtle bug?

It's not exactly a bug but it is a somewhat unexpected behaviour and IIRC Guido regretted that list.__iadd__ was overridden this way.

> None of the other += operators returns a value.

It's not the operator which returns a value, it's the data model hook. The data model requires that it return a value: https://docs.python.org/3/reference/datamodel.html?#object._...

> These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self).

Really the issue is that the "in-place" hooks are simply weird.


Because if `a` and `b` are lists, `a.__iadd__(b)` mutates `a` in place, then returns it.


Right, got it.


Of course you can. The first element of that tuple is a list, which is mutable, and can be sorted in-place.


So the question is if += and sort are both "in-place", why does one throw and the other doesn't? Clearly "in-place" is not the full explanation here.

I think the real explanation is that with += there are two steps:

1- The existing list gets modified. This is fine since lists are mutable.

2- The tuple's reference to the list gets updated (even though this update is unnecessary since the list object's identity is the same).

The exception occurs at step 2, but this step is otherwise a no-op. Whether mutating a reference to the same value it already had is an actual mutation is an interesting semantics debate.


Tuples are immutable, but that doesn't mean they can't contain references to mutable objects.

When you want to use a tuple as a key in a dictionary, Python checks if all members of the tuple are immutable. If one or more of them aren't, you'll get an 'unhashable type' error.





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: