Преимущества наличия статических функций, таких как len (), max () и min (), по сравнению с вызовами унаследованных методов - PullRequest
9 голосов
/ 27 октября 2009

Я новичок в Python, и я не уверен, почему Python реализовал len (obj), max (obj) и min (obj) как статические функции (я из языка Java) над obj.len ( ), obj.max () и obj.min ()

Каковы преимущества и недостатки (кроме очевидной несогласованности) наличия len () ... по сравнению с вызовами метода?

почему Гвидо выбрал это вместо вызовов методов? (это можно было бы решить в python3, если это необходимо, но это не изменилось в python3, поэтому должны быть веские причины ... я надеюсь)

спасибо !!

Ответы [ 4 ]

19 голосов
/ 27 октября 2009

Большим преимуществом является то, что встроенные функции (и операторы) могут применять дополнительную логику в случае необходимости, помимо простого вызова специальных методов. Например, 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 действительно удаляет некоторую часть присущей им элегантности, это все же разумное сочетание элегантности и практичности! -).

3 голосов
/ 27 октября 2009

На самом деле, это не «статичные» методы в том, как вы о них думаете. Это встроенные функции , которые на самом деле просто псевдоним некоторых методов в объектах Python, которые их реализуют.

>>> class Foo(object):
...     def __len__(self):
...             return 42
... 
>>> f = Foo()
>>> len(f)
42

Они всегда доступны для вызова независимо от того, реализует их объект или нет. Дело в том, чтобы иметь некоторую последовательность. Вместо некоторого класса, имеющего метод length () и другой, называемый size (), соглашение состоит в том, чтобы реализовать len и позволить вызывающим всегда получать к нему доступ через более читаемый len (obj) вместо obj.methodThatDoesSomethingCommon

3 голосов
/ 27 октября 2009

Он подчеркивает возможности объекта, а не его методы или тип. Capabilites объявляются вспомогательными функциями, такими как __iter__ и __len__, но они не составляют интерфейс. Интерфейс находится во встроенных функциях, а также в встроенных операторах, таких как + и [], для индексации и нарезки.

Иногда это не однозначное соответствие: например, iter(obj) возвращает итератор для объекта и будет работать, даже если __iter__ не определено. Если он не определен, он продолжает поиск, если объект определяет __getitem__, и возвращает итератор, обращающийся к объекту по индексу (как массив).

Это идет вместе с Python's Duck Typing, мы заботимся только о том, что мы можем сделать с объектом, а не о том, что он имеет определенный тип.

1 голос
/ 27 октября 2009

Я думал, что причина в том, что эти основные операции можно выполнять на итераторах с тем же интерфейсом, что и контейнеры. Тем не менее, это на самом деле не работает с len:

def foo():
    for i in range(10):
        yield i
print len(foo())

... завершается с ошибкой TypeError. len () не будет потреблять и считать итератор; он работает только с объектами, которые имеют __len__ вызов.

Так что, насколько я понимаю, len () не должно существовать. Гораздо естественнее сказать obj.len, чем len (obj), и гораздо более согласуется с остальным языком и стандартной библиотекой. Мы не говорим добавить (1, 1); мы говорим lst.append (1). Наличие отдельного глобального метода для длины является странным, противоречивым особым случаем, и он ест очень очевидное имя в глобальном пространстве имен, что является очень плохой привычкой Python.

Это не связано с набором утки; Вы можете сказать getattr(obj, "len"), чтобы решить, можете ли вы использовать len на объекте так же легко - и гораздо более последовательно - чем вы можете использовать getattr(obj, "__len__").

Все это сказало, что языковые бородавки идут - для тех, кто считает это бородавкой - с этим очень легко жить.

С другой стороны, min и max do работают на итераторах, что позволяет использовать их отдельно от любого конкретного объекта. Это просто, поэтому я просто приведу пример:

import random
def foo():
    for i in range(10):
        yield random.randint(0, 100)
print max(foo())

Однако нет методов __min__ или __max__ для переопределения его поведения, поэтому нет единого способа обеспечить эффективный поиск отсортированных контейнеров. Если контейнер отсортирован по тому же ключу, который вы ищете, мин / макс - это операции O (1) вместо O (n), и единственный способ показать это другим несовместимым методом. (Конечно, это можно исправить на языке относительно легко.)

Чтобы решить еще одну проблему: она предотвращает использование привязки метода Python. В качестве простого надуманного примера вы можете сделать это, чтобы предоставить функцию для добавления значений в список:

def add(f):
    f(1)
    f(2)
    f(3)
lst = []
add(lst.append)
print lst

и это работает для всех функций-членов. Вы не можете сделать это с помощью min, max или len, поскольку они не являются методами объекта, с которым они работают. Вместо этого вам придется прибегнуть к functools.partial, неуклюжему обходному решению второго класса, распространенному на других языках.

Конечно, это необычный случай; но это редкие случаи, которые говорят нам о согласованности языка.

...