Большим преимуществом является то, что встроенные функции (и операторы) могут применять дополнительную логику в случае необходимости, помимо простого вызова специальных методов. Например, min
может просматривать несколько аргументов и применять соответствующие проверки неравенства или может принимать один итеративный аргумент и действовать аналогично; abs
при вызове объекта без специального метода __abs__
может попытаться сравнить указанный объект с 0 и, если необходимо, использовать метод знака изменения объекта (хотя в настоящее время это не так); и пр.
Таким образом, для согласованности все операции с широкой применимостью всегда должны проходить через встроенные модули и / или операторы, и ответственность за эти встроенные функции заключается в поиске и применении соответствующих специальных методов (для одного или нескольких аргументов). используйте альтернативную логику, где применимо, и т. д.
Примером, где этот принцип не был правильно применен (но несоответствие было исправлено в Python 3), является «шаг вперед итератора»: в 2.5 и более ранних версиях вам нужно было определить и вызвать не специально названное next
метод на итераторе. В 2.6 и более поздних версиях вы можете сделать это правильно: объект итератора определяет __next__
, новая встроенная next
может называть его и применять дополнительную логику, например, для предоставления значения по умолчанию ( в 2.6 вы все равно можете сделать это плохим старым способом для обратной совместимости, хотя в 3.*
вы больше не можете).
Другой пример: рассмотрим выражение x + y
. В традиционном объектно-ориентированном языке (способном выполнять диспетчеризацию только по типу самого левого аргумента - например, Python, Ruby, Java, C ++, C # и C), если x
имеет некоторый встроенный тип, а y
- это вашего собственного необычного нового типа, вам, к сожалению, не повезло, если язык настаивает на делегировании всей логики методу type(x)
, который реализует сложение (при условии, что язык допускает перегрузку операторов; -).
В Python оператор +
(и аналогично, конечно, встроенный operator.add
, если это то, что вы предпочитаете) пробует тип x в __add__
, и если тот не знает, что делать с y
, затем пытается у типа __radd__
. Таким образом, вы можете определить свои типы, которые знают, как добавлять себя к целым числам, числам с плавающей запятой, сложным и т. Д. И т. Д., А также те, которые знают, как добавлять такие встроенные числовые типы к себе (т. Е. Вы можете кодировать их так, чтобы x + y
и y + x
оба работают нормально, когда y
является экземпляром вашего необычного нового типа, а x
является экземпляром некоторого встроенного числового типа).
«Универсальные функции» (как в PEAK) - более элегантный подход (допускающий любое переопределение на основе комбинации типов, никогда с сумасшедшим мономаниакальным фокусом на самых левых аргументах, которые ООП поощряет! -) , но (а) они, к сожалению, не были приняты для Python 3, и (б) они, конечно, требуют, чтобы универсальная функция была выражена как автономная (было бы абсолютно безумно рассматривать функцию как «принадлежащую» любой отдельный тип, где целое значение POINT может быть переопределено / перегружено по-разному в зависимости от произвольной комбинации типов нескольких аргументов! -). Любой, кто когда-либо программировал на Common Lisp, Dylan или PEAK, знает, о чем я говорю; -).
Таким образом, отдельно стоящие функции и операторы - это правильный и последовательный путь (даже несмотря на то, что отсутствие универсальных функций в чистом Python действительно удаляет некоторую часть присущей им элегантности, это все же разумное сочетание элегантности и практичности! -).