运算符+=在Python中返回什么

原始问题

当我试图在 stackoverflow 上回答另一个人关于Python=+=Python之间的区别的问题时,我遇到了以下问题:

class Foo:
    def __init__(self, value, name="default"):
        self.value = value
        self.name = name
        
    def __add__(self, that):
        return Foo(self.value + that.value)
    
    def __iadd__(self, that):
        self.value = self.value + that.value
        return self
    
    def __str__(self):
        return "name: {}, value: {:d}".format(self.name, self.value)
    
a = Foo(1, 'alice')
b = Foo(2, 'bob')
print(a+=b)

最后一个print电话没有成功,给了我这个:

File "<ipython-input-8-0faa82ba9e4a>", line 3
    print(a+=b)
            ^
SyntaxError: invalid syntax

我不知道为什么这不起作用。也许它与关键字参数传递机制有关?我只是找不到关于这个主题的任何资源,因为重载的__iadd__方法已经返回了一个Foo对象。

************** 更新 ******************

如果我__iadd__像这样更改方法(只需删除return语句):

...
    def __iadd__(self, that):
        print("__iadd__ was called")
        self.value = self.value + that.value

a = Foo(1, 'alice')
b = Foo(2, 'bob')
a += b
print(a)  # Outputs: None

所以,returnin 中的最终语句__iadd__确实是必需的。但它并没有像我想象的那样运行(我来自 C/C++ 背景,所以这种行为对我来说有点奇怪)

************************* 第二次更新 *********************** *********

我几乎忘记了=在 Python 中是由语句而不是表达式组成的。中的return语句__iadd__和我使用其他语言的经验给了我一种+=可以用作表达的错觉。

正如 Python 文档中所述,__add__用于构造一个新对象。__iadd__专为就地修改而设计。但这一切都取决于实现。虽然__iadd__返回一个对象,但这个对象以某种方式被语言“拦截”并重新分配给左侧运算符,最终效果是,__iadd__仍然是一个语句,而不是一个表达式。如果我错了,请纠正我。此外,我没有找到任何资源确认这+=是一个声明。

概括

  1. 一个普通的代码,比如说a = 1是一个赋值语句。不允许用作函数参数。但是,关键字参数传递不受此限制:print('Hello world', end='')仍然有效。
    • x = x + y相当于x = x.__add__(y)
    • x += y等价于x = x.__iadd__(y),请查看文档以获取更多详细信息。
  2. 一个例子:
class Foo:
    def __init__(self, value, name="default"):
        self.value = value
        self.name = name

    def __add__(self, that):
        return Foo(self.value + that.value)

    def __iadd__(self, that):
        self.value = self.value + that.value
        return self

    def __str__(self):
        return "name: {}, value: {:d}".format(self.name, self.value)

a = Foo(1, 'alice')
b = Foo(2, 'bob')
c = a + b # objects a and b are unchanged
a += b    # object a is changed
print(c) # name: default, value: 3
print(a) # name: alice, value: 3

回答

赋值/就地操作不能被视为可以传递的表达式,这是设计使然。

除此之外,Guido 还想避免臭名昭著的 C 错字

if (a=b)  // user actually wanted to test a==b

但是 python 3.8 提供了一种新的赋值方法,称为赋值表达式,它允许将赋值的值作为参数传递(这在列表推导中非常方便)。

您仍然无法进行就地添加,但您可以添加然后分配:

>> a=2
>> b=3
>> print(a:=a+b)
5


回答

Python 的语义可能令人困惑。作为说明性示例,请尝试完成以下示例:

class Foo:
    def __init__(self, value):
        self.value = value

    def __iadd__(self, other):
        self.value = self.value + other.value
        return 'did iadd'

a = Foo(1)
c = a
c += Foo(2)

print((c, a.value))

应该导致('did iadd', 3)被打印

根据文档:

x += y 相当于 x = x.__iadd__(y)

契约__iadd__是你应该以这样一种方式实现它,即 的语义x = x + y评估为“相同” x += y。“相同”取决于问题的细节,例如对于不可变对象(例如整数或字符串),这将需要分配一个新对象并更新指向的引用,例如:

a = 500  # move out of the "interned" number space
c = a
a += 1
print(id(a), id(c))

应该给你两个不同的数字,在 CPython 中这将是两个 int 对象的地址,既不同又不可变。对于其他数据类型,允许发生可变/就地更新可能很有用,例如出于效率或其他原因。__iadd__允许类实现选择默认值的语义。

每种语言都有自己的特点,因此我建议不要试图将它与 C++ 进行过于字面的比较。特别是因为 C++ 有很多历史包袱,它被迫随身携带。


以上是运算符+=在Python中返回什么的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>