Почему код Python использует функцию len () вместо метода длины?

206

Я знаю, что в python есть len()функция, которая используется для определения размера строки, но мне было интересно, почему это не метод объекта string.

Обновить

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

fuentesjr
источник
Относящийся к вашему обновлению: Есть ли случай , когда len(someObj)не называет someObj«s __len__функции? tl; dr Да, поэтому всегда используйте len()вместо __len__().
wjandrea

Ответы:

182

Строки имеют метод длины: __len__()

Протокол в Python состоит в том, чтобы реализовать этот метод на объектах, которые имеют длину и используют встроенную len()функцию, которая вызывает ее для вас, подобно тому, как вы реализуете __iter__()и используете встроенную iter()функцию (или вызываете метод позади). сцены для вас) на объектах, которые являются повторяемыми.

См. Эмуляция типов контейнеров для получения дополнительной информации.

Вот хорошее прочтение на тему протоколов в Python: Python и принцип наименьшего удивления

Джонни Бьюкенен
источник
124
Меня удивляет, насколько идиотская причина для использования len. Они думают, что легче заставить людей осуществлять, .__len__чем заставлять людей выполнять .len(). Это то же самое, и каждый выглядит намного чище. Если у языка будет ООП __len__, в чем смысл создания мираlen(..)
альтернатива
40
len, str и т. д. могут использоваться с функциями более высокого порядка, такими как map, Reduce и Filter, без необходимости определять функцию или лямбду только для вызова метода. Не все вращается вокруг ООП, даже в Python.
Evicatos
12
Кроме того, используя протокол, они могут предоставить альтернативные способы реализации вещей. Например, вы можете создать итерацию с помощью __iter__или только с помощью __getitem__и iter(x)работать в любом случае. Вы можете создать объект in-bool-context с помощью __bool__или __len__и bool(x)работать в любом случае. И так далее. Я думаю, что Армин достаточно хорошо объясняет это в связанном посте, но даже если бы он этого не сделал, называть Python дебильным из-за внешнего объяснения парня, который часто публично расходится с основными разработчиками, было бы не совсем справедливо…
abarnert
8
@Evicatos: +1 за «Не все вращается вокруг ООП», но я бы закончил словами « особенно в Python», даже не так . Python (в отличие от Java, Ruby или Smalltalk) не пытается быть языком «чистого ООП»; он явно разработан, чтобы быть "мультипарадигмальным" языком. Постижения списков не являются методами на итерации. zipэто функция верхнего уровня, а не zip_withметод. И так далее. То, что все является объектом, не означает, что быть объектом - это самое важное в каждой вещи.
'17
7
Эта статья написана так плохо, что я чувствую растерянность и злость после того, как пытаюсь ее прочитать. «В Python 2.x, например, тип Tuple не предоставляет никаких нестандартных методов, и все же вы можете использовать его, чтобы сделать из него строку:« Все это представляет собой объединение почти неразличимых предложений.
MirroredFate
94

Ответ Джима на этот вопрос может помочь; Я копирую это здесь. Цитируя Гвидо ван Россума:

Прежде всего, я выбрал len (x) вместо x.len () по причинам HCI (def __len __ () появилась намного позже). На самом деле есть две взаимосвязанные причины, обе - HCI:

(a) Для некоторых операций префиксная нотация читается лучше, чем постфиксная - у префиксных (и инфиксных!) операций есть давняя традиция в математике, которой нравятся нотации, в которых визуальные эффекты помогают математику думать о проблеме. Сравните легкость, с которой мы переписываем формулу типа x * (a + b) в x a + x b с неуклюжостью делать то же самое, используя необработанную запись ОО.

(б) Когда я читаю код, который говорит len (x), я знаю, что он запрашивает длину чего-либо. Это говорит мне о двух вещах: результат - целое число, а аргумент - своего рода контейнер. Наоборот, когда я читаю x.len (), я уже должен знать, что x - это какой-то контейнер, реализующий интерфейс или наследующий от класса, который имеет стандартный len (). Обратите внимание на путаницу, которую мы иногда испытываем, когда класс, который не реализует отображение, имеет метод get () или keys (), или что-то, что не является файлом, имеет метод write ().

Говоря то же самое по-другому, я вижу 'len' как встроенную операцию. Я бы не хотел потерять это. / ... /

Федерико А. Рампони
источник
36

Есть lenметод:

>>> a = 'a string of some length'
>>> a.__len__()
23
>>> a.__len__
<method-wrapper '__len__' of str object at 0x02005650>
неокантованный
источник
Да, но он не предназначен для непосредственного использования. lenпроверяет вывод __len__. См Есть ли случай , когда len(someObj)не называет someObj«s __len__функции? Методы Dunder в целом не предназначены для непосредственного вызова.
wjandrea
35

Python является прагматическим языком программирования, и причины для len()того , чтобы быть функцией , а не метод str, list, и dictт.д. прагматичны.

len()Встроенная функция имеет дело непосредственно с встроенными типами: реализация CPython на len()самом деле возвращает значение ob_sizeполя в PyVarObjectC структуру , которая представляет какое - либо переменного размер встроенного объекта в памяти. Это намного быстрее, чем вызов метода - поиск атрибутов не требуется. Получение количества элементов в коллекции является обычной операцией и должно эффективно работать для таких базовых и разнообразных типов, как str, listи array.arrayт. Д.

Тем не менее, для обеспечения согласованности при применении len(o)к пользовательскому типу, Python вызывает o.__len__()как запасной вариант. __len__, __abs__И все другие специальные методы документированы в модели данных Python позволяют легко создавать объекты , которые ведут себя как встроенные модули, что позволяет выразительные и очень последовательные интерфейсы API , которые мы называем «Pythonic».

Благодаря реализации специальных методов ваши объекты могут поддерживать итерацию, перегрузить инфиксные операторы, управлять контекстами в withблоках и т. Д. Вы можете представить модель данных как способ использования самого языка Python в качестве основы, в которой создаваемые объекты могут быть легко интегрированы.

Вторая причина, поддерживаемая цитатами из Гвидо ван Россума, как эта , заключается в том, что ее легче читать и писать, len(s)чем s.len().

Нотация len(s)соответствует унарным операторам с префиксной нотацией, например abs(n). len()используется гораздо чаще abs(), и это заслуживает того, чтобы было так легко написать.

Может также быть историческая причина: в языке ABC, который предшествовал Python (и был очень влиятельным в его дизайне), был унарный оператор, написанный как #sозначающий len(s).

Лучано Рамальо
источник
11
met% python -c 'import this' | grep 'only one'
There should be one-- and preferably only one --obvious way to do it.
Алекс Ковентри
источник
35
Очевидная вещь при работе с объектом - это, конечно, метод.
Питер Купер
4
@Peter: Я бы заплатил 20 долларов любому, у кого есть фотографии, подтверждающие, что они приклеили ваш комментарий к спине Гвидо. 50 долларов, если он у него на лбу.
ошибка
1
Да, дизайнеры Python придерживаются догмы, но сами не уважают свою догму.
Битек
2
$ python -c 'импортировать это' | grep очевидно
Эд Рэндалл
3

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

  • Python не является чистым языком ООП - это универсальный язык с множеством парадигм, который позволяет программисту использовать парадигму, с которой он наиболее удобен, и / или ту парадигму, которая лучше всего подходит для их решения.
  • Python имеет первоклассные функции, поэтому lenна самом деле это объект. Ruby, с другой стороны, не имеет функций первого класса. Таким образом, у lenобъекта функции есть свои собственные методы, которые вы можете проверить, запустив dir(len).

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

>>> class List(list):
...     def len(self):
...         return len(self)
...
>>> class Dict(dict):
...     def len(self):
...         return len(self)
...
>>> class Tuple(tuple):
...     def len(self):
...         return len(self)
...
>>> class Set(set):
...     def len(self):
...         return len(self)
...
>>> my_list = List([1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'])
>>> my_dict = Dict({'key': 'value', 'site': 'stackoverflow'})
>>> my_set = Set({1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'})
>>> my_tuple = Tuple((1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'))
>>> my_containers = Tuple((my_list, my_dict, my_set, my_tuple))
>>>
>>> for container in my_containers:
...     print container.len()
...
15
2
15
15
Чарльз Аддис
источник
2

Чего-то не хватает в остальных ответах здесь: lenфункция проверяет, что __len__метод возвращает неотрицательное значение int. Тот факт, что lenэто функция, означает, что классы не могут переопределить это поведение, чтобы избежать проверки. Как таковой, len(obj)дает уровень безопасности, который obj.len()не может.

Пример:

>>> class A:
...     def __len__(self):
...         return 'foo'
...
>>> len(A())
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    len(A())
TypeError: 'str' object cannot be interpreted as an integer
>>> class B:
...     def __len__(self):
...         return -1
... 
>>> len(B())
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    len(B())
ValueError: __len__() should return >= 0

Конечно, можно «переопределить» lenфункцию, переназначив ее как глобальную переменную, но код, который делает это, гораздо более подозрительно, чем код, который переопределяет метод в классе.

kaya3
источник
Он также проверяет, что int не больше чем sys.maxsize, и что obj.__len__существует в первую очередь. Я написал ответ на другой вопрос, в котором перечислены все различные проверки.
wjandrea
-1

Вы также можете сказать

>> x = 'test'
>> len(x)
4

Использование Python 2.7.3.

SpanosAngelos
источник
9
lenфункция уже упоминалась в предыдущих ответах. Какой смысл в этом "ответе"?
Петр Доброгост