When is “i += x” different from “i = i + x” in Python?

后端 未结 3 593
栀梦
栀梦 2020-11-22 01:20

I was told that += can have different effects than the standard notation of i = i +. Is there a case in which i += 1 would be differen

3条回答
  •  迷失自我
    2020-11-22 01:35

    This depends entirely on the object i.

    += calls the __iadd__ method (if it exists -- falling back on __add__ if it doesn't exist) whereas + calls the __add__ method1 or the __radd__ method in a few cases2.

    From an API perspective, __iadd__ is supposed to be used for modifying mutable objects in place (returning the object which was mutated) whereas __add__ should return a new instance of something. For immutable objects, both methods return a new instance, but __iadd__ will put the new instance in the current namespace with the same name that the old instance had. This is why

    i = 1
    i += 1
    

    seems to increment i. In reality, you get a new integer and assign it "on top of" i -- losing one reference to the old integer. In this case, i += 1 is exactly the same as i = i + 1. But, with most mutable objects, it's a different story:

    As a concrete example:

    a = [1, 2, 3]
    b = a
    b += [1, 2, 3]
    print a  #[1, 2, 3, 1, 2, 3]
    print b  #[1, 2, 3, 1, 2, 3]
    

    compared to:

    a = [1, 2, 3]
    b = a
    b = b + [1, 2, 3]
    print a #[1, 2, 3]
    print b #[1, 2, 3, 1, 2, 3]
    

    notice how in the first example, since b and a reference the same object, when I use += on b, it actually changes b (and a sees that change too -- After all, it's referencing the same list). In the second case however, when I do b = b + [1, 2, 3], this takes the list that b is referencing and concatenates it with a new list [1, 2, 3]. It then stores the concatenated list in the current namespace as b -- With no regard for what b was the line before.


    1In the expression x + y, if x.__add__ isn't implemented or if x.__add__(y) returns NotImplemented and x and y have different types, then x + y tries to call y.__radd__(x). So, in the case where you have

    foo_instance += bar_instance

    if Foo doesn't implement __add__ or __iadd__ then the result here is the same as

    foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

    2In the expression foo_instance + bar_instance, bar_instance.__radd__ will be tried before foo_instance.__add__ if the type of bar_instance is a subclass of the type of foo_instance (e.g. issubclass(Bar, Foo)). The rationale for this is that Bar is in some sense a "higher-level" object than Foo so Bar should get the option of overriding Foo's behavior.

提交回复
热议问题