Dlaczego kod Python używa funkcji len () zamiast metody długości?


204

Wiem, że python ma len()funkcję służącą do określania rozmiaru łańcucha, ale zastanawiałem się, dlaczego nie jest to metoda obiektu łańcucha.

Aktualizacja

Ok, zrozumiałem, że się wstydziłem. __len__()jest właściwie metodą obiektu łańcuchowego. Po prostu dziwnie jest widzieć kod zorientowany obiektowo w Pythonie za pomocą funkcji len na obiektach łańcuchowych. Co więcej, dziwnie jest też patrzeć __len__na nazwę zamiast po prostu len.

Odpowiedzi:


180

Ciągi mają metodę długości: __len__()

Protokół w Pythonie polega na implementowaniu tej metody na obiektach o długości i korzystających z wbudowanej len()funkcji, która ją wywołuje, podobnie jak w przypadku implementacji __iter__()i korzystania z wbudowanej iter()funkcji (lub metody wywoływanej za nią sceny dla Ciebie) na obiektach, które można iterować.

Aby uzyskać więcej informacji, zobacz Emulowanie typów kontenerów .

Oto dobra lektura na temat protokołów w Pythonie: Python i zasada najmniejszego zdziwienia


124
Zaskakuje mnie, jak idiotyczny jest powód używania len. Uważają, że łatwiej jest zmusić ludzi do wdrożenia .__len__niż zmusić ludzi do wdrożenia .len(). To to samo i wygląda na znacznie czystszego. Jeśli język ma mieć OOP __len__, co na świecie ma senslen(..)
alternatywne

38
len, str itp. mogą być używane z funkcjami wyższego rzędu, takimi jak mapowanie, zmniejszanie i filtrowanie, bez potrzeby definiowania funkcji lub lambda tylko w celu wywołania metody. Nie wszystko kręci się wokół OOP, nawet w Pythonie.
Evicatos,

11
Ponadto, używając protokołu, mogą zapewnić alternatywne sposoby implementacji rzeczy. Na przykład możesz utworzyć iterowalny z __iter__lub tylko __getitem__i iter(x)będzie działał w obu kierunkach. Możesz utworzyć obiekt kontekstu użytecznego w bool za pomocą __bool__lub __len__i bool(x)będzie działał w obie strony. I tak dalej. Myślę, że Armin wyjaśnia to dość dobrze w powiązanym poście - ale nawet gdyby nie zrobił, nazywanie Pytona głupkiem z powodu zewnętrznego wyjaśnienia faceta, który często publicznie kłóci się z głównymi deweloperami, nie byłoby w porządku…
abarnert

8
@Evicatos: +1 dla „Nie wszystko kręci się wokół OOP”, ale kończyłbym na „ szczególnie w Pythonie”, nawet nie . Python (w przeciwieństwie do Java, Ruby lub Smalltalk) nie próbuje być językiem „czystego OOP”; został wyraźnie zaprojektowany jako język „paradygmatu”. Wyrażenia listowe nie są metodami iteracyjnymi. zipjest funkcją najwyższego poziomu, a nie zip_withmetodą. I tak dalej. To, że wszystko jest przedmiotem, nie oznacza, że ​​bycie przedmiotem jest najważniejsze w każdej rzeczy.
abarnert

7
Ten artykuł jest tak źle napisany, że po przeczytaniu czuję się zdezorientowany i zły. „Na przykład w Pythonie 2.x typ Tuple nie ujawnia żadnych nietypowych metod, a mimo to można go użyć do utworzenia z niego łańcucha:„ Całość stanowi połączenie prawie nieczytelnych zdań.
MirroredFate

93

Odpowiedź Jima na to pytanie może pomóc; Kopiuję to tutaj. Cytując Guido van Rossum:

Przede wszystkim wybrałem len (x) zamiast x.len () ze względów HCI (def __len __ () przyszedł znacznie później). Istnieją dwa powiązane ze sobą powody, oba HCI:

(a) W przypadku niektórych operacji notacja prefiksowa odczytuje lepiej niż postfiks - operacje prefiksowe (i infix!) mają długą tradycję w matematyce, która lubi notacje, w których wizualizacje pomagają matematykowi myśleć o problemie. Porównaj łatwość, z jaką przepisujemy formułę taką jak x * (a + b) na x a + x b, do niezdarności robienia tego samego przy użyciu surowej notacji OO.

(b) Kiedy czytam kod mówiący len (x) wiem, że pyta o długość czegoś. To mówi mi dwie rzeczy: wynikiem jest liczba całkowita, a argumentem jest pewien rodzaj kontenera. Przeciwnie, kiedy czytam x.len (), muszę już wiedzieć, że x jest rodzajem kontenera implementującego interfejs lub dziedziczącego z klasy, która ma standardową len (). Zobacz zamieszanie, które czasami mamy, gdy klasa, która nie implementuje mapowania, ma metodę get () lub keys (), lub coś, co nie jest plikiem, ma metodę write ().

Mówiąc to samo w inny sposób, postrzegam „len” jako operację wbudowaną. Nie chciałbym tego stracić. /… /


37

Istnieje lenmetoda:

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

34

Python jest pragmatyczny język programowania, a przyczyny len()jest funkcją, a nie metoda str, list, dictitd. Są pragmatyczne.

len()Wbudowanej funkcji zajmuje się bezpośrednio z wbudowanych typów: realizacja CPython od len()faktycznie zwraca wartość ob_sizepola w PyVarObjectstruct C , który reprezentuje dowolny zmiennej wielkości wbudowanego obiektu w pamięci. Jest to o wiele szybsze niż wywoływanie metody - nie trzeba wyszukiwać atrybutów. Pierwsze liczbę elementów w kolekcji jest wspólna praca i musi pracować wydajnie dla takich podstawowych i zróżnicowanych typów jak str, list, array.arrayitd.

Jednak w celu promowania spójności przy stosowaniu len(o)do typu zdefiniowanego przez użytkownika Python wywołuje o.__len__()funkcję rezerwową. __len__, __abs__a wszystkie inne specjalne metody udokumentowane w Python Data Model ułatwiają tworzenie obiektów, które zachowują się jak wbudowane, umożliwiając ekspresyjne i wysoce spójne interfejsy API, które nazywamy „Pythonic”.

Wdrażając specjalne metody, Twoje obiekty mogą obsługiwać iterację, przeciążać operatorów poprawki, zarządzać kontekstami w withblokach itp. Model danych można traktować jako sposób wykorzystania samego języka Python jako struktury, w której tworzone obiekty można bezproblemowo zintegrować.

Drugim powodem, popartym cytatami z Guido van Rossum, takimi jak ten , jest to, że łatwiej jest czytać i pisać len(s)niż s.len().

Notacja len(s)jest spójna z jednoargumentowymi operatorami z notacją przedrostkową, np abs(n). len()jest używany znacznie częściej niż abs()i zasługuje na równie łatwą pisanie.

Przyczyna może być także historyczna: w języku ABC poprzedzającym Pythona (który miał duży wpływ na jego konstrukcję) istniał #sjednoznaczny operator, co oznaczało 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
Oczywistą rzeczą jest praca z obiektem będącym oczywiście metodą.
Peter Cooper

4
@Peter: Zapłaciłbym 20 USD każdemu, kto ma dowód ze zdjęcia, że ​​przykleił twój komentarz do pleców Guido. 50 $, jeśli jest na jego czole.
błąd

1
Tak, projektanci Pythona wyznają dogmaty, ale sami nie szanują własnego dogmatu.
bitek

2
$ python -c 'import this' | grep oczywiste
Ed Randall

4

Jest tu kilka świetnych odpowiedzi, więc zanim dam własne, chciałbym podkreślić kilka klejnotów (nie zamierzam używać ruby ​​punka), które przeczytałem tutaj.

  • Python nie jest czystym językiem OOP - jest to uniwersalny język wieloparadygmowy, który pozwala programiście korzystać z paradygmatu, w którym są najwygodniejsi i / lub paradygmatu, który najlepiej pasuje do ich rozwiązania.
  • Python ma pierwszorzędne funkcje, więc w lenrzeczywistości jest obiektem. Z drugiej strony Ruby nie ma funkcji pierwszej klasy. Tak więc lenprzedmiotem funkcja ma własne metody, które można sprawdzić, uruchamiając dir(len).

Jeśli nie podoba ci się sposób, w jaki działa to we własnym kodzie, to jest trywialne, aby ponownie wdrożyć kontenery przy użyciu preferowanej metody (patrz przykład poniżej).

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

Możesz też powiedzieć

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

Korzystanie z Python 2.7.3.


9
lenfunkcja była już wspomniana w poprzednich odpowiedziach. Jaki jest zatem sens tej „odpowiedzi”?
Piotr Dobrogost

0

Coś brakuje w pozostałych odpowiedziach tutaj: lenfunkcja sprawdza, czy __len__metoda zwraca wartość nieujemną int. Fakt, że lenjest funkcją, oznacza, że ​​klasy nie mogą zastąpić tego zachowania, aby uniknąć sprawdzenia. Jako taki len(obj)daje poziom bezpieczeństwa, który obj.len()nie może.

Przykład:

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

Oczywiście możliwe jest „przesłonięcie” lenfunkcji przez ponowne przypisanie jej jako zmiennej globalnej, ale kod, który to robi, jest znacznie bardziej podejrzany niż kod, który przesłania metodę w klasie.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.