Dlaczego Python używa „magicznych metod”?


99

Bawiłem się ostatnio Pythonem i jedną rzeczą, która wydaje mi się nieco dziwna, jest szerokie użycie `` magicznych metod '', np. Aby udostępnić swoją długość, obiekt implementuje metodę def __len__(self), a następnie jest wywoływany, gdy piszesz len(obj).

Zastanawiałem się tylko, dlaczego obiekty po prostu nie definiują len(self)metody i nie są wywoływane bezpośrednio jako element członkowski obiektu, np. obj.len()? Jestem pewien, że muszą istnieć dobre powody, aby Python robił to w taki sposób, ale jako początkujący jeszcze nie odkryłem, czym one są.


4
Myślę, że ogólny powód jest a) historyczny ib) coś podobnego len()lub reversed()stosuje się do wielu typów obiektów, ale metoda taka append()ma zastosowanie tylko do sekwencji itp.
— Grant Paul

Odpowiedzi:


64

AFAIK lenjest pod tym względem wyjątkowy i ma historyczne korzenie.

Oto cytat z FAQ :

Dlaczego Python używa metod dla niektórych funkcji (np. List.index ()), a funkcje dla innych (np. Len (lista))?

Głównym powodem jest historia. Funkcje były używane dla tych operacji, które były ogólne dla grupy typów i które miały działać nawet dla obiektów, które w ogóle nie miały metod (np. Krotki). Wygodne jest również posiadanie funkcji, którą można łatwo zastosować do amorficznej kolekcji obiektów, gdy używasz funkcji funkcjonalnych Pythona (map (), apply () et al).

W rzeczywistości implementacja len (), max (), min () jako funkcji wbudowanej to w rzeczywistości mniej kodu niż implementacja ich jako metod dla każdego typu. Można spierać się o pojedyncze przypadki, ale jest to część Pythona i jest już za późno, aby wprowadzić tak fundamentalne zmiany. Funkcje muszą pozostać, aby uniknąć masowego uszkodzenia kodu.

Inne „magiczne metody” ( w folklorze Pythona właściwie nazywane metodami specjalnymi ) mają wiele sensu, a podobne funkcje istnieją w innych językach. Są one głównie używane w przypadku kodu, który jest wywoływany niejawnie, gdy używana jest specjalna składnia.

Na przykład:

  • przeciążone operatory (istnieją w C ++ i innych)
  • konstruktor / destruktor
  • punkty zaczepienia dostępu do atrybutów
  • narzędzia do metaprogramowania

i tak dalej...


2
Python i zasada najmniejszego zdziwienia to dobra lektura, jeśli chodzi o niektóre zalety takiego rozwiązania (chociaż przyznaję, że angielski wymaga pracy). Podstawowa kwestia: pozwala standardowej bibliotece na zaimplementowanie mnóstwa kodu, który staje się bardzo, bardzo przydatny do ponownego wykorzystania, ale nadal można go zastąpić.
— jpmc26

20

Z Zen Pythona:

W obliczu niejasności odrzuć pokusę zgadywania.
Powinien być jeden - a najlepiej tylko jeden - oczywisty sposób na zrobienie tego.

Jest to jeden z powodów - z metod niestandardowych, programiści będą wolne, aby wybrać inną nazwę metody, jak getLength(), length(), getlength()albo w ogóle. Python wymusza ścisłe nazewnictwo, aby len()można było używać wspólnej funkcji .

Wszystkie operacje, które są wspólne dla wielu typów obiektów, są umieszczane w metodach magicznych, takich jak __nonzero__, __len__lub __repr__. Są one jednak w większości opcjonalne.

Przeciążanie operatorów również odbywa się za pomocą metod magicznych (np. __le__), Więc sensowne jest używanie ich również do innych typowych operacji.


To przekonujący argument. Bardziej satysfakcjonujące jest to, że „Guido tak naprawdę nie wierzył w OO”… (jak widziałem w innym miejscu).
— Andy Hayden

15

Python używa słowa „magiczne metody” , ponieważ te metody naprawdę wykonują magię za program. Jedną z największych zalet korzystania z magicznych metod Pythona jest to, że zapewniają one prosty sposób na sprawienie, by obiekty zachowywały się jak typy wbudowane. Oznacza to, że możesz uniknąć brzydkich, sprzecznych z intuicją i niestandardowych sposobów wykonywania podstawowych operatorów.

Rozważmy następujący przykład:

dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}

dict1 + dict2
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Daje to błąd, ponieważ typ słownika nie obsługuje dodawania. Teraz rozszerzmy klasę słownika i dodajmy magiczną metodę „__add__” :

class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

Teraz daje następujący wynik.

{1: 'ABC', 2: 'EFG'}

Tak więc, dodając tę ​​metodę, nagle pojawiła się magia i błąd, który otrzymywałeś wcześniej, zniknął.

Mam nadzieję, że to wszystko wyjaśnia. Więcej informacji można znaleźć w:

Przewodnik po magicznych metodach Pythona (Rafe Kettler, 2012)


9

Niektóre z tych funkcji robią więcej niż jedna metoda byłaby w stanie zaimplementować (bez metod abstrakcyjnych w nadklasie). Na przykład bool()działa mniej więcej tak:

def bool(obj):
    if hasattr(obj, '__nonzero__'):
        return bool(obj.__nonzero__())
    elif hasattr(obj, '__len__'):
        if obj.__len__():
            return True
        else:
            return False
    return True

Możesz także mieć 100% pewności, że bool()zawsze zwróci True lub False; jeśli polegałeś na metodzie, nie możesz być całkowicie pewien, co otrzymasz.

Niektóre inne funkcje, które stosunkowo skomplikowane wdrożenia (bardziej skomplikowane niż podstawowych metod magicznych są prawdopodobnie) są iter()i cmp(), a wszystkie metody (atrybut getattr, setattra delattr). Rzeczy takie jak intrównież dostęp do magicznych metod podczas stosowania przymusu (możesz zaimplementować __int__), ale wykonuj podwójne obowiązki jako typy. len(obj)jest właściwie jedynym przypadkiem, w którym nie wierzę, że kiedykolwiek się różni obj.__len__().


2
Zamiast tego hasattr()użyłbym try:/ except AttributeError:a zamiast tego if obj.__len__(): return True else: return Falsepowiedziałbym tylko, że return obj.__len__() > 0to tylko stylistyczne rzeczy.
— Chris Lutz

W Pythonie 2.6 (do którego bool(x)przywołano x.__nonzero__()) Twoja metoda nie zadziała. instancje bool mają metodę __nonzero__(), a twój kod będzie się wywoływał, gdy obj był bool. Może bool(obj.__bool__())powinieneś być traktowany tak samo jak ty __len__? (A może ten kod faktycznie działa dla Pythona 3?)
— Ponkadoodle

Okrągły charakter bool () był celowo absurdalny, aby odzwierciedlić szczególnie kołowy charakter definicji. Istnieje argument, że należy go po prostu uznać za prymitywny.
— Ian Bicking

Jedyną różnicą (obecnie) między len(x)i x.__len__()jest to, że pierwszy z nich zgłosi OverflowError dla długości, które przekraczają sys.maxsize, podczas gdy drugi generalnie nie dla typów zaimplementowanych w Pythonie. To raczej błąd niż funkcja (np. Obiekt range w Pythonie 3.2 może w większości obsłużyć dowolnie duże zakresy, ale używanie lenz nimi może się nie powieść. Ich również __len__zawodzi, ponieważ są zaimplementowane w C, a nie w Pythonie)
— ncoghlan

4

Nie są to tak naprawdę „magiczne imiona”. To tylko interfejs, który obiekt musi zaimplementować, aby świadczyć daną usługę. W tym sensie nie są one bardziej magiczne niż jakakolwiek predefiniowana definicja interfejsu, którą musisz ponownie zaimplementować.


1

Chociaż przyczyna jest głównie historyczna, w Pythonie istnieją pewne osobliwości, lenktóre sprawiają, że użycie funkcji zamiast metody jest właściwe.

Niektóre operacje w Pythonie są zaimplementowane na przykład jako metody, list.indexa dict.appendpodczas gdy inne są zaimplementowane jako wywołania i metody magiczne, na przykład stri iteri reversed. Te dwie grupy różnią się wystarczająco, więc inne podejście jest uzasadnione:

  1. Są pospolite.
  2. str, intI przyjaciele są typy. Bardziej sensowne jest wywołanie konstruktora.
  3. Implementacja różni się od wywołania funkcji. Na przykład itermoże wywołać, __getitem__jeśli __iter__nie jest dostępny, i obsługuje dodatkowe argumenty, które nie pasują do wywołania metody. Z tego samego powodu it.next()został zmieniony next(it)w ostatnich wersjach Pythona - ma to większy sens.
  4. Niektórzy z nich są bliskimi krewnymi operatorów. Istnieje składnia do wywoływania __iter__i __next__- to się nazywa forpętla. Dla spójności funkcja jest lepsza. I to sprawia, że ​​jest to lepsze dla niektórych optymalizacji.
  5. Niektóre funkcje są po prostu w pewien sposób zbyt podobne do innych - reprdziała jak strdziała. Mając str(x)kontra x.repr()byłoby mylące.
  6. Na przykład niektóre z nich rzadko korzystają z rzeczywistej metody implementacji isinstance.
  7. Niektórzy z nich są faktycznymi operatorami, getattr(x, 'a')to inny sposób działania x.ai getattrmają wiele z wyżej wymienionych cech.

Osobiście nazywam pierwszą grupę metodą metodyczną, a drugą grupę operacyjną. To niezbyt dobre rozróżnienie, ale mam nadzieję, że jakoś pomoże.

Powiedziawszy to, lennie pasuje dokładnie do drugiej grupy. Jest bardziej zbliżony do operacji z pierwszej, z tą różnicą, że jest o wiele bardziej powszechny niż prawie każda z nich. Ale jedyne, co robi, to dzwonienie __len__i jest bardzo blisko L.index. Istnieją jednak pewne różnice. Na przykład __len__może zostać wywołany w celu implementacji innych funkcji, na przykład bool, jeśli metoda została wywołana, lenmożesz zerwać bool(x)z lenmetodą niestandardową , która robi zupełnie inne rzeczy.

Krótko mówiąc, masz zestaw bardzo typowych funkcji, które mogą implementować klasy, a do których można uzyskać dostęp za pośrednictwem operatora, poprzez specjalną funkcję (która zwykle robi więcej niż implementacja, jak zrobiłby to operator), podczas konstruowania obiektu i wszystkie z nich mają wspólne cechy. Cała reszta to metoda. I lenjest pewnym wyjątkiem od tej reguły.


0

Nie ma wiele do dodania do powyższych dwóch postów, ale wszystkie „magiczne” funkcje wcale nie są magiczne. Są częścią modułu __ builtins__, który jest niejawnie / automatycznie importowany podczas uruchamiania interpretera. To znaczy:

from __builtins__ import *

dzieje się za każdym razem przed rozpoczęciem programu.

Zawsze uważałem, że byłoby bardziej poprawne, gdyby Python robił to tylko dla powłoki interaktywnej i wymagał skryptów do importowania różnych części z wbudowanych, których potrzebowali. Prawdopodobnie inna obsługa __ main__ byłaby fajna w powłokach niż interaktywna. W każdym razie sprawdź wszystkie funkcje i zobacz, jak to jest bez nich:

dir (__builtins__)
...
del __builtins__
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.