Reputation: 437
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?
Upvotes: 2
Views: 2850
Reputation: 152677
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, ifx
is an instance of a class with an__iadd__()
method, x += y is equivalent tox = x.__iadd__(y)
. Otherwise,x.__add__(y)
andy.__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?
Upvotes: 2
Reputation: 29690
Frozensets are immutable, except your assignment isn't mutating the original frozenset - you are just reassigning the variable x
to the result of your binary operator &
. As noted by user2357112 in the comments, x &= {'baz', 'qux', 'quux'}
falls back on x = x & {'baz', 'qux', 'quux'}
after an __iand__
method is not found, leaving you with a non-mutating operation.
This behavior can be seen for other augmented operations on immutable types that don't supply __iand__
, e.g.
In[1]: x = (1, 2, 3)
In[2]: id(x)
Out[2]: 761782862328
In[3]: x += (4, 5)
In[4]: id(x) # we now have a different id
Out[4]: 761780182888
In[5]: x[2] = 3 # an actual mutating operation
TypeError: 'tuple' object does not support item assignment
Upvotes: 2