【问题标题】:How do chained assignments work?链式作业如何工作?
【发布时间】:2011-11-27 22:41:45
【问题描述】:

引用某事:

>>> x = y = somefunction()

相同
>>> y = somefunction()
>>> x = y

问题:是

x = y = somefunction()

一样
x = somefunction()
y = somefunction()

?

根据我的理解,它们应该是相同的,因为somefunction 只能返回一个值。

【问题讨论】:

  • 您可能希望使用python 标签而不是python-3.x,因为它受到更广泛的关注,而且您的问题并非特定于 Python 3。您也不需要在标题,但最好在某处提及您的 Python 版本。

标签: python python-3.x


【解决方案1】:

如果somefunction() 每次调用都返回不同的值怎么办?

import random

x = random.random()
y = random.random()

【讨论】:

  • 这是一个有效的替代反例,尽管我在 OP 中没有做出这样的假设。
  • 关键是你必须做出相反的假设,即函数总是返回相同的东西,对于你所说的持有。
【解决方案2】:

如果somefunction 返回一个可变值,它们的工作方式不一定相同。考虑:

>>> def somefunction():
...     return []
... 
>>> x = y = somefunction()
>>> x.append(4)
>>> x
[4]
>>> y
[4]
>>> x = somefunction(); y = somefunction()
>>> x.append(3)
>>> x
[3]
>>> y
[]

【讨论】:

  • 好收获。即使它是一个不可变的值,x is y 也会有所不同(避免像缓存字符串文字和小整数这样令人讨厌的陷阱)。
  • 是的,很好。当我想偷懒做x = y = []时,我不止一次遇到麻烦。如果 xy 应该是不同的列表,那么这就是错误。
  • 我认为 x=y=somefunction 的工作原理是首先为 y 赋值,然后将 x 赋值为指向 y 的指针
  • @JiapengZhang 这不太正确。 xy 都将引用相同的底层对象,但不会相互引用。考虑x=y=[]; y=3; print(x)。它将打印[],而不是3,因为名称xy 之间没有联系,只是基础值。
【解决方案3】:

x = somefunction()
y = somefunction()

somefunction 将被调用两次而不是一次。

即使每次返回相同的结果,如果返回结果需要一分钟,这将是一个明显的问题! 或者如果它有副作用,例如询问用户的密码。

【讨论】:

    【解决方案4】:

    都没有。

    x = y = some_function()
    

    等价于

    temp = some_function()
    x = temp
    y = temp
    

    注意顺序。 最左边的目标首先分配。 (C may 中的类似表达式在 opposite order 中赋值。)来自Python assignment 上的文档:

    ...将单个结果对象分配给每个目标列表,从左到右。

    反汇编显示:

    >>> def chained_assignment():
    ...     x = y = some_function()
    ...
    >>> import dis
    >>> dis.dis(chained_assignment)
      2           0 LOAD_GLOBAL              0 (some_function)
                  3 CALL_FUNCTION            0
                  6 DUP_TOP
                  7 STORE_FAST               0 (x)
                 10 STORE_FAST               1 (y)
                 13 LOAD_CONST               0 (None)
                 16 RETURN_VALUE
    

    注意:same object 始终分配给每个目标。所以正如@Wilduck 和@andronikus 指出的那样,你可能永远不会想要这个:

    x = y = []   # Wrong.
    

    在上述情况下,x 和 y 指的是同一个列表。因为列表是mutable,附加到x 将seem to affect y。

    x = []   # Right.
    y = []
    

    现在您有两个名称引用两个不同的空列表。

    【讨论】:

    • 你能解释一下吗?你说:“相同的,相同的值总是分配给每个目标(当链式分配时)”,所以有理由认为当我做x = y = [] - 我得到temp = []x = tempy = temp。但后来你说:“你可能永远不想这样做:x = y = [] 因为那样 x 和 y 是同一个对象。” - 我真的得到了两个变量,指向同一个对象,而不是两个变量,指向两个不同的空列表。
    • @jumpjet67 更好吗?
    • 完美无止境 ;-)。实际上很好且解释清楚的答案。
    • 很好的解释。我发现这种行为不直观,并被它烧毁。我有x = x.a = y,希望它表现得像x.a = y; x = y,但由于分配顺序,它有效地做了x = y; x.a = y,但失败得相当可怕。我想我可以通过x.a = x = y 来获得所需的行为,但这对我来说似乎真的很误导。
    • @grofte 在重读几次后我不确定我是否得到了你的例子。但我建议复杂的简洁并不好。代码应该是人类可读的。副作用应该重新定义为明显的、明确的后果。你未来的大脑是一个温柔的东西。
    【解决方案5】:

    如果函数没有副作用并以确定的方式返回一个单例(给定它的输入),它会产生相同的

    例如:

    def is_computer_on():
        return True
    
    x = y = is_computer_on()
    

    def get_that_constant():
        return some_immutable_global_constant
    

    注意,结果是一样的,但实现结果的过程不一样:

    def slow_is_computer_on():
        sleep(10)
        return True
    

    x 和 y 变量的内容相同,但指令 x = y = slow_is_computer_on() 会持续 10 秒,而对应的指令 x = slow_is_computer_on() ; y = slow_is_computer_on() 会持续 20 秒。

    如果函数没有副作用并且以确定的方式返回一个不可变对象(给定它的输入),这将是几乎相同的。

    例如:

    def count_three(i):
        return (i+1, i+2, i+3)
    
    x = y = count_three(42)
    

    请注意,上一节中解释的相同捕获也适用。

    为什么我说几乎?正因为如此:

    x = y = count_three(42)
    x is y  # <- is True
    
    x = count_three(42)
    y = count_three(42)
    x is y  # <- is False
    

    好的,使用is 有点奇怪,但这说明返回不一样。这对于可变情况很重要:

    如果函数返回一个可变的,这很危险并且可能导致错误

    这个问题也已经回答了。为了完整起见,我重演了这个论点:

    def mutable_count_three(i):
        return [i+1, i+2, i+3]
    
    x = y = mutable_count_three(i)
    

    因为在那种情况下xy 是同一个对象,所以执行x.append(42) 之类的操作意味着xy 都持有对现在有4 个元素的列表的引用。

    如果函数有副作用就不一样了

    考虑到打印的副作用(我认为这是有效的,但可以使用其他示例):

    def is_computer_on_with_side_effect():
        print "Hello world, I have been called!"
        return True
    
    x = y = is_computer_on_with_side_effect()  # One print
    
    # The following are *two* prints:
    x = is_computer_on_with_side_effect()
    y = is_computer_on_with_side_effect()
    

    它可能是一个更复杂或更微妙的副作用,而不是打印,但事实仍然存在:该方法被调用一次或两次,这可能会导致不同的行为。

    如果函数在给定其输入的情况下是非确定性的,那就不一样了

    也许是一个简单的随机方法:

    def throw_dice():
        # This is a 2d6 throw:
        return random.randint(1,6) + random.randint(1,6)
    
    x = y = throw_dice()  # x and y will have the same value
    
    # The following may lead to different values:
    x = throw_dice()
    y = throw_dice()
    

    但是,与时钟、全局计数器、系统内容等相关的事物在给定输入的情况下具有不确定性是明智的,在这些情况下,xy 的值可能会有所不同。

    【讨论】:

      【解决方案6】:

      正如 Bob Stein 已经说过的,分配顺序很重要;看看下面这个非常有趣的案例:

      L = L[1] = [42, None]
      

      现在,L 包含什么?您必须了解单个对象最初是[42, None],它被分配给L;最后,执行类似L[1] = L 的操作。因此,您创建了一些循环无限“列表”(“列表”一词在这里更类似于 Lisp 中的一些 CONS,标量 42CAR,列表本身是 CDR)。

      只需输入:

      >>> L
      [42, [...]]
      

      然后输入L[1],然后输入L[1][1],然后输入L[1][1][1],直到最后...

      结论

      这个例子比其他答案中的其他例子更难理解,但另一方面,你可以更快地看到

      L = L[1] = [42, None]
      

      不一样

      L[1] = L = [42, None]
      

      因为如果之前没有定义L,第二个将引发异常,而第一个将始终有效。

      【讨论】:

        猜你喜欢
        • 2021-01-02
        • 2018-07-15
        • 2021-04-25
        • 1970-01-01
        • 1970-01-01
        • 2014-02-23
        相关资源
        最近更新 更多