我最初是作为评论写的,但我想稍微扩展一下,特别是添加元组示例。
Mark Tolonen's answer 是正确的(并且赞成),因为普通整数是不可变的(不能更改),而列表是可变的(可以替换元素),但没有提到另外几个关键概念,这些概念有点吓人例子:
-
对象将绑定到变量。
像x = 3 这样的普通变量赋值只是将右侧的对象(如果需要可以在现场构造)绑定到左侧的名称。
-
像+= 这样的“就地”运算符会尝试调用修饰函数,从而允许可变对象捕获它们。例如,如果x 绑定到一个类实例,写入x += 3 将实际执行x.__iadd__(3),如果x 有一个__iadd__。1 如果没有,它运行@987654335 @ 而是调用 __add__ 运算符:x = x.__add__(3)。有关所有血腥细节,请参阅the operator documentation。在这种情况下,所涉及的对象——普通整数——没有修饰函数:
>>> (3).__add__
<method-wrapper '__add__' of int object at 0x801c07f08>
>>> (3).__iadd__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__iadd__'
所以这个特殊的转折与int 无关,但值得记住。
-
索引 赋值 x[i] = expression 调用 __setitem__ 方法。这就是可变(可修改)对象自身发生变异的方式。列表对象实现__setitem__:
>>> [].__setitem__
<method-wrapper '__setitem__' of list object at 0x8007767e8>
为了完整起见,我会注意到它还实现了__getitem__ 以检索 x[i]。
因此,当您写 a[i] += 5 时,您最终会调用:
a.__setitem__(i, a.__getitem__(i) + 5)
这就是 Python 如何设法将 5 添加到绑定到 a 的列表的第 i 个元素中。
这是一个有点吓人的例子。 tuple 对象是不可 可修改的,但 list 对象是可修改的。如果我们将列表嵌入到元组中:
>>> l = [0]
>>> t = (l,)
然后我们可以使用t[0] 调用t.__getitem__ 和t.__setitem__。同时t[0] 绑定到与l 相同的列表对象。这部分很明显:
>>> t
([0],)
>>> l.append(1)
>>> t
([0, 1],)
我们在原地修改了l,因此与l 命名相同的列表的t[0] 已被修改。但是现在:
>>> t[0] += [2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
([0, 1, 2],)
什么?!当我们收到一个错误告诉我们t 无法更改时,t 是如何更改的?
答案是,t没有改变,但是列表(我们也可以通过l访问)改变了 改变。列表实现__iadd__,所以赋值:
t[0] += [2]
“意思”:
t.__setitem__(0, t.__getitem__(0).__iadd__([2]))
__getitem__ 访问了列表,__iadd__ 扩充了列表:
>>> l
[0, 1, 2]
然后t.__setitem__(0, ...) 提出了TypeError,但到那时该列表已经增加了。
注意,顺便说一下,调整绑定到l 的列表对象会影响绑定到t 的元组对象,因为t[0] 是那个列表对象。这一点——变量绑定到对象,元组、列表和字典等数据结构中的元素可以引用其他对象——对于阅读和编写 Python 代码至关重要。 了解绑定规则以及创建对象的时间是了解为什么这通常是一个坏主意的关键:
def f(a=[]):
具体来说,这里的列表对象是在def 时间创建的,即只有一次。任何时候有人添加到f 中的列表,例如a.append,它会不断添加到原始列表中!
另请参阅python: how binding works,了解更多关于附加绑定规则的信息。 :-)
1作为Duncan points out in a comment对chepner's answer,调用__iadd__后,返回的结果重新绑定到对象。 (所有函数都返回一个结果;不带表达式返回,或“从函数末尾掉线”定义为返回None。)
可变对象应该通常返回它们自己,而不可变对象首先不需要实现__iadd__,因为在一个所谓的不可变对象上实现突变似乎很奇怪。尽管如此,我们可以通过编写一个 假装 不可变但实际上并非可变的类来滥用,从而暴露这种行为。这是一个例子。这并不意味着有用,只是为了说明 Python 的黑暗角落之一。
"""
demonstration of iadd behavior
"""
from __future__ import print_function
class Oddity(object):
"""
A very odd class: like a singleton, but there can be
more than one of them. Each one is a list that just
accumulates more items. The __iadd___ (+=) operator
augments the item, then advances to the next instance.
Creating them is tricky as we want to create new ones
up until we "freeze" the class, then start re-using
the instances. We use a __new__ operator a la immutable
objects, plus a boolean in the class itself, even though
each instance is mutable.
"""
def __new__(cls):
if not hasattr(cls, 'frozen'):
cls.frozen = False
if cls.frozen:
whichone = cls.rotator
cls.rotator = (whichone + 1) % len(cls.instances)
return cls.instances[whichone]
self = object.__new__(cls)
if not hasattr(cls, 'instances'):
cls.instances = []
self.whichone = len(cls.instances)
self.values = []
cls.instances.append(self)
print('created', self)
return self
def __str__(self):
return '#{}, containing {}'.format(self.whichone, self.values)
def __iadd__(self, value):
print('iadd to', self)
self.values.append(value)
all_oddities = self.__class__.instances
nextone = (self.whichone + 1) % len(all_oddities)
return all_oddities[nextone]
@classmethod
def freeze(cls):
if not hasattr(cls, 'frozen'):
raise TypeError('need at least one instance to freeze')
cls.frozen = True
cls.rotator = 0
# Now make two instances, and freeze the rest so that
# we can cycle back and forth.
o0 = Oddity()
o1 = Oddity()
Oddity.freeze()
print('o0 is', o0)
o0 += 'first add to o0'
o0 += 'second add to o0'
o0 += 'third add to o0'
print('now o0 is', o0, 'and o1 is', o1)
print('the first object is', Oddity.instances[0])
一旦我们创建了两个对象并冻结了类,我们在o0上调用了三次__iadd__,所以最后o0和o1实际上都绑定到了秒 em>对象。第一个对象(只能通过类的 cls.instances 字段找到)在其项目列表中有两个项目。
作为一个练习,在运行之前尝试预测它会打印出什么。
(超高级练习:将Oddity 变成一个元类,可以将它们应用于类以将它们变成可冻结的多单件。[是否有“事物是喜欢一个单例,但允许其中 N 个“?] 另见 Why Singletons Are Controversial。)
[编辑:这一直困扰着我:原来是一个固定集合的单例的名称。当集合只有两个元素时,它是一个“doubleton”。在 Python 中,True 和 False 是组成 bool 类型的双元组。 n 个对象的泛化是multiton,经常被实例化/用作固定的——至少在“冻结”时间之后——哈希表。请注意,Python 的 doubleton 布尔实例是不可变的,Python 的单例 None 也是如此。]