【问题标题】:Why does b+=(4,) work and b = b + (4,) doesn't work when b is a list?当 b 是列表时,为什么 b+=(4,) 有效而 b = b + (4,) 无效?
【发布时间】:2020-02-04 03:59:46
【问题描述】:

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

【问题讨论】:

标签: python python-3.x list tuples


【解决方案1】:

“为什么”问题的问题在于,它们通常可能意味着多种不同的事物。我会尽量回答你可能想到的每一个问题。

“为什么它有可能以不同的方式工作?” 例如回答。 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的类型。

【讨论】:

  • 哎呀,完全正确。而且列表不使用|,所以这有点毁了我的例子。如果我以后想到一个更清晰的例子,我会换掉它。
  • 顺便说一句,集合的| 是一个通勤运算符,但列表的+ 不是。出于这个原因,我不认为关于类型歧义的论点特别强烈。既然运营商不通勤,为什么需要相同的类型?人们可能会同意结果具有 l.h.s. 的类型。另一方面,通过限制list + iterator,鼓励开发人员更明确地表达他们的意图。如果您想创建一个新列表,其中包含来自a 的内容由来自b 的内容扩展而来的内容,那么已经有一种方法可以做到这一点:new = a.copy(); new += b。又多了一条线,但一清二楚。
  • a += b 的行为与a = a + b 不同的原因不是效率。实际上,Guido 认为所选择的行为较少令人困惑。想象一个函数接收列表a 作为参数,然后执行a += [1, 2, 3]。这种语法确实看起来就像是在原地修改列表,而不是创建一个新列表,因此决定它应该按照大多数人对预期结果的直觉行事。但是,该机制还必须适用于像 ints 这样的不可变类型,这导致了当前的设计。
  • 我个人认为该设计实际上更多比简单地将a += b 简化为a = a + b 的简写,就像Ruby 所做的那样,但我可以理解我们是如何做到的.
【解决方案2】:

它们不等价:

b += (4,)

是以下的简写:

b.extend((4,))

+ 连接列表,因此:

b = b + (4,)

你正在尝试将一个元组连接到一个列表

【讨论】:

    【解决方案3】:

    当你这样做时:

    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】:

      来自官方文档,mutable sequence types 两者:

      s += t
      s.extend(t)
      

      定义为:

      t的内容扩展s

      这不同于被定义为:

      s = s + t    # not equivalent in Python!
      

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

      但它也适用于范围和生成器!例如,您还可以这样做:

      s += range(3)
      

      【讨论】:

        【解决方案5】:

        += 这样的“增强”赋值运算符是在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 anyway – 在执行引发错误的行后,t 将是([42, 44], [43])

        【讨论】:

        • 太棒了!参考 PEP 特别有用。我在另一端添加了一个链接,指向之前关于元组列表行为的 SO question。当我回顾 Python 在 2.3 左右之前的样子时,与今天相比,它似乎几乎无法使用......(我仍然模糊地记得尝试和未能让 1.5 在非常旧的 Mac 上做任何有用的事情)
        【解决方案6】:

        大多数人会期望 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 仍在使用,就会导致沮丧的程序员花费数小时的调试时间
        【解决方案7】:

        正如here 解释的那样,如果array 没有实现__iadd__ 方法,b+=(4,) 将只是b = b + (4,) 的简写,但显然不是,所以array 确实实现了__iadd__方法。显然__iadd__ 方法的实现是这样的:

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

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

        【讨论】:

          猜你喜欢
          • 2013-05-13
          • 1970-01-01
          • 2010-12-16
          • 2014-03-29
          • 2018-06-22
          • 1970-01-01
          • 2021-10-06
          • 2022-11-10
          • 2019-12-22
          相关资源
          最近更新 更多