【问题标题】:Python unpacking gotcha (unexpected behavior) [duplicate]Python解包陷阱(意外行为)[重复]
【发布时间】:2020-04-04 04:00:03
【问题描述】:

谁能解释这里发生了什么?为什么会这样?

>>> b = "1984"
>>> a = b, c = "AB"
>>> print(a, b, c)
'AB', 'A', 'B'

这种行为真的让我大吃一惊。 找到这个here

【问题讨论】:

  • 我不是 Python 内部的专家,但似乎第二行被解析为 a = (b, c = "AB")
  • @RobinZigmond 实际上它更等同于a = "AB",然后是b, c = a
  • a 取最右边项的值,显然解包b, c = "AB" 是可以的,所以这是两种行为的组合。
  • 因为a = b = "value" 很有用,而[a,b] = (1,2) 很有用,所以两者都受支持。您只是期望它具有与使用分号相同的行为。 a = 1 ; b = 2 是在同一行进行多个独立赋值的合理方式。

标签: python iterable-unpacking


【解决方案1】:

赋值是一个语句;它被定义为从左到右将最右侧分配给各种目标。比较干的language grammar description is

赋值语句计算表达式列表(请记住,这可以是单个表达式或逗号分隔的列表,后者产生一个元组)并将单个结果对象从左到右分配给每个目标列表。

例如:

a = b = 1

1 分配给a,然后再次将其分配给b,与您所做的大致相同:

__specialtmp = 1
a = __specialtmp
b = __specialtmp

其中__specialtmp 是一个未命名的临时存储位置(在 CPython 上,它只是加载到程序堆栈的顶部,然后复制到两个引用中,然后每个引用都被弹出以进行赋值)。

这只是添加了可迭代的解包;以同样的方式扩展你的代码,它看起来像:

__specialtmp = "AB"
a = __specialtmp  # Assigns original value to a
b, c = __specialtmp  # Unpacks string as iterable of its characters, assigning "A" to b, and "B" to c

请注意,这并不总是有效;如果被解包的是一个迭代器,并且您将first分配给解包的名称,则迭代器将被耗尽,并且没有任何有用的东西可用于第二个赋值:

b, c = [*a] = iter("AB")

这会将"A" 解压缩为b,将"B" 解压缩为c,但是当它到达a 时,简单的[*a] = iter("AB") 将变为["A", "B"](捕获“剩余”的星号语法值到list),在这种情况下,迭代器在填充bca 得到什么都没有(空的list[])。

重点是,虽然这个技巧有效,但我一般不会推荐它。将多个名称初始化为相同的不可变值很好,但否则可能会咬你。

【讨论】:

  • __specialtmp 总是最右边的值吗?
  • @roganjosh:这是最右边 = 分隔符后面的表达式的结果(显然,如果 ==:= 或字符串文字或任何涉及的内容,它不是最后一个文字 @ 987654347@ 字符)。
  • “显然”部分是我正在考虑的 :) == 将评估为布尔值,所以我可以理解为我所指的“价值”,但 := 我'我不太确定它的行为方式,因为您可能在x = y = x 链中有重复的名称(我对海象没有很好的把握)?
  • @AlexandrShurigin:不。 “表达式列表”是最右边等号右侧的部分(其结果在目标列表中分配)。另一部分是“目标列表”。
  • @roganjosh:海象是一种表达方式。在开始分配给目标之前,表达式已被完全评估,因此a = b = (c := 1) 分配给c,然后分配给a,然后分配给b
【解决方案2】:

这么酷的问题!带来很多乐趣! :) 可用于面试:)

好的,我们到了

>>> b = "1984"
>>> a = b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = (b, c) = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>>

在python中多次赋值,可以省略(...),看起来python解析这行类似于2行

a = "AB"
b, c = "AB" # which is equal to (b, c) = "AB"

更多示例

>>> a = b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = (b, c) = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = "AB"
>>> b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>>

它可以很好地使用列表:)

>>> a = [b, c] = 'AB'
>>> print((a,b,c))
('AB', 'A', 'B')
>>>

更多示例:

【讨论】:

  • 这里没有tuples,引用它们有点误导;解包使用逗号,但不会生成tuples。就此而言,如果涉及tuples (在最右边的等号的右侧),则不需要括号(不是括号使非-空tuple,它是逗号,括号仅用于解决优先/歧义问题)。
  • 谢谢,已修复:)
  • 这是一个常见的错误(即使在教程中),但它仍然是一个错误。在实现或逻辑行为中不涉及tuples。 a, b = b, a 确实涉及逻辑 tuple (在右侧),实现省略,但 a, b 部分是语法的不相关部分(恰好与 tuples 的方式大致对称用于右侧的表达式)。
  • 没有实际的lists 参与a = [b, c] = 'AB'。该语法允许您根据需要用圆括号或方括号将“target_list”(与list 数据类型无关的语法术语)括起来,但它仍然不是listtuple。您可以阅读the grammar description,但请注意,它不是为普通人设计的,而是为解析器设计的,因此措辞在很多方面都非常严格且不直观。
【解决方案3】:

让我们把它简化一点。我们来看下面的案例

>>> b = 1
>>> b, c = (0, 2)
>>> print(b, c)
0, 2

b 是 0 而不是 1 是否令人惊讶?这不应该是因为我们在调用b, c = (0, 2) 时将b 分配给0 并将c 分配给2,这要归功于元组解包。

现在要解决问题的另一部分,让我们举这个例子

>>> b = 1
>>> a = b = 0
>>> print (b)
0

b 是 0 而不是 1 是否又令人惊讶?同样,它不应该是因为在调用a = b = 0 时,我们已经将ab 都分配给了0,并进行了多次分配。

所以回到问题上来,a = b, c = "AB" 只是这两种行为的组合。 b, c = "AB" 将把"A" 解包到b"B"c,我们还将"AB" 分配给a。虽然看起来我们正在分配a = b,但实际上我们只是在执行以下两行

>>> b = "1984"
>>> b, c = "AB"
>>> a = "AB"

希望这可以分解元组拆包发生的位置和分配发生的位置,并且它不会像看起来那样令人困惑。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    相关资源
    最近更新 更多