我刚刚在 freezeset 上尝试了一个增强的任务,结果让我感到惊讶:
>>> x = frozenset(['foo', 'bar', 'baz'])
>>> x
frozenset({'foo', 'baz', 'bar'})
>>> x &= {'baz', 'qux', 'quux'}
>>> x
frozenset({'baz'})
这不应该发生,是吗?freezesets 不是不可变的吗?
我刚刚在 freezeset 上尝试了一个增强的任务,结果让我感到惊讶:
>>> x = frozenset(['foo', 'bar', 'baz'])
>>> x
frozenset({'foo', 'baz', 'bar'})
>>> x &= {'baz', 'qux', 'quux'}
>>> x
frozenset({'baz'})
这不应该发生,是吗?freezesets 不是不可变的吗?
Frozensets是不可变的,除了您的分配不会改变原始的 freezeset - 您只是将变量重新分配x
给二元运算符的结果&
。正如 user2357112 在评论中指出的那样,在找不到方法后x &= {'baz', 'qux', 'quux'}
回退,给您留下一个非变异操作。x = x & {'baz', 'qux', 'quux'}
__iand__
对于不提供的不可变类型的其他增强操作,可以看到此行为__iand__
,例如
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
你为什么惊讶?
您知道“增强赋值”这个术语,所以找到“增强算术赋值上的 Python 数据模型”(重点是我的)没有问题:
这些 [
__i***__
] 方法应该尝试就地执行操作(修改 self)并返回结果(可以是但不一定是 self)。如果未定义特定方法,则扩充分配回退到正常方法。例如,如果x
是具有__iadd__()
方法的类的实例,则 x += y 等价于x = x.__iadd__(y)
。否则,x.__add__(y)
并被y.__radd__(x)
认为 [...]
>>> x = frozenset(['foo', 'bar', 'baz'])
>>> x.__iand__
[...]
AttributeError: 'frozenset' object has no attribute '__iand__'
所以它没有__iand__
方法,所以你执行的代码是:
>>> x = x & {'baz', 'qux', 'quux'}
然而,该__and__
方法由以下定义frozenset
:
>>> x & {'baz', 'qux', 'quux'}
frozenset({'baz'})
但是,您丢失了对原始文件的引用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'}))
但这只是遵循“最小惊讶原则”。你想要它__and__
并且你明确表示你不想保留你的原始x
- 就地操作也会改变它!
再说一遍:为什么这让你感到惊讶?