【问题标题】:Why must Python list addition be homogenous?为什么 Python 列表加法必须是同质的?
【发布时间】:2012-12-04 00:12:29
【问题描述】:

任何熟悉 Python 内部结构(CPython 或其他实现)的人都可以解释为什么列表添加必须是同质的吗:

In [1]: x = [1]

In [2]: x+"foo"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
C:\Users\Marcin\<ipython-input-2-94cd84126ddc> in <module>()
----> 1 x+"foo"

TypeError: can only concatenate list (not "str") to list

In [3]: x+="foo"

In [4]: x
Out[4]: [1, 'f', 'o', 'o']

为什么上面的x+"foo" 不应该返回与上面脚本中x 的最终值相同的值?

这个问题来自 NPE 的问题:Is the behaviour of Python's list += iterable documented anywhere?

更新:我知道异构+= 不需要工作(但确实如此),同样,异构+ 也不需要是错误。这个问题是关于为什么做出后一种选择。

说将序列添加到列表中的结果是不确定的,这太过分了。如果这是一个充分的反对意见,那么防止异构+= 将是有意义的。更新 2:特别是,python 总是将运算符调用委托给左侧操作数,因此不会出现“什么是正确的事情”的问题:左侧对象始终管理(除非它委托给右侧)。

更新 3:对于任何认为这是一个设计决定的人,请解释 (a) 为什么它没有记录在案;或 (b) 记录在案的地方。

Update4:“[1] + (2, ) 应该返回什么?”它应该返回一个与变量x 的值相等的结果值,该变量最初在x+=(2, ) 之后立即持有[1]。这个结果是明确定义的。

【问题讨论】:

    标签: python language-design language-implementation python-internals


    【解决方案1】:

    来自 Python 之禅:

    面对模棱两可,拒绝猜测的诱惑。

    让我们看看这里发生了什么:

    x + y
    

    这给了我们一个值,但是是什么类型的呢?当我们在现实生活中添加东西时,我们期望类型与输入类型相同,但如果它们是不同的呢?好吧,在现实世界中,我们拒绝添加1"a",这没有任何意义。

    如果我们有相似的类型怎么办?在现实世界中,我们看上下文。计算机无法做到这一点,所以它必须猜测。 Python 选择左操作数并让其决定。由于缺乏上下文,您的问题出现了。

    假设程序员想做["a"] + "bc" - 这可能意味着他们想要"abc"["a", "b", "c"]。目前,解决方案是在第一个操作数上调用"".join() 或在第二个操作数上调用list(),这允许程序员做他们想做的事情并且清晰明确。

    您的建议是让 Python 进行猜测(通过内置规则来选择给定的操作数),因此程序员可以通过加法来做同样的事情 - 为什么这样更好?这只是意味着更容易错误地得到错误的类型,我们必须记住一个任意规则(左操作数选择类型)。相反,我们得到一个错误,因此我们可以为 Python 提供进行正确调用所需的信息。

    那么为什么+= 不同呢?嗯,那是因为我们给了 Python 那个上下文。通过就地操作,我们告诉 Python 修改一个值,因此我们知道我们正在处理我们正在修改的值的类型。这是 Python 进行正确调用所需的上下文,因此我们无需猜测。

    当我谈论猜测时,我是在谈论 Python 猜测程序员的意图。这是 Python 经常做的事情——参见 3.x 中的除法。 /做浮点除法,修正2.x中为整数除法的错误。

    这是因为我们在尝试除法时隐式要求浮点除法。 Python考虑到了这一点,它的操作就是按照这个来完成的。同样,这里是关于猜测意图。当我们添加+ 时,我们的意图并不明确。我们用+=的时候就很清楚了。

    【讨论】:

    • “面对歧义,拒绝猜测的诱惑。”这个答案没有解释(a)为什么涉及任何猜测;或 (b) 为什么在异构 += 中没有猜测。后者的结果是明确定义的,因为 python 总是将运算符调用委托给左操作数。 “因为我们正在给 Python 提供上下文。” Python always 具有该上下文,因为如何评估运算符调用。
    • += 确实“猜测”,我会争辩。至少,a = a + ba += b 之间的区别令人困惑。请注意,对于a = 1b = 0.5a += b 给出a == 1.5
    • @delnan 是的。我在问为什么会有差异,因为这两个选择似乎不一致。我注意到列表加法永远不是可交换的。
    • @Lattyware 对我来说,x += y 应该与x = x + y 具有相同的结果是最有意义的。如果它们不同,我会感到非常惊讶(在这种情况下我就是这样)。在这种情况下,我希望它们都不起作用。
    • @Marcin 这就是为什么x += y 其中x 是一个列表而y 不是不应该工作的论点,我想说也许这是对的,但它不是使x = x + y 在上下文中起作用的论点,这肯定会使事情变得不那么明显。
    【解决方案2】:

    这些错误报告表明这种设计怪癖是一个错误。

    Issue12318:

    是的,这是预期的行为,是的,这是不一致的。

    这种情况已经很久了,Guido 说他不会再这样做了(这在他的遗憾清单中)。但是,我们不会通过更改代码来破坏代码(list.__iadd__ 的工作方式类似于 list.extend)。

    Issue575536:

    意图是list.__iadd__ 完全对应于 list.extend()。没有必要过度概括 list.__add__() 太:这是一个人们不知道的功能 想对类似 Martin 的例子感到惊讶可以避免 他们通过使用普通的+ 列表。

    (当然,我们当中有些人对此行为感到非常惊讶,包括打开该错误报告的开发人员)。

    (感谢@Mouad 找到这些)。

    【讨论】:

    • 我认为我的 answer 已被删除,因为有人认为这不是答案!但我不介意 b/c 我们都从你的问题中学到了谢谢 :) 并感谢你从我的回答中提取了好的部分;我还想补充一下 Raymond 引用 GvR he wouldn't do it again (it's in his list of regrets) 的话,+1 顺便说一句,我认为你应该接受你的回答。
    • @mouad 是的。我想知道为什么你的答案被删除了。已被 ThiefMaster 删除,没有任何评论。
    【解决方案3】:

    我相信 Python 设计人员以这种方式进行了加法,因此“+”运算符在结果类型方面保持一致可交换:type(a + b) == type(b + a)

    每个人都希望 1 + 2 与 2 + 1 具有相同的结果。您会希望 [1] + 'foo' 与 'foo' + [1] 相同吗?如果是,结果应该是什么?

    您有 3 个选择,您可以选择左操作数作为结果类型,选择右操作数作为结果类型,或者引发错误。

    += 不可交换,因为它包含赋值。在这种情况下,您要么选择左操作数作为结果类型,要么抛出。这里的惊喜是a += ba = a + b 不同。 a += b 不会用英语翻译成“将 a 添加到 b 并将结果分配给 a”。它翻译为“将a添加到b”。这就是为什么它不适用于字符串或元组等不可变对象的原因。

    感谢 cmets。编辑了帖子。

    【讨论】:

    • -1 列表添加不可交换:([1] + [2]) != ([2]+[1]).
    • @Marcin 虽然他对此有点模糊,但第三段澄清说他在谈论结果的类型,这是正确的。不一定是这个问题的答案,但列表加法在结果类型方面确实是可交换的。
    • @delnan 当然可以,但是如果任何序列类型都允许异质添加,那么它就会被破坏。
    • @Marcin 是否任何序列类型都允许异构?
    • @delnan 当然,您想要创建的任何序列类型都可以做到这一点。 pastebin.com/N16EK4BM
    【解决方案4】:

    我的猜测是 Python 是强类型的,这里没有明确的指示正确。您是在要求 Python 附加字符串本身,还是将字符串转换为列表(这是您表示希望它执行的操作)?

    请记住,显式优于隐式。在最常见的情况下,这些猜测都不是正确的,并且您不小心尝试做一些您不打算做的事情。提出一个 TypeError 并让你解决它是最安全、最 Pythonic 的事情。

    【讨论】:

    • "你是在要求 Python 自己附加字符串" 这到底是什么意思?该操作定义得很好,+= 可以工作。没有简单的逻辑或期望为什么这两个操作不应该具有相同的(如在值相等的)结果。
    • “提出一个 TypeError 并让你解决它是最安全、最 Pythonic 的事情。”冲洗并重复,直到您使用 C++ 编程。
    • @Marcin list += string 的明显结果是list = list + [string]list.append(string)。我不认为这比list += list(string) 更“明显”正确。你我对正确结果的看法不同,所以我认为合理的答案是“以上都不是”。
    • 但是list.__iadd__ 实际上选择了一个选项而忽略了另一个(它充当list.extend,附加了可迭代的项目)。根据你的论点,它不会。
    • @KirkStrauser list.__iadd__list.extend 都不投任何东西。它们只适用于任何可迭代对象,而字符串恰好是单字符字符串的可迭代对象。
    猜你喜欢
    • 2017-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-13
    • 1970-01-01
    • 2011-06-14
    • 2016-09-30
    • 1970-01-01
    相关资源
    最近更新 更多