Правила большого пальца для того, когда использовать перегрузку оператора в python
Из того, что я помню из своего класса С++, профессор сказал, что перегрузка оператора классная, но поскольку для охвата всех конечных случаев требуется относительно много мысли и кода (например, при перегрузке +
вы, вероятно, также захотите перегружать ++
и +=
, а также следить за конечными случаями, такими как добавление объекта самому себе и т.д.), вы должны учитывать это только в тех случаях, когда эта функция будет иметь большое влияние на ваш код, например, перегрузка операторов для матричного класса в математическом приложении.
То же самое относится к python? Вы бы рекомендовали переопределить поведение оператора в python? И какие эмпирические правила вы можете мне дать?
Ответы
Ответ 1
Перегрузка операторов в основном полезна, когда вы создаете новый класс, который попадает в существующий "абстрактный базовый класс" (ABC) - действительно, многие из ABC в стандартном библиотечном модуле collections полагаются на наличие определенных специальных методов (и специальных методов, один с именами, начинающимися и заканчивающимися двойными подчеркиваниями AKA "dunders", точно так же вы выполняете перегрузку оператора в Python). Это обеспечивает хорошее начальное руководство.
Например, класс Container
должен переопределять специальный метод __contains__
, т.е. оператор проверки принадлежности item in container
(как в, if item in container:
- не путайте с оператором for
, for item in container:
, который полагается на __iter__
! -).
Аналогично, a Hashable
должен переопределить __hash__
, a Sized
должен переопределить __len__
, a Sequence
или Mapping
должен переопределить __getitem__
и так далее. (Более того, ABC могут предоставить вашему классу функции mixin - например, как Sequence
, так и Mapping
могут предоставить __contains__
на основе предоставленного вами __getitem__
переопределения и тем самым автоматически сделать ваш класс a Container
).
Помимо collections
, вы захотите переопределить специальные методы (т.е. обеспечить перегрузку оператора), главным образом, если ваш новый класс "является числом". Существуют и другие особые случаи, но сопротивляются искушению перегружать операторов "просто для прохлады", без семантической связи с "нормальными" значениями, поскольку потоки С++ для строк <<
и >>
и Python (в Python 2.*
, к счастью, не в 3.*
больше;-) do для %
- когда такие операторы больше не означают "бит-сдвиг" или "остаток деления", вы просто порождаете путаницу. Языковая стандартная библиотека может уйти с ней (хотя это не должно;-), но если ваша библиотека не будет столь распространенной, как стандартная для языка, путаница повредит! -)
Ответ 2
Я написал программное обеспечение со значительными перегрузками, и в последнее время я сожалею об этой политике. Я бы сказал следующее:
Только операторы перегрузки, если это естественная, ожидаемая вещь и не имеет никаких побочных эффектов.
Итак, если вы создаете новый класс RomanNumeral
, имеет смысл перегрузить сложение и вычитание и т.д. Но не перегружайте его, если оно не является естественным: нет смысла определять сложение и вычитание для Car
или Vehicle
объект.
Другое эмпирическое правило: не перегружать ==
. Это очень сложно (хотя и не невозможно), чтобы проверить, являются ли два объекта одинаковыми. Я сделал эту ошибку и заплатил за нее в течение долгого времени.
Что касается того, когда перегружать +=
, ++
и т.д., я бы сказал: перегружайте дополнительные операторы, если у вас есть большой спрос на эту функциональность. Легче иметь один способ сделать что-то, чем пять. Конечно, это означает, что вам иногда приходится писать x = x + 1
вместо x += 1
, но больше кода в порядке, если оно яснее.
В целом, как и многие "причудливые" функции, легко думать, что вы хотите чего-то, когда вы на самом деле не реализуете кучу вещей, не замечаете побочных эффектов, а затем выясняете это позже. Err на консервативной стороне.
EDIT: я хотел добавить пояснительную записку о перегрузке ==
, потому что кажется, что различные комментаторы неправильно понимают это, и это меня поймало. Да, is
существует, но это другая операция. Скажем, у меня есть объект x
, который либо из моего пользовательского класса, либо является целым числом. Я хочу узнать, является ли x
числом 500. Но если вы установите x = 500
, а затем протестируйте x is 500
, вы получите False
из-за того, как Python кэширует числа. С помощью 50
он вернет True
. Но вы не можете использовать is
, потому что вы можете захотеть x == 500
вернуть True
, если x
является экземпляром вашего класса. Смешение? Определенно. Но это то, что вам нужно понять, чтобы успешно перегрузить операторы.
Ответ 3
Вот пример, который использует побитовое или операцию для имитации конвейера unix. Это предназначено в качестве встречного примера для большинства правил.
Я только что нашел Lumberjack, который использует этот синтаксис в реальном коде
class pipely(object):
def __init__(self, *args, **kw):
self._args = args
self.__dict__.update(kw)
def __ror__(self, other):
return ( self.map(x) for x in other if self.filter(x) )
def map(self, x):
return x
def filter(self, x):
return True
class sieve(pipely):
def filter(self, x):
n = self._args[0]
return x==n or x%n
class strify(pipely):
def map(self, x):
return str(x)
class startswith(pipely):
def filter(self, x):
n=str(self._args[0])
if x.startswith(n):
return x
print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | strify() | startswith(5):
print i
print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | startswith(5):
print i
print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | pipely(filter=lambda x: x.startswith('5')):
print i
Ответ 4
Перегрузка Python "безопаснее" вообще, чем С++ - например, оператор присваивания не может быть перегружен, а +=
имеет разумную реализацию по умолчанию.
В некотором роде, однако, перегрузка в Python по-прежнему "сломана", как в С++. Программисты должны сдерживать желание "повторно использовать" оператора для несвязанных целей, таких как C + + повторное использование битов для выполнения форматирования строк и синтаксического анализа. Не перегружайте оператор с помощью другой семантики из вашей реализации, чтобы получить более красивый синтаксис.
Современный стиль Python категорически препятствует перегрузке "изгоев", но многие аспекты языка и стандартной библиотеки сохраняют слабо названные операторы для обратной совместимости. Например:
-
%
: модуль и форматирование строк
-
+
: сложение и последовательность конкатенаций
-
*
: умножение и повторение последовательности
Итак, эмпирическое правило? Если ваша реализация оператора удивит людей, не делайте этого.