2009-10-27 5 views
9

Jestem początkującym pytonem i nie jestem pewien, dlaczego python zaimplementował len (obj), max (obj), i min (obj) jako statyczne funkcje (ja jestem z języka Java) ponad obj.len(), obj.max() i obj.min()Zalety posiadania funkcji statycznej, takiej jak len(), max() i min() nad odziedziczonymi wywołaniami metod

jakie są zalety i wady (inne niż oczywista niekonsekwencja) posiadania len() ... przez wywołania metody?

dlaczego guido wybrał to przez wywołania metody? (w razie potrzeby można to było rozwiązać w python3, ale nie zostało to zmienione w python3, więc nie bez powodu ... mam nadzieję)

dzięki !!

+2

Osobiście uważam, że 'min (1,2)' i 'min ([1,2])' są wyjątkowo spójne. Nie wszystko powinno być napisane w Javie: –

+0

@TomLeys: W jaki sposób 'len (x)' jest spójne z wywołaniami metod, takimi jak 'x.foo()'? Obie są operacjami na obiekcie x, prawda? Nie chodzi o to, że jest jak Java. Chodzi o zgodność z [Principle of least Least Surprise] (http://en.wikipedia.org/wiki/Principle_of_least_surprise). Ruby przestrzega tej zasady. –

Odpowiedz

19

Dużą zaletą jest to, że wbudowane funkcje (i operatory) mogą w razie potrzeby zastosować dodatkową logikę, poza zwykłym wywołaniem specjalnych metod. Na przykład min może przeglądać kilka argumentów i stosować odpowiednie sprawdzanie nierówności lub może akceptować pojedynczy argument iteracyjny i postępować podobnie; abs po wywołaniu obiektu bez specjalnej metody może próbować porównać wymieniony obiekt z 0 i, w razie potrzeby, użyć metody zmiany znaku obiektu (mimo że obecnie nie działa); i tak dalej.

Dla zachowania spójności wszystkie operacje o szerokim zastosowaniu muszą zawsze być realizowane przez wbudowane i/lub operatory, a to one są odpowiedzialne za wyszukiwanie i stosowanie odpowiednich specjalnych metod (na jednym lub kilku argumentach).), stosuj logikę alternatywną, jeśli ma to zastosowanie, i tak dalej.

Przykład, w którym ta zasada nie została poprawnie zastosowana (ale niespójność została rozwiązana w języku Python 3), to "wykonaj krok iteratora do przodu": w wersji 2.5 i wcześniejszej konieczne było zdefiniowanie i wywołanie niezwiązanego specjalnie z nazwą next metoda na iteratorze. W wersji 2.6 i późniejszych możesz zrobić to we właściwy sposób: obiekt iteratora definiuje __next__, nowy next może nazwać go i zastosować dodatkową logikę, na przykład do podania wartości domyślnej (w 2.6 nadal możesz to zrobić w zły, stary sposób, ze względu na kompatybilność wsteczną, jednak w 3.* nie możesz już więcej.

Inny przykład: rozważ wyrażenie: x + y. W tradycyjnym języku zorientowanym obiektowo (zdolnym do wysyłania tylko na typ skrajnego lewego argumentu - jak Python, Ruby, Java, C++, C#, & c) jeśli x jest jakiegoś wbudowanego typu, a y jest Twoim własnym fantazyjny nowy typ, niestety nie masz szczęścia, jeśli język nalega na przekazanie całej logiki do metody type(x), która implementuje dodatek (zakładając, że język pozwala operatorowi przeładować ;-).

W Pythonie, operator + (i podobnie oczywiście wbudowane operator.add, jeśli to, co wolisz) próbuje x typ za __add__, a jeśli to się nie wie, co zrobić z y, a następnie próbuje Y typ na __radd__. Możesz więc zdefiniować typy, które potrafią dodawać się do liczb całkowitych, zmiennoprzecinkowych, złożonych itp., A także takie, które wiedzą, jak dodać takie wbudowane typy liczbowe (tzn. Możesz je kodować tak, aby x + y i y + x oba działają poprawnie, gdy y jest instancją Twojego nowego, nowego typu, a x jest instancją jakiegoś wbudowanego typu numerycznego).

„Ogólne funkcje” (jak w peak) są bardziej eleganckie podejście (umożliwiając dowolną nadpisywanie oparty na kombinacji typów nigdy z szalonym naciskiem monomania na skrajnej lewej argumentów OOP zachęca -!), Ale (a) nie zostały niestety zaakceptowane w Pythonie 3, i (b) oczywiście wymagają, aby funkcja generyczna była wyrażana jako wolnostojąca (byłoby całkowicie szalone, aby uważać funkcję za "przynależną" do dowolnego typu) , gdzie cały POINT może być inaczej przesłonięty/przeciążony w oparciu o dowolną kombinację jego typów argumentów! -). Każdy, kto kiedykolwiek był zaprogramowany w Common Lisp, Dylan lub PEAK, wie o czym mówię ;-).

Tak więc, niezależne funkcje i operatory są po prostu właściwą, spójną drogą do przejścia (nawet jeśli brak ogólnych funkcji, w gołych kościach Python, usuwa pewną część nieodłącznej elegancji, to wciąż jest rozsądna mieszanka elegancji i praktyczności! -).

+0

+1 dla doskonałego wyjaśnienia korzyści wbudowanych. – whaley

+1

Mówisz "w stanie wysyłać tylko na typ argumentu z lewej strony - jak Python, C++, ...", a następnie pokazać, że python może również wysłać do właściwego argumentu z '__radd__'. Ponadto C++ może wywołać oba argumenty, jeśli zadeklarujesz 'operator +' jako wolną funkcję. Wydaje się to być trochę niekonsekwentne ... – sth

+2

@sth, Python nie może _dispatch_ na argument RHS - może _użyć wbudowanego lub operatora_ do wykonania pracy gruntowej. W C++, jeśli 'operator +' jest funkcją wolnostojącą, może być przeciążony w oparciu o typy operandów, które są widoczne jawnie_ (również widoczne w czasie kompilacji), po prostu ** CAN NOT ** ** DISPATCH **, tj. używaj wyłącznie ** typów operandów **, aby wybrać kod do wykonania (jak funkcja członkowska może, ale oparta tylko na ** LEWEJ RĘKI typu **). Nic niekonsekwentnego, dla czytelników, którzy wiedzą, co oznacza wysyłka, a różnica między typem kompilacji a czasem pracy! -) –

3

Podkreśla możliwości obiektu, a nie jego metody lub typ. Zdolności są deklarowane przez funkcje "pomocnicze", takie jak __iter__ i __len__, ale nie stanowią interfejsu. Interfejs znajduje się w wbudowanych funkcjach, a obok tego również w operacjach wbudowanych, takich jak + i [], w celu indeksowania i krojenia.

Niekiedy nie jest to korespondencja jeden do jednego: Na przykład: iter(obj) zwraca iterator dla obiektu i będzie działać, nawet jeśli __iter__ nie zostanie zdefiniowany. Jeśli nie jest zdefiniowany, sprawdza, czy obiekt definiuje __getitem__ i zwróci iterator uzyskujący dostęp do obiektu w sposób indeksu (jak tablica).

Jest to zgodne z Python's Duck Typing, dbamy tylko o to, co możemy zrobić z obiektem, a nie o konkretnym typie.

3

W rzeczywistości nie są to metody "statyczne" w sposobie, w jaki o nich myślisz. Są to built-in functions, które naprawdę są tylko aliasami do pewnych metod na obiektach Pythona, które je implementują.

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

Są one zawsze dostępne do wywołania bez względu na to, czy obiekt je zaimplementuje, czy nie. Chodzi o to, żeby mieć pewną spójność. Zamiast niektóre klasa ma metodę o nazwie length() a innym nazwie size(), konwencja jest wdrożenie len i pozwól rozmówcy zawsze do niego dostęp przez bardziej czytelnej len (obj) zamiast obj.methodThatDoesSomethingCommon

+0

Nie, to tylko len. Nie ma specjalnych metod "__min__" lub "__max__". –

+1

Chodzi o to, że pytanie traktuje ich wszystkich podobnie, co jest nieprawidłowe. Ta odpowiedź mówi - słusznie - że nie jest to ogólny wzorzec, ale jest mieszanka rzeczy o wspólnej notacji. –

+0

Nawet 'f .__ len __()' nie jest całkiem aliasem dla 'len (f)'. Jeśli twoja metoda specjalna "__len__" zwraca cokolwiek nie będącego liczbą całkowitą (lub nawet dużą liczbę całkowitą), to funkcja 'len' zawiedzie z' TypeError'. –

1

Myślałem, że powodem było to, że te podstawowe operacje można wykonać na iteratorach z tym samym interfejsem co kontenery. Jednak w rzeczywistości nie działa z len:

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

... kończy się niepowodzeniem z TypeError. len() nie będzie zużywać i liczyć iteratora; działa tylko z obiektami, które mają wywołanie __len__.

Tak więc, o ile mi wiadomo, len() nie powinno istnieć. O wiele bardziej naturalne jest powiedzenie obj.len niż len (obj), i znacznie bardziej zgodne z resztą języka i standardową biblioteką. Nie mówimy append (lst, 1); mówimy lst.append (1). Posiadanie osobnej globalnej metody długości jest nieparzystym, niespójnym przypadkiem specjalnym i zjada bardzo oczywistą nazwę w globalnej przestrzeni nazw, co jest bardzo złym nawykiem Pythona.

Nie ma to związku z pisaniem na maszynie; możesz powiedzieć getattr(obj, "len"), aby zdecydować, czy możesz używać len na obiekcie równie łatwo - i znacznie bardziej konsekwentnie - niż możesz użyć getattr(obj, "__len__").

Wszystko, co powiedzieliśmy, gdy pojawiają się brodawki - dla tych, którzy uważają to za brodawkę - bardzo łatwo z tym żyć.

Z drugiej strony, min i maks. do działają na iteratory, co daje im możliwość korzystania z różnych obiektów. To jest proste, więc będę po prostu dać przykład:

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

Jednakże istnieją żadne __min__ lub __max__ metody aby przesłonić jego zachowania, więc nie ma spójny sposób, aby zapewnić efektywne poszukiwanie segregowanych pojemników. Jeśli kontener jest posortowany na tym samym kluczu, którego szukasz, min/max to operacje O (1) zamiast O (n), a jedynym sposobem na ujawnienie tego jest inna, niespójna metoda. (Można to naprawić w języku stosunkowo łatwo, oczywiście.)

Aby poradzić sobie z innym problemem z tym: zapobiega używaniu metody Pythona. Jako prosty, contrived przykład, można to zrobić, aby dostarczyć funkcji, aby dodać wartości do listy:

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

a to działa na wszystkich funkcji składowych. Nie możesz tego zrobić przy pomocy min, max lub len, ponieważ nie są one metodami obiektu, na którym działają. Zamiast tego musisz uciekać się do functools.partial, niezdarnego rozwiązania drugiej klasy, powszechnego w innych językach.

Oczywiście jest to rzadkość; ale to rzadkie przypadki mówią nam o spójności języka.

Powiązane problemy