当b是列表时,为什么b + =(4,)有效而b = b +(4,)不起作用?


77

如果我们采取b = [1,2,3]并尝试这样做:b+=(4,)

它返回b = [1,2,3,4],但是如果我们尝试这样做b = b + (4,)是行不通的。

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

我预计b+=(4,)会失败,因为您无法添加列表和元组,但它确实有效。因此,我尝试b = b + (4,)期望得到相同的结果,但是没有用。


4
我相信可以在这里找到答案。
jochen


起初,我误读了此文件,并尝试将其关闭得太宽,然后将其收回。然后,我认为它必须是重复的,但不仅我不能重新投票,而且还试图找出类似的其他答案。:/
Karl Knechtel

Answers:


70

“为什么”问题的问题在于它们通常可能意味着多种不同的事物。我会尝试回答我认为您可能会想到的每个问题。

“为什么它可能有不同的工作方式?” 这可以通过例如this来回答。基本上,+=尝试使用该对象的不同方法:(__iadd__仅在左侧检查),vs __add____radd__(“反向添加”,在左侧没有检查的情况下在右侧检查__add__)为+

“每个版本到底做什么?” 简而言之,该list.__iadd__方法的功能与之相同list.extend(但由于语言设计的原因,仍然存在分配问题)。

例如,这还意味着

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+,当然会创建一个新对象,但是显式地需要另一个列表,而不是尝试从其他序列中拉出元素。

“为什么这样做对+ =有用?它效率更高;该extend方法不必创建新对象。当然,有时这会产生一些令人惊讶的效果(如上所示),通常Python并不是真正意义上的效率,但这些决定是很久以前做出的。

“为什么不允许使用+添加列表和元组的原因是什么?” 这里(谢谢@ splash58); 一个想法是(tuple + list)应该产生与(list + tuple)相同的类型,并且不清楚结果应该是哪种类型。+=没有这个问题,因为a += b显然不应该更改的类型a


2
哦,非常正确。并且列表不使用|,因此有点毁了我的示例。如果我想更清楚的例子后,我会在交换它。
卡尔Knechtel

1
顺便说一句|,集合是通勤运算符,但+列表则不是。因此,我认为关于类型歧义性的论点不是特别强烈。由于运算符不上下班,为什么对类型要求相同?一个人可能只是同意结果是lhs类型。另一方面,通过限制list + iterator,鼓励开发人员更明确地说明其意图。如果您要创建一个新列表,其中包含来自a的内容,再加上来自的内容,b则已经存在一种方法:new = a.copy(); new += b。这是另外一行,但非常清晰。
a_guest,

a += b行为不同于a = a + b效率的原因。实际上,Guido认为所选择的行为不太混乱。想象一个函数接收一个列表a作为参数,然后执行a += [1, 2, 3]。这种语法肯定看起来像是在修改列表,而不是创建新列表,因此决定要根据大多数人对预期结果的直觉行事。但是,该机制还必须适用于诸如ints的不可变类型,这导致了当前的设计。
Sven Marnach

我个人认为设计实际上是 不是简单地做困惑a += b一个简写a = a + b,如红宝石一样,但我能理解我们是如何到达那里。
Sven Marnach

21

它们不等效:

b += (4,)

是以下内容的简写:

b.extend((4,))

同时+连接列表,因此:

b = b + (4,)

您正在尝试将元组连接到列表


14

执行此操作时:

b += (4,)

转换为此:

b.__iadd__((4,)) 

在幕后b.extend((4,))extend接受一个迭代器,这也为什么起作用:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

但是当您这样做时:

b = b + (4,)

转换为此:

b = b.__add__((4,)) 

仅接受列表对象。


4

从官方文档来看,对于可变序列类型,两种:

s += t
s.extend(t)

定义为:

扩展s了内容t

不同于定义为:

s = s + t    # not equivalent in Python!

这也意味着任何序列类型都适用于t,包括示例中的元组。

但它也适用于范围和发电机!例如,您还可以执行以下操作:

s += range(3)

3

+=在2000年10月发布的Python 2.0中引入了类似的“增强”赋值运算符。在PEP 203中描述了其设计和原理。这些运营商宣布的目标之一是就地运营的支持。写作

a = [1, 2, 3]
a += [4, 5, 6]

应该更新列表a 到位。如果还有其他对列表的引用a,例如当a作为函数参数被接收时,这一点就很重要。

然而,由于许多Python类型(包括整数和字符串)都是不可变的,因此操作不一定总是在原地进行的,因此例如i += 1对于整数,i就不可能在原地进行操作。

总而言之,应该在可能的情况下使用扩充的赋值运算符,否则将创建一个新对象。为了实现这些设计目标,表达式的x += y行为指定如下:

  • 如果x.__iadd__定义,x.__iadd__(y)则进行评估。
  • 否则,评估x.__add__是否实施x.__add__(y)
  • 否则,评估y.__radd__是否实施y.__radd__(x)
  • 否则引发错误。

通过该过程获得的第一个结果将被分配回x(除非结果是NotImplemented单例,在这种情况下,查找将继续进行下一步)。

此过程允许支持就地修改的类型实现__iadd__()支持就地修改的类型不需要添加任何新的魔术方法,因为Python会自动降为本质上x = x + y

因此,最后让我们提出您的实际问题–为什么您可以使用增强的赋值运算符将元组添加到列表中。从内存来看,其历史大致是这样的:list.__iadd__()实现该方法只是为了调用list.extend()Python 2.0中已经存在的方法。在Python 2.1中引入迭代器后,该list.extend()方法已更新为可以接受任意迭代器。这些更改的最终结果是my_list += my_tuple从Python 2.1开始起作用。list.__add__()但是,该方法从未被视为支持任意迭代器作为右手参数–对于强类型语言,这被认为是不合适的。

我个人认为,增强运算符的实现最终在Python中过于复杂。它具有许多令人惊讶的副作用,例如以下代码:

t = ([42], [43])
t[0] += [44]

第二行引发TypeError: 'tuple' object does not support item assignment但无论如何都成功执行了操作 - t([42, 44], [43])在执行引发错误的行之后执行。


太棒了!引用PEP特别有用。我在另一端添加了一个链接,该链接指向有关元组列表行为的先前的SO问题。当我回顾2.3左右之前的Python时,与今天相比,它似乎实际上是不可用的……(而且我仍然模棱两可,难以尝试并无法获得1.5来在非常老的Mac上做任何有用的事情)
Karl Knechtel

2

大多数人希望X + = Y等于X = X +Y。确实,Mark Lutz撰写的Python Pocket Reference(第4版)在第57页上说:“以下两种格式大致等效:X = X + Y, X + = Y”。但是,指定Python的人没有使它们等效。可能是一个错误,只要Python继续使用,就会导致沮丧的程序员花费数小时的调试时间,但是现在这就是Python的使用方式。如果X是可变序列类型,则X + = Y等效于X.extend(Y)而不是X = X +Y。


>可能是一个错误,只要Python继续使用,就会导致沮丧的程序员花费数小时的调试时间<-您实际上是否因此而受苦?您似乎是在根据经验说话。我非常想听听你的故事。
Veky

1

正如它的解释 这里,如果array没有实现__iadd__法,b+=(4,)将只是一个缺兵少将的b = b + (4,),但显然它不是,那么array确实实现__iadd__方法。显然__iadd__方法的实现是这样的:

def __iadd__(self, x):
    self.extend(x)

但是,我们知道上面的代码不是__iadd__方法的实际实现,但是我们可以假定并接受像extend方法这样的东西,它可以接受tupple输入。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.