Tuples and immutability

Ian Kelly ian.g.kelly at gmail.com
Sat Mar 1 14:45:55 EST 2014


On Fri, Feb 28, 2014 at 11:25 PM, Mark H. Harris <harrismh777 at 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.



More information about the Python-list mailing list