Операторы перегрузки в Python

… и немного о перегрузках в целом (но я постараюсь не перегружать вас)

Большинство из нас, обучающихся программированию на Python, сталкиваются с концепциями перегрузки операторов относительно рано в ходе нашего обучения. Но, как и большинство аспектов Python (и других языков; и, если на то пошло, почти всего), изучение перегруженных операторов обязательно связано с другими концепциями, расширяя область темы и несколько запутывая путь через нашу индивидуальную кривую обучения. Имея это в виду, я постараюсь не отвлекаться от темы, не вдаваясь в слишком много других областей изучения Python - однако, некоторые объектно-ориентированное программирование естественным образом связано с ним; и, хотя я хотел бы сосредоточиться на перегрузке операторов, более широкая и сложная тема перегрузки функций также заслуживает хотя бы некоторого упоминания.

В контексте программирования под перегрузкой понимается способность функции или оператора вести себя по-разному в зависимости от переданных параметров к функции или операндам, с которыми действует оператор [1]. В Python перегрузка оператора (также известная как «специальный полиморфизм операторов»), в частности, представляет собой форму «синтаксического сахара», которая позволяет мощные и удобные способы прописывать операции оператора, например инфиксное выражение указанных типов. Иными словами, перегрузка оператора придает операторам расширенное значение, выходящее за рамки их заранее определенного рабочего значения.

Классический пример перегрузки операторов в Python - это знак плюс, двоичный (т.е. два операнда) оператор, который не только добавляет пару чисел, но и объединяет пару списков или строк. Звездочка так же перегружена не только как множитель для чисел, но и как оператор повторения для списков или строк. Операторы сравнения (такие как ›, == или! =) Демонстрируют аналогичное поведение; однако для всех этих перегруженных операторов мы, пользователи Python, должны быть осторожны при рассмотрении проверки типов. Как напоминает нам профессор Джон Гуттаг из Массачусетского технологического института, «проверка типов в Python не так сильна, как в некоторых других языках программирования (например, Java), но в Python 3 она лучше, чем в Python 2. Например, довольно ясно, что‹ должен означать, когда он используется для сравнения двух строк или двух чисел. Но каким должно быть значение ‘4’ ‹3? Скорее произвольно, разработчики Python 2 решили, что оно должно быть False, потому что все числовые значения должны быть меньше, чем все значения типа str. Разработчики Python 3 и большинства других современных языков решили, что, поскольку такие выражения не имеют очевидного значения, они должны генерировать сообщение об ошибке »[2].

Это все хорошо, но что, если оператор используется в качестве операнда для одного или нескольких пользовательских типов данных (т. Е. Из созданного класса)? В таком случае - скажем, при попытке добавить пару координат (x, y), как показано здесь - компилятор выдаст ошибку, поскольку он не знает, как добавить два объекта. И хотя перегрузка может выполняться только для существующих операторов в Python, их несколько, а также соответствующий магический метод, который вызывает каждый из этих операторов; используя эти соответствующие методы, мы можем создавать / получать доступ / редактировать их внутреннюю работу (см. конец статьи).

Как и другие номенклатуры в этой быстро развивающейся области, похоже, нет единого мнения о том, как их называть - их несколько обычно называют магическими методами - называемыми магическими, поскольку они не вызываются напрямую - и это кажется наиболее близким к стандарту, возможно, поскольку альтернативный специальный метод звучит, ну, не так уж и необычно. Некоторые провозглашают более красочное прозвище dunder методы как сокращение от методов с двойным подчеркиванием (то есть dunder-init-dunder). В любом случае, это особый тип метода, который не ограничен только методами, связанными с операторами (__init __ () или __call __ () , в качестве примеров); на самом деле их немало.

Вкратце: печать имеет свой собственный магический метод, __str __ (). Если бы мы напечатали простой класс Point с единственным __init __ (), мы бы получили не очень удобный вывод, показанный выше.

Добавление метода __str __ () в класс Point исправит это. Интересно, что format () также вызывает тот же метод __str __ (), что и print ().

Оказывается, использование координат (x, y) для просмотра примеров перегрузки, методов и других концепций Python является довольно распространенной практикой при изучении Python, вероятно, потому, что мы можем создать наш собственный класс с чем-то столь же математически знакомым, как точки координат. . Отсюда можно создать ряд полезных магических методов для определенного пользователем класса и использовать их для перегрузки операторов.

Примечательным аспектом перегрузки операторов является положение каждого операнда по отношению к его оператору. В качестве примера возьмем оператор «меньше чем» - он вызывает метод __lt __ () для первого (или левого / предыдущего) операнда. Другими словами, выражение x ‹y является сокращением для x .__ lt __ (y); если первый операнд является определяемым пользователем классом, он должен иметь собственный соответствующий метод __lt __ (), чтобы иметь возможность использовать «‹ ». Это может показаться неприятным, но на самом деле это добавляет некоторую удобную гибкость для разработки собственных классов, поскольку мы можем настроить то, что любая функция оператора делает для класса. «Помимо обеспечения синтаксического удобства написания инфиксных выражений, использующих‹, - отмечает профессор Гуттаг, - эта перегрузка обеспечивает автоматический доступ к любому полиморфному методу, определенному с помощью __lt __ (). Встроенный метод сортировки - один из таких методов. »[2]

В свете этого различия между первым и вторым операндами Python также предоставляет нам набор обратных методов, таких как __radd __ (), __rsub __ (), __rmul __ (), и так далее. Имейте в виду, что эти обратные методы вызываются только в том случае, если левый операнд не поддерживает соответствующую операцию и операнды разных типов. Реддитор Pythonista по имени Rhomboid объясняет это лучше, чем я когда-либо мог, поэтому я смиренно полагаюсь на его мнение:

Может ли кто-нибудь объяснить мне __radd__ простым языком? Я прочитал документацию и не понимаю ее.

И последнее предостережение: несмотря на то, что у нас есть такая гибкость, это здорово, но мы должны помнить об изначальном намерении операторов. Например, обычно считается, что len () используется для возврата длины последовательности; поэтому перегрузка этого метода требует, чтобы возвращалось целое число (в противном случае он вернет ошибку TypeError).

… А теперь краткое погружение в более обширные, более неспокойные воды перегруженных функций. Согласно Википедии, это относится к «возможности создавать несколько функций с одним и тем же именем с разными реализациями». Функции могут различаться по арности или типам их параметров. Эта концепция гораздо более полезна для других языков (C ++, Java) и на самом деле не соответствует «способу работы» Pythonic, как указывали некоторые в stackoverflow:

Как использовать перегрузку методов в Python?

… И еще одна полезная ветка по этому поводу:

Ясно, что Python обрабатывает такие случаи по-другому. С учетом сказанного, чтение о перегрузке методов помогло мне понять важную концепцию Pythonic: полиморфизм. Это определяется как возможность использовать один и тот же интерфейс для различных базовых форм, таких как типы данных или классы. Полиморфизм является отличительной чертой классов в Python, поскольку он позволяет использовать методы с общими именами во многих классах или подклассах, что, кроме того, позволяет функциям использовать объекты, принадлежащие одному классу таким же образом, что и он работает с объектами другого класса, и все это без необходимости знать о различиях между классами. [3]. Это позволяет использовать утиную типизацию, особый случай динамической типизации, который использует характеристики полиморфизма (включая позднее связывание и динамическую отправку) для оценки объекта. типы.

Отсюда все начинается с одно- и множественной диспетчеризации и статической и динамической диспетчеризации, что далеко выходит за рамки моего текущего уровня понимания; так что я оставлю это на время.

Источники:

[1] https://stackabuse.com/overloading-functions-and-operators-in-python/

[2] Гуттаг, Джон В. Введение в вычисления и программирование с использованием Python (MIT Press). MIT Press. Разжечь издание.

[3] https://www.digitalocean.com/community/tutorials/how-to-apply-polymorphism-to-classes-in-python-3

[4] (заглавное изображение) https://www.osgpaintball.com/event/operation-overlord-scenario-paintball/

Https://www.reddit.com/r/learnpython/comments/3cvgpi/can_someone_explain_radd_to_me_in_simple_terms_i/





















Заявление об ограничении ответственности: ошибки, неправильное толкование, злоупотребление концепциями и слишком далеко зашедшие идеи - мои и только мои.