为什么Python的海象运算符不能用于设置实例属性?

16

我刚刚学到,新的海象运算符 (:=) 不能用于设置实例属性,这是无效的语法(会引发SyntaxError)。

为什么? (您能提供官方文档中提到这一点的链接吗?)

我查看了PEP 572,但没有找到这个问题被记录在哪里。


研究

这个答案提到了这个限制,但没有解释或来源:

你不能在对象属性上使用海象运算符


示例代码

class Foo:
    def __init__(self):
        self.foo: int = 0

    def bar(self, value: int) -> None:
        self.spam(self.foo := value)  # Invalid syntax

    def baz(self, value: int) -> None:
        self.spam(temp := value)
        self.foo = temp

    def spam(self, value: int) -> None:
        """Do something with value."""

试图导入Foo会导致SyntaxError错误:

    self.spam(self.foo := value)
              ^
SyntaxError: cannot use assignment expressions with attribute

3
@SuperStew,您能解释一下为什么吗? - jtbandes
@SuperStew 有时候坏事是两害中较小的一个。我发现 PEP 572 中的例子很有说服力。 - Caleth
@jtbandes 看起来似乎与 Python 的禅意相悖。 - SuperStew
2
我怀疑这是因为对属性的赋值实际上是一个伪装成方法调用的操作,可能会有隐藏的副作用。而对名称的纯赋值则不会。 - chepner
8
“我不喜欢海象运算符”是一个合理的说法。“海象运算符很糟糕,你应该感到难受”则完全不同。让我们不要忘记,海象运算符经过了许多Python专家的严格审查和讨论,他们对Python哲学对语言的意义有着强烈而深入的看法。对他们持不同意见是可以的,但因为这种分歧而告诉陌生人感到难受是不好的。 - inclement
2个回答

21

PEP 572提出了以下目的(重点在于“我”):

这是一个创建一种方式以使用符号NAME:= expr在表达式中分配变量的建议。

self.foo不是一个变量,而是对象属性。

语法和语义部分进一步指定了它:

NAME是一个标识符。

self.foo不是一个标识符,而是由.操作符分隔的两个标识符。

虽然我们经常类似地使用变量和属性,并有时马虎地将self.foo称为变量,但它们并不相同。实际上,对self.foo的赋值只是一个简写方式:

setattr(self, 'foo', temp)

这是可以让你为属性定义getter和setter的功能。如果必须与具有定制setter的属性一起工作,那么它将使赋值表达式的规范和实现变得复杂。

例如,如果setter转换正在被分配的值,那么赋值表达式的值应该是原始值还是转换后的值?

另一方面,变量不能被定制。分配给变量始终具有相同的简单语义,并且很容易使表达式评估为已分配的值。

类似地,您不能使用海象运算符进行切片分配。这是无效的:

foo1[2:4] := foo2[1:3]

8
有趣的是,禁止使用海象操作符 `:=` 给对象属性赋值似乎只存在于Python解析器的文本代码解析成抽象语法树(AST)阶段。而手动创建一个AST语法树并将 `self.foo` 替换 `var` 在 `(var := temp)` 中, 然后使用 compileexec 编译或执行该语法树时,它会像你直觉地期望的那样编译和执行。
所以显然,允许使用海象操作符对对象属性赋值的基础功能是存在的,他们只是选择不让我们使用它,因为他们担心这会使人们写出令人困惑的代码之类的问题。太感谢了...
总之,一种极端的(绝不推荐!)解决方案是对AST进行预编译的修补,将对象属性目标剪切到海象运算符中,然后它可能会按照你的期望运行。(我发现这个方法,因为我已经在做其他原因的AST修补,用对象属性替换简单变量,并且很高兴地发现海象赋值仍然可以工作!)

3
Python的很多功能都是“我们选择不提供给你这个功能,为了你自己好”。 - alexchandel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接