Разница между len () и .__ len __ ()?

104

Есть ли разница между звонком len([1,2,3])или [1,2,3].__len__()?

Если нет видимой разницы, что по-другому делается за кадром?

отметка
источник

Ответы:

104

len- функция для получения длины коллекции. Он работает, вызывая __len__метод объекта . __something__атрибуты являются особенными и обычно более обширны, чем кажется на первый взгляд, и, как правило, не должны называться напрямую.

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

Майк Грэм
источник
67

Часто «типичным» поведением встроенного оператора or является вызов (с другим и более приятным синтаксисом) подходящих магических методов (с такими именами __whatever__) для задействованных объектов. Часто встроенный оператор or имеет «добавленную стоимость» (он может принимать разные пути в зависимости от задействованных объектов) - в случае lenvs __len__это просто небольшая проверка работоспособности встроенного, которого нет в магический метод:

>>> class bah(object):
...   def __len__(self): return "an inch"
... 
>>> bah().__len__()
'an inch'
>>> len(bah())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer

Когда вы видите вызов lenвстроенной функции, вы уверены, что, если программа продолжится после этого, а не вызовет исключение, вызов вернул целое число, неотрицательное и меньше 2 ** 31 - когда вы видите вызов xxx.__len__(), у вас нет уверенности (за исключением того, что автор кода либо не знаком с Python, либо почти ничего не знает ;-).

Другие встроенные функции обеспечивают еще большую добавленную стоимость, помимо простой проверки работоспособности и удобочитаемости. Единообразно спроектировав весь Python для работы через вызовы встроенных функций и использование операторов, а не через вызовы магических методов, программисты избавлены от бремени запоминания, какой случай есть какой. (Иногда выскакивает ошибка: до версии 2.5 вам приходилось вызывать foo.next()- в версии 2.6, хотя это все еще работает для обратной совместимости, вы должны вызвать next(foo), и в 3.*, магический метод правильно назван __next__вместо «ой-эй» next! - ).

Таким образом, общее правило должно заключаться в том, чтобы никогда не вызывать магический метод напрямую (но всегда косвенно через встроенный), если вы точно не знаете, зачем вам это нужно (например, когда вы переопределяете такой метод в подклассе, если подкласс должен подчиняться суперклассу, что должно быть выполнено посредством явного вызова магического метода).

Алекс Мартелли
источник
Я начинающий пользователь Python (не думал, что начинающий программист), и я не уверен в том, что «когда вы видите вызов встроенного len, вы уверены, что если программа продолжится после этого, а не вызовет исключение». Я пробовал это: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))и он напечатал I am stringдважды. Вы можете объяснить это подробнее?
Дарек Нендза
4
@ DarekNędza Это не имеет ничего общего с вышеупомянутым, которое касается встроенной len. Вы только что определили свою функцию len, которая, конечно, может возвращать все, что вы хотите. OP говорил о встроенном len, который вызывает __len__специальный метод (не функцию) для рассматриваемого объекта.
Veky
@Veky Как я могу быть уверен, что вызываю встроенную функцию, а lenне какую-то другую функцию (как в моем примере) с таким же именем - len. Нет предупреждений вроде «Вы переопределяете встроенную функцию len» или чего-то подобного. На мой взгляд, я не могу быть уверен в том, что сказал Алекс в своем ответе.
Darek Nędza
3
Алекс прямо сказал, что если вы вызываете встроенный, то вы уверены ..._. Он ничего не сказал о том, что вы звоните встроенным. Но если вы хотите знать , что вы можете: len in vars(__builtins__).values().
Veky
1
К сожалению, это еще один пример отсутствия общего базового класса для объектов в Python. Синтаксическое переключение контекста всегда было чушью. В некоторых случаях это обычная идиома - использовать метод подчеркивания, в других следует использовать что-то вроде функции, чтобы делать что-то общее для многих объектов. Это также странно, потому что многие объекты не имеют смыслового использования для len. Иногда объектная модель больше похожа на C ++, кухня тонкая ..
uchuugaka
28

Вы можете думать о len () как о примерно эквиваленте

def len(x):
    return x.__len__()

Одним из преимуществ является то, что он позволяет писать такие вещи, как

somelist = [[1], [2, 3], [4, 5, 6]]
map(len, somelist) 

вместо того

map(list.__len__, somelist)

или

map(operator.methodcaller('__len__'), somelist)

Однако есть немного другое поведение. Например, в случае ints

>>> (1).__len__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__len__'
>>> len(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()
Джон Ла Рой
источник
2
Я полагаю, вы имеете в виду operator.methodcallerвместо operator.attrgetter.
Элазар
5

Вы можете проверить документы Pythond :

>>> class Meta(type):
...    def __getattribute__(*args):
...       print "Metaclass getattribute invoked"
...       return type.__getattribute__(*args)
...
>>> class C(object):
...     __metaclass__ = Meta
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print "Class getattribute invoked"
...         return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__()                 # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c)          # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c)                      # Implicit lookup
10
Дмитрий Озаркив
источник