Перегрузка оператора += (iadd)

Рейтинг: 5Ответов: 1Опубликовано: 13.01.2023

Что лучше возвращать из перегруженного магического метода __iadd__: новый объект, или тот же самый, через self?

Ответы

▲ 5Принят

Возвращать нужно новый объект.

Это принципиально важный момент, потому что при использовании оператора += (и других) с изменяемыми объектами могут возникнуть нежелательные проблемы.

Покажу наглядно, что может произойти, если мы переопределим этот оператор и все-таки будем возвращать тот же самый объект (другими словами - сделаем его изменяемым):

class SomeObj:
    def __init__(self, val):
        self.val = val

    def __iadd__(self, other):
        self.val += other.val
        return self

a = SomeObj(5) 
b = SomeObj(6) 
print(id(a))  # 19774544
a += b
print(id(a))  # 19774544
print(a.val)  # 11

Как мы видим, адрес объекта в памяти остался тем же самым, потому что был возвращен исходный объект, и на первый взгляд все работает правильно и без ошибок, но если мы вдруг захотим написать как-то так:

a = SomeObj(5) 
print(a.val)  # 5
b = a
a += SomeObj(1) 
print(a.val)  # 6
print(b.val)  # 6

То увидим, что вследствие возврата того же самого объекта - в переменной b, которая содержит ссылку на исходный объект, изменилось значение атрибута val, не смотря на то, что к этой переменной никто не обращался, в будущем это может быть чревато различными side-эффектами.


В случае же правильной реализации с созданием нового объекта:

class SomeObj:
    def __init__(self, val):
        self.val = val

    def __iadd__(self, other):
        return SomeObj(self.val + other.val) 

a = SomeObj(5) 
b = SomeObj(6) 
print(id(a))  # 39177904
a += b
print(id(a))  # 39176240
print(a.val)  # 11

Заметим, что в данном случае ссылка в переменной a теперь указывает на другой, вновь созданный объект.

Теперь убедимся, что пример с копированием ссылки отрабатывает корректно:

a = SomeObj(5) 
print(a.val)  # 5
b = a
a += SomeObj(1) 
print(a.val)  # 6
print(b.val)  # 5

Все правильно, значение в объекте переменной b не изменилось, потому что в результате операции был создан новый объект.