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

时间:2019-10-06 17:29:22

标签: python python-3.x list tuples

如果我们服用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,)希望得到相同的结果,但是没有用。

7 个答案:

答案 0 :(得分:70)

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

“为什么它的工作方式可能不同?” ,例如this。基本上,+=尝试使用不同的对象方法:__iadd__(仅在左侧选中),与__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并不是真正意义上的效率,但是这些决定是很久以前做出的。

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

答案 1 :(得分:21)

它们不是等效的:

b += (4,)

的简写:

b.extend((4,))

+连接列表时,通过以下方式:

b = b + (4,)

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

答案 2 :(得分: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,)) 

仅接受列表对象。

答案 3 :(得分:4)

对于mutable sequence types而言,都来自官方文档:

s += t
s.extend(t)

定义为:

  

s的内容扩展到t

与定义不同的地方是

s = s + t    # not equivalent in Python!

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

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

s += range(3)

答案 4 :(得分: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__()方法的实现是为了简单地调用Python 2.0中已经存在的list.extend()方法。在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 assignmentbut the operation is successfully performed anywayt将在执行引起错误的那一行后为([42, 44], [43])

答案 5 :(得分: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。

答案 6 :(得分:1)

正如here所述,如果chi**2未实现array方法,则__iadd__只是b+=(4,)的简写,但显然不是,因此b = b + (4,)确实实现了array方法。显然__iadd__方法的实现是这样的:

__iadd__

但是我们知道上面的代码不是def __iadd__(self, x): self.extend(x) 方法的实际实现,但是我们可以假设并接受类似__iadd__方法的东西,它接受extend输入。