pythonsetimmutabilityaugmented-assignment

Augmented assignment with frozenset


I just tried an augmented assignment on a frozenset, and the result surprised me:

>>> x = frozenset(['foo', 'bar', 'baz'])
>>> x
frozenset({'foo', 'baz', 'bar'})
>>> x &= {'baz', 'qux', 'quux'}
>>> x
frozenset({'baz'})

This isn't supposed to happen, is it? Aren't frozensets immutable?


Solution

  • Why are you surprised?

    You knew the term "augmented assignment" so there is no problem finding the "Python Data Model on augmented arithmetic assignments" (emphasis mine):

    These [__i***__] 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). If a specific method is not defined, the augmented assignment falls back to the normal methods. For instance, if x is an instance of a class with an __iadd__() method, x += y is equivalent to x = x.__iadd__(y) . Otherwise, x.__add__(y) and y.__radd__(x) are considered, [...]

    >>> x = frozenset(['foo', 'bar', 'baz'])
    >>> x.__iand__
    [...]
    AttributeError: 'frozenset' object has no attribute '__iand__'
    

    So it has no __iand__ method so the code you perform is:

    >>> x = x & {'baz', 'qux', 'quux'}
    

    The __and__ method however is defined by frozenset:

    >>> x & {'baz', 'qux', 'quux'}
    frozenset({'baz'})
    

    However you lost your reference to the original frozenset: x:

    >>> y = x   # that doesn't do a copy, it's just to check if `x` has changed"
    >>> x &= {'baz', 'qux', 'quux'}
    >>> x is y  # check if they reference the same object!
    False
    >>> x, y
    (frozenset({'baz'}), frozenset({'bar', 'baz', 'foo'}))
    

    But that just follows the "Principle of least astonishment". You wanted the __and__ and you made it clear that you didn't want to keep your original x - an in-place operation also would have altered it!

    So again: Why did that surprise you?