【问题标题】:Why can I not assign to a named expression (LHS walrus operator)?为什么我不能分配给命名表达式(LHS 海象运算符)?
【发布时间】:2021-03-08 09:14:30
【问题描述】:

赋值给表达式(相对于名称)在 Python 中很常见。例如,这是完全有效的语法:

my.object["with_some"].very_long["expression"] = func(my.object["with_some"].very_long["expression"], my.object["with_some"].very_long["expression"])

但是,如果我尝试使用海象运算符通过将 LHS 设为命名表达式来缩短它,例如

(x:=my.object["with_some"].very_long["expression"]) = func(x, x)

Python 引发 SyntaxError:

语法错误:无法分配给命名表达式

同样,for x[0] in range(5) 是有效的语法(只是非常混乱),而 for (a:=x[0]) in range(5) 又是 SyntaxError: cannot assign to named expression

为什么我不能分配给命名表达式?这是设计使然还是实现?

PEP 572 提到了一些无法使用海象运算符的情况,但除了一种情况外,所有情况都与无括号表达式的语法有关,最后一种情况与 f 字符串有关。与this answer ((self.x := ...)) 中指出的情况不同,在我的例子中,海象运算符中的赋值目标是一个简单的名称/标识符,而不是表达式。 the language reference 也不清楚为什么不允许这样做。 Googling the error message 今天在撰写本文时恰好产生了三个结果:一个 issue about limitations in comprehensions、一个 Stack Overflow chat message expecting hundreds of Hot Network Questions(没有发生)和 an issue in a 3rd-party Python parser;没人帮我。

我无法分配给命名表达式的原因是什么?这是在某处记录或定义的设计规则,还是实施限制?据我所知,它不会导致任何歧义,而且我的用例似乎应该是有效的。

【问题讨论】:

  • 你可以这样做:f(x:=..., x)。但这并不能回答“为什么我不能分配给命名表达式?”你问题的一部分。
  • @hiroprotagonist 是的,我可以,但我仍然需要在赋值运算符的左侧和右侧重复长表达式(在右侧只需一次而不是两次)。
  • 命名表达式的值是表达式的。值不是您可以分配的东西。你认为(x := a+b) = f(x) 是什么意思?
  • @Barmer 我希望(x := a+b) = c 会因cannot assign to operator 而失败,就像a+b = c 没有命名表达式一样。同样,for x[0] in range(5) 是有效语法,for (a:=x[0]) in range(5)SyntaxError: cannot assign to named expression
  • @gerrit,请看我的扩展答案。

标签: python syntax-error python-3.8 walrus-operator


【解决方案1】:

海象运算符仅作为副作用进行赋值 - 它的值不是左侧变量,而是右侧表达式的结果。

所以命名表达式的值

(x:=my.object["with_some"].very_long["expression"])

海象运算符右侧的结果 (:=),i。 e.表达式的结果

my.object["with_some"].very_long["expression"]

让我们表示为result,所以你的命令

(x:=my.object["with_some"].very_long["expression"]) = func(x, x)

相同
result = func(x, x)

现在,result 是一个,而不是一个变量名。也许是None,也许是一个数字,也许是一个特定的列表或其他东西,但不允许出现在赋值运算符的左侧。

因此,分配给命名表达式(至少在大多数情况下)是没有意义的,因此不允许。

【讨论】:

  • 但不一样,因为语法不允许。而且您不再分配 func() 调用中使用的 x 变量。
【解决方案2】:
my.object["with_some"].very_long["expression"] = \
  func(my.object["with_some"].very_long["expression"],
       my.object["with_some"].very_long["expression"])`

是语法糖

my.object["with_some"].very_long.__setitem__(
    "expression",
    func(my.object["with_some"].very_long["expression"], 
         my.object["with_some"].very_long["expression"]))

所以它不像你想象的那么对称。原始长表达式的,而不是表达式本身,作为两个参数传递给func,而原始表达式本身只是一种赋值的目标.

可以,但是,写

x["expression"] = func(
    (x:=my.object["with_some"].very_long)["expression"],
     x["expression"])

x 被分配给脱糖版本的一个常用表达式的值,my.object["with_some"].very_long

赋值表达式必须在赋值的右边,因为它在左边之前被计算。此外,它必须是使用:=第一个 参数,因为函数参数保证从左到右进行计算。


假设我适当地定义了A,这是我用来验证上述是否可行的测试。

class A:
    def __init__(self, y):
        self.b = dict(foo=y)


def func(x, y):
    return x + y


a = A(A("bar"))    
x["foo"] = func((x:=a.b["foo"].b)["foo"], x["foo"])

a.b["foo"].b["foo"] 的新值是 "barbar",正如 func 的定义所预期的那样。

【讨论】:

    【解决方案3】:

    比较:

    my.object["with_some"].very_long["expression"] = func(7, 7)      # OK
    

    7 = func(7, 7)                                                   # error
    

    解释:

    1. 在你的第一个表达中

      my.object["with_some"].very_long["expression"] = \
          func(my.object["with_some"].very_long["expression"],               
                my.object["with_some"].very_long["expression"])
      

      您想要更改对象的 属性 的值(基于其当前属性的值),即做某事

      my.object["with_some"].very_long["expression"] = func(7, 7)
      
    2. 在你的第二个表达中

      (x:=my.object["with_some"].very_long["expression"]) = func(x, x)
      

      赋值运算符(=) 的左侧不是您对象的属性,而是它的值。所以您尝试将某些内容分配给,即做某事

      7 = func(7, 7)
      

    来自PEP 572 - Assignment Expressions, Abstract

    这是创建一种在表达式中分配给变量的方法的提议...

    没有提到在分配的 LHS 中使用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-07-23
      • 1970-01-01
      • 1970-01-01
      • 2022-08-03
      • 2019-12-03
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多