链式分配如何工作?

How do chained assignments work?

提问人:q0987 提问时间:9/30/2011 最后编辑:agfq0987 更新时间:3/29/2022 访问量:23031

问:

引自某事:

>>> x = y = somefunction()

>>> y = somefunction()
>>> x = y

问题:是

x = y = somefunction()

同上

x = somefunction()
y = somefunction()

?

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

python-3.x

评论

6赞 agf 9/30/2011
您可能希望使用该标记而不是,因为它被更广泛地关注,并且您的问题并非特定于 Python 3。您也不需要在标题中重申标签,但最好在某处提及您的 Python 版本。pythonpython-3.x

答:

16赞 Greg Hewgill 9/30/2011 #1

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

import random

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

评论

1赞 q0987 9/30/2011
这是一个有效的替代反例,尽管我在 OP 中没有做出这样的假设。
8赞 asmeurer 9/16/2012
关键是你必须做出相反的假设,即函数总是返回相同的东西,对于你所说的东西。
51赞 Wilduck 9/30/2011 #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
[]

评论

2赞 9/30/2011
好渔获。即使它是一个不可变的值,也会有所不同(除了讨厌的陷阱,如字符串文字和小整数的缓存)。x is y
7赞 andronikus 9/30/2011
是的,很好。当我想偷懒时,我不止一次遇到麻烦。如果 和 应该是不同的列表,那么这就是错误啊。x = y = []xy
0赞 JP Zhang 5/12/2017
我认为 x=y=somefunction 的工作原理是首先将值分配给 y,然后将 x 作为指向 y 的指针
2赞 Wilduck 5/14/2017
@JiapengZhang 这并不完全正确。两者都将引用相同的基础对象,但彼此不引用。考虑。它将打印 ,不是因为名称和 之间没有联系,只是基础值。xyx=y=[]; y=3; print(x)[]3xy
4赞 Rob Cliffe 3/17/2014 #3

x = somefunction()
y = somefunction()

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

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

93赞 Bob Stein 4/1/2016 #4

左先

x = y = some_function()

相当于

temp = some_function()
x = temp
y = temp

记下顺序。最左边的目标首先被分配。(C 语言中的类似表达式可能相反的顺序分配。从有关 Python 作业的文档中:

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

反汇编显示:

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

注意:始终将相同的对象分配给每个目标。因此,正如 @Wilduck 和 @andronikus 所指出的,您可能永远不会想要这样:

x = y = []   # Wrong.

在上述情况下,x 和 y 指的是同一个列表。因为列表是可变的,所以附加到 x 似乎会影响 y。

x = []   # Right.
y = []

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

评论

0赞 aryndin 6/13/2016
你能解释一下吗?你说:“总是为每个目标分配相同的、相同的值(当链接分配时)”,所以有理由认为当我这样做时 - 我得到 , .但后来你说:“你可能永远不想这样做:因为那时 x 和 y 是同一个对象。 - 我真的得到了两个变量,指向同一个对象,而不是两个变量,指向两个不同的空列表。x = y = []temp = []x = tempy = tempx = y = []
0赞 Bob Stein 6/13/2016
@jumpjet67更好?
0赞 aryndin 6/13/2016
完美是没有限制的;-)。实际上,答案很好,解释得很好。
4赞 Tom Karzes 9/2/2019
很好的解释。我觉得这种行为不直观,并被它烧毁了。我本来以为它会表现得像,但由于分配顺序,它实际上失败了,但失败了。我想我可以做一些事情来获得想要的行为,但这对我来说似乎真的很误导。x = x.a = yx.a = y; x = yx = y; x.a = yx.a = x = y
5赞 Bob Stein 11/17/2020
@grofte 不确定我在几次重读后是否得到了你的例子。但我认为,棘手的简洁并不好。代码应该是人类可读的。副作用应重新转化为明显、明确的后果。你未来的大脑是一个温柔的东西。
9赞 MariusSiuram 6/14/2016 #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 变量的内容是相同的,但指令将持续 10 秒,而其对应的指令将持续 20 秒。x = y = slow_is_computer_on()x = slow_is_computer_on() ; y = slow_is_computer_on()

如果函数没有副作用并以确定性方式返回不可变(给定其输入),则几乎相同。

例如:

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)

因为在这种情况下,并且是同一个对象,执行像 whould 这样的操作意味着两者都包含对现在有 4 个元素的列表的引用。xyx.append(42)xy

如果功能有副作用,那就不一样了

将打印视为副作用(我认为这有效,但可以使用其他示例来代替):

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

7赞 Thomas Baruchel 12/14/2019 #6

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

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

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

只需输入:

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

然后通过打字来找点乐子,然后,然后,直到你到达最后......L[1]L[1][1]L[1][1][1]

结论

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

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

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

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