Ответ 1
Почему необходимо возвращать что-либо из этих методов, если в документации указано, что, если они будут реализованы, они все равно должны делать что-то на месте? Почему операторы расширенного присваивания просто не выполняют избыточное присваивание в случае реализации
__iadd__
?
Одна из причин заключается в том, чтобы заставить их быть операторами вместо выражений.
Большей причиной является то, что задание не всегда является излишним. В случае, когда левая часть - это просто переменная, обязательно, после мутации объекта, повторное связывание этого объекта с именем, к которому оно уже привязано, обычно не требуется.
Но как насчет случая, когда левая сторона является более сложной целью назначения? Помните, что вы можете назначать и дополнять-присваивать подпискам, срезам и атрибутам, например a[1] += 2
или a.b -= 2
. В этом случае вы на самом деле вызываете __setitem__
или __setattr__
для объекта, а не просто привязываете переменную.
Кроме того, стоит отметить, что "избыточное присвоение" - не совсем дорогая операция. Это не С++, где любое присваивание может вызвать вызов пользовательского оператора присваивания для значения. (Это может привести к вызову пользовательского оператора сеттера на объекте, что значение является элементом, подслоем или атрибутом, и это может быть дорогостоящим... но в этом случае оно не является избыточным, как объяснялось выше.)
И последняя причина напрямую связана с вашим вторым вопросом: вы почти всегда хотите вернуть self
из __ispam__
, но почти всегда это не всегда. И если __iadd__
никогда не возвращался self
, назначение было бы явно необходимым.
При каких обстоятельствах имеет смысл возвращать что-то другое, кроме себя, из расширенного метода присваивания?
Вы просмотрели важный связанный бит здесь:
Эти методы должны пытаться выполнять операцию на месте (модифицировать self)
В любом случае, когда они не могут выполнять операцию на месте, но могут делать что-то еще, вероятно, будет разумно вернуть что-то другое, кроме self
.
Представьте себе объект, который использовал реализацию копирования на запись, мутируя на месте, если он был единственной копией, но в противном случае создавал новую копию. Вы не можете этого сделать, не выполнив __iadd__
и не позволяя +=
вернуться к __add__
; вы можете сделать это, выполнив __iadd__
, который может создавать и возвращать копию вместо мутирования и возврата self
. (Вы можете сделать это по соображениям производительности, но также возможно, что у вас будет объект с двумя разными интерфейсами, "высокоуровневый" интерфейс выглядит неизменным и копирует на запись, в то время как "низкоуровневый" интерфейс раскрывает фактическое разделение.)
Итак, первая причина, по которой это необходимо, - это обработать случай, не относящийся к делу.
Но есть ли другие причины? Конечно.
Одна из причин заключается только в том, чтобы обернуть другие языки или библиотеки, где это важная функция.
Например, в Objective C многие методы возвращают self
, который обычно, но не всегда тот же объект, который получил вызов метода. Это не всегда означает, что ObjC обрабатывает такие вещи, как кластеры классов. В Python есть лучшие способы сделать одно и то же (даже изменение класса во время выполнения обычно лучше), но в ObjC это совершенно нормально и идиоматично. (Он используется только для методов init
в Apple current Framework, но это соглашение их стандартной библиотеки, что методы mutator, добавленные NSMutableFoo
, всегда возвращают void
, как и соглашение, что методы мутатора, такие как list.sort
, всегда возвращают None
в Python, а не в части языка.) Итак, если вы хотите завершить работу среды выполнения ObjC в Python, как бы вы справились с этим?
Вы можете добавить дополнительный прокси-сервер перед всем, поэтому ваш объект-обертка может изменить объект ObjC, который он обертывает. Но это означает много сложного кода делегирования (особенно если вы хотите, чтобы работа ObjC отразилась с помощью оболочки в Python) и код управления памятью, а также производительность.
Вместо этого вы можете просто иметь общую тонкую обертку. Если вы вернете другой объект ObjC, чем вы начали, вы вернете оболочку вокруг этой вещи вместо обертки вокруг той, с которой вы начали. Тривиальный код, управление памятью автоматически, без затрат на производительность. Пока пользователи вашей обертки всегда делают a += b
вместо a.__iadd__(b)
, они не будут видеть разницы.
Я понимаю, что "писать оболочку стиля PyObjC вокруг другой библиотеки фреймворков ObjC, чем Apple Foundation" - это не совсем повседневный случай использования... но вы уже знали, что это функция, которую вы не используете каждый день, так что еще вы ожидаете?
Простой ленивый прокси-сервер сети может сделать что-то похожее - начните с крошечного объекта moniker, замените его на полный прокси-объект при первом попытке что-то сделать с ним. Вероятно, вы можете думать о других подобных примерах. Вы, вероятно, никогда не напишете ни одного из них... но если вам нужно, вы могли бы.