RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1554497
Accepted
Dark Space
Dark Space
Asked:2023-12-04 18:32:32 +0000 UTC2023-12-04 18:32:32 +0000 UTC 2023-12-04 18:32:32 +0000 UTC

元组中列表的 += 和 .append 之间的区别

  • 772

尝试了解元组中列表的 += 和 .append() 方法之间的差异。提出这个问题是为了了解“内部厨房”,而不是为了寻找实际应用。示例代码:

def add_func():
    a = (1, 2, [1, 2])
    a[-1] += [3] # в список значение добавится, но потом падает ошибка

第二个版本:

 def append_func(): 
    a = (1, 2, [1, 2])
    a[-1].append(3) # в данном случае всё корректно отработает 

查看字节码后,我发现有些行有所不同:

print(dis.dis(add_func))
print('--------------')
print(dis.dis(append_func))

  4           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (2)
              4 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (2)
              8 BUILD_LIST               2
             10 BUILD_TUPLE              3
             12 STORE_FAST               0 (a)

  5          14 LOAD_FAST                0 (a)
             16 LOAD_CONST               3 (-1)
             18 DUP_TOP_TWO
             20 BINARY_SUBSCR
             22 LOAD_CONST               4 (3)
             24 BUILD_LIST               1
             26 INPLACE_ADD              # Кажется, это "отголоски" __iadd__
             28 ROT_THREE
             30 STORE_SUBSCR
             32 LOAD_CONST               0 (None)
             34 RETURN_VALUE
--------------
  8           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (2)
              4 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (2)
              8 BUILD_LIST               2
             10 BUILD_TUPLE              3
             12 STORE_FAST               0 (a)

  9          14 LOAD_FAST                0 (a)
             16 LOAD_CONST               3 (-1)
             18 BINARY_SUBSCR
             20 LOAD_METHOD              0 (append)
             22 LOAD_CONST               4 (3)
             24 CALL_METHOD              1
             26 POP_TOP
             28 LOAD_CONST               0 (None)
             30 RETURN_VALUE

字节码的差异让我认为我理解了这个问题,但在那之后我决定阅读SO。
引用这个问题:

+= 是一个赋值。当你使用它时,你实际上是在说“some_list2= some_list2+['something']”。分配涉及重新绑定,因此:

同时他们__iadd__ 在这里写到:

从 API 的角度来看,iadd应该用于就地修改可变对象(返回已变异的对象),而add应该返回某个新的实例。

还有L. Ramalho 的书《Python - to the heights of mastery》中的一句话:

如果左侧的变量与不可变对象关联,并且可以就地修改可变对象,则复合赋值(运算符 +=、*= 等)会创建一个新对象。

坦白说,我对此感到困惑,并发现其中有很多矛盾。如果+=“就地”更改对象,为什么会发生错误?

PS我也不太理解STORE_FAST文档中字节码中的一行:

将 STACK.pop() 存储到本地 co_varnames[var_num] 中。

python
  • 2 2 个回答
  • 94 Views

2 个回答

  • Voted
  1. Best Answer
    Stanislav Volodarskiy
    2023-12-04T20:17:48Z2023-12-04T20:17:48Z

    这是语言缺陷。Python中的构造x += y统一处理如下:

    # x += y
    z = x.__iadd__(y)
    x = z
    

    第一步是访问对象x并要求它对对象执行某些操作y。作为此调用的结果,返回了某个对象z,该对象最终被写入x.

    让我们比较一下__iadd__列表和元组的实现。

    def __iadd__list(self, y):  # для списка
        self.extend(y)
        return self
    
    def __iadd__tuple(self, y):  # для кортежа
        z = create_new_tuple_from(self, y)
        return z
    

    在第一种情况下,列表会修改自身并返回自身。在第二种情况下,这是行不通的——元组是不可变的。我们能做的最好的事情就是将内容复制self到y一个新的元组中并返回它。列表操作具有更好的复杂性——摊余常数。对元组的操作具有线性复杂度。速度上的差异证明了不同实现的合理性。

    翻译代码时,x += yPython 编译器必须创建能够正确处理列表和元组的代码。对于元组来说,x = z需要赋值,否则运算结果将无法到达程序员手中。对于列表来说,此分配是无害的。几乎无害。

    如果左边+=有一个不能改变的结构怎么办,比如元组a[-1]在哪里?a然后,当程序执行时,会调用tuple方法__setitem__。但它总是失败并出现错误 - 你无法更改元组。

    语言作者希望使列表和元组的使用具有多态性,从而导致了这种情况。顺便说一下,如果不改变语言就无法纠正这个问题。

    编译器行为可以改进。

    第一个想法:如果元组是自分配的,则允许对其进行分配。事实上,在列表的情况下,运算符x = z实际上会说x = x。在__setitem__元组方法中,可以捕获自分配并返回控制而不会出现错误。

    第二个想法:在评估右侧之前检查左侧的构造+=是否可分配。这将需要更改语言,但也许这种更改可以向后兼容。

    第三个想法:如果不需要,就不要调用赋值。例如这样:

    # x += y
    z = x.__iadd__(y)
    if x is not z:
        x = z
    

    我认为没有人会纠正这个小缺陷。这甚至不是一个缺陷,而是一个考虑我们为代码在对其处理的值类型知之甚少的情况下工作而预先付出多少代价的理由。

    • 3
  2. wchistow
    2023-12-04T20:10:52Z2023-12-04T20:10:52Z

    考虑操作的字节码s[a] += b:

    >>> dis.dis('s[a] += b')
      1           0 LOAD_NAME                0 (s)
                  2 LOAD_NAME                1 (a)
                  4 DUP_TOP_TWO
                  6 BINARY_SUBSCR  # (1)
                  8 LOAD_NAME                2 (b)
                 10 INPLACE_ADD    # (2)
                 12 ROT_THREE
                 14 STORE_SUBSCR   # (3)
                 16 LOAD_CONST               0 (None)
                 18 RETURN_VALUE
    
    1. 将值压s[a]入栈顶;
    2. 对列表顶部的对象 ( )执行就地赋值 (inplace add, +=) - 此操作会成功,因为它引用了一个可变对象;bs[a]s[a]
    3. 对堆栈顶部的对象(已修改s[a])执行分配以s[a]- 此操作将失败,因为s- 不可变对象(元组)。

    同时,在第二个例子中:

    >>> dis.dis('s[a].append(b)')
      1           0 LOAD_NAME                0 (s)
                  2 LOAD_NAME                1 (a)
                  4 BINARY_SUBSCR
                  6 LOAD_METHOD              2 (append)
                  8 LOAD_NAME                3 (b)
                 10 CALL_METHOD              1
                 12 RETURN_VALUE
    

    没有赋值,只是调用元组元素之一的方法,并且不会出现错误 - 毕竟,元组仅存储对对象的引用,并且当对象更改时引用不会更改。在第一个示例中,尝试保存新链接而不是旧链接,这导致了错误。

    • 2

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5