Чому код Python використовує функцію len () замість методу довжини?


204

Я знаю, що в python є len()функція, яка використовується для визначення розміру рядка, але мені було цікаво, чому це не метод об'єкта string.

Оновлення

Гаразд, я зрозумів, що я бентежно помиляюся. __len__()насправді метод струнного об'єкта. Просто дивно бачити об'єктно-орієнтований код у Python за допомогою функції len на рядкових об'єктах. Крім того, це також дивно бачити, __len__як ім'я, а не просто len.

Відповіді:


180

Строки мають метод довжини: __len__()

Протокол в Python полягає в тому, щоб реалізувати цей метод на об'єктах, що мають довжину і використовують вбудовану len()функцію, яка викликає його для вас, аналогічно тому, як ви б реалізували __iter__()та використовували вбудовану iter()функцію (або метод, названий позаду сцени для вас) на об'єкти, які є ітерабельними.

Додаткову інформацію див. У розділі Емуляція типів контейнерів .

Ось хороше прочитання на тему протоколів на Python: Python and the Principle of Najstreach


124
Мене дивує, наскільки дивна причина вживання len. Вони вважають, що легше змусити людей реалізовувати, .__len__ніж змусити людей до реалізації .len(). Це те саме, і один виглядає набагато чистіше. Якщо мова буде мати OOP __len__, що у світі має сенс робитиlen(..)
альтернатива

38
len, str тощо можуть використовуватися для функцій вищого порядку, таких як карта, зменшення та фільтрація без необхідності визначення функції або лямбда лише для виклику методу. Не все обертається навколо ООП, навіть у Python.
Евікатос

11
Також, використовуючи протокол, вони можуть надати альтернативні способи реалізації речей. Наприклад, ви можете створити ітерабельний з __iter__, або тільки __getitem__, і iter(x)буде працювати в будь-якому випадку. Ви можете створити придатний до вживання в-BOOL контексту об'єкта з __bool__або __len__, і bool(x)буде працювати в будь-якому випадку. І так далі. Я думаю, що Армін пояснює це досить добре у зв’язаному пості, але навіть якби він цього не зробив, називаючи Python моронічним через зовнішнє пояснення хлопця, який часто публічно
розбігається

8
@Evicatos: +1 за «Не все обертається навколо об'єктно - орієнтованого програмування», але я б закінчити з « особливо в Python", не навіть . Python (на відміну від Java, Ruby або Smalltalk) не намагається бути «чистою OOP» мовою; вона явно розроблена як "багатопарадигмальна" мова. Зрозуміння списків - це не методи на ітерабелях. zip- це функція верхнього рівня, а не zip_withметод. І так далі. Тільки тому, що все є об'єктом, це не означає, що предмет є найважливішим у кожній речі.
abarnert

7
Ця стаття настільки погано написана, що я відчуваю розгубленість і злість після спроби її прочитати. "У Python 2.x, наприклад, тип Tuple не розкриває жодних неспеціальних методів, але ви можете використовувати його, щоб зробити з нього рядок:" Вся справа в об'єднанні майже нерозбірливих пропозицій.
MirroredFate

93

Відповідь Джима на це питання може допомогти; Я копіюю його сюди. Цитуючи Гідо ван Россума:

Перш за все, я вибрав len (x) над x.len () з причин HCI (def __len __ () з'явився значно пізніше). Насправді є дві взаємопов'язані причини, обидві ІСІ:

(a) Для деяких операцій позначення префікса просто читається краще, ніж постфікс - операції з префіксом (і інфіксом!) мають багатовікову традицію в математиці, яка подобається нотаціям, коли наочні карти допомагають математику думати про проблему. Порівняйте просту, з якою ми переписуємо формулу на зразок x * (a + b) в x a + x b з незграбністю того ж самого, використовуючи необроблене позначення OO.

(b) Коли я читаю код, який говорить len (x), я знаю, що він просить довжину чогось. Це говорить мені про дві речі: результат - ціле число, а аргумент - якийсь контейнер. Навпаки, коли я читаю x.len (), я вже повинен знати, що x - це якийсь контейнер, що реалізує інтерфейс або успадковує клас, у якого є стандартний len (). Зауважте, що у нас періодично виникає плутанина, коли клас, який не реалізує відображення, має метод get () або keys (), або щось, що не є файлом, має метод write ().

Говорячи те ж саме по-іншому, я бачу 'len' як вбудовану операцію. Мені б не хотілося цього втрачати. /… /


37

Існує lenметод:

>>> a = 'a string of some length'
>>> a.__len__()
23
>>> a.__len__
<method-wrapper '__len__' of str object at 0x02005650>

34

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).


12
met% python -c 'import this' | grep 'only one'
There should be one-- and preferably only one --obvious way to do it.

34
Очевидна річ, коли робота з об'єктом є, звичайно, методом.
Пітер Купер

4
@ Peeter: Я заплатив би 20 доларів будь-кому із фотодоказами, що вони записали ваш коментар до спини Гідо. 50 доларів, якщо це на лобі.
помилка

1
Так, дизайнери Python дотримуються догми, але самі не дотримуються власної догми.
бітек

2
$ python -c 'імпортувати це' | греп очевидний
Ед Рандалл

4

Тут є декілька чудових відповідей, і тому, перш ніж дати власне, я хотів би виділити декілька дорогоцінних каменів (жодних рубінових каламбурів) я не читав тут.

  • Python не є чистою мовою OOP - це загальна мета, багатопарадигмальна мова, яка дозволяє програмісту використовувати парадигму, яка їм найбільше комфортна та / або парадигму, яка найкраще підходить для їх вирішення.
  • 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


0

Щось бракує в решті відповідей тут: 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функцію, призначивши її як глобальну змінну, але код, який робить це, набагато більш очевидно підозрілий, ніж код, який перекриває метод у класі.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.