Zaawansowany Python: Czym są metody magiczne?

Ten artykuł podkreśla obowiązkowe metody specjalne Pythona, które każdy programista Pythona musi znać

Farhad Malik

Follow

16 maja, 2020 – 14 min read

Metody magiczne pomagają nam wzbogacić nasze aplikacje. Pośrednio dodają magii do naszego kodu Pythona. Jest to temat na poziomie zaawansowanym dla programistów Pythona i polecam go każdemu, kto używa/zamierza używać języka programowania Python.

Metody magiczne dają nam większą kontrolę nad tym, jak zachowuje się nasza aplikacja.

Ten artykuł ma na celu wyjaśnienie, czym są metody magiczne i jak można je wykorzystać do budowania aplikacji w Pythonie. Przedstawi on przegląd najczęściej używanych metod magicznych dla różnych typów danych.

Celem nakreślenia kluczowych metod magicznych jest zrozumienie, czy chcemy nadpisać te metody w naszych niestandardowych klasach, aby wzbogacić aplikacje.

Metody magiczne czynią język programowania Python niezwykle potężnym

Photo by Rodion Kutsaev on Unsplash

What Are The Python Magic Methods?

Metody magiczne Pythona są również znane jako metody specjalne lub metody dunder. Są one otoczone podwójnymi podkreślnikami, np. __init__().

Obiekt może mieć wiele metod magicznych.

Pamiętaj, że wszystko w Pythonie jest obiektem, w tym zmienna/funkcja/klasa itd. Obiekty są abstrakcją Pythona dla danych.

Metody magiczne są używane do konstruowania i inicjalizowania nowych obiektów, pomagają nam odzyskać obiekt jako słownik, są używane do usuwania obiektu wśród innych operacji. Są używane, gdy wywoływany jest operator +, a nawet gdy chcemy reprezentować obiekt jako ciąg znaków.

Chociaż każda metoda w Pythonie jest publiczna, konwencją kodowania jest otaczanie wszystkich metod prywatnych podwójnymi podkreślnikami __<metoda>__()

To sugeruje, że metody magiczne są przeznaczone do bycia metodami prywatnymi. Oznacza to również, że wywołujący obiekt nie powinien wywoływać tej metody bezpośrednio, ponieważ metoda ta ma być wywoływana przez klasę wewnętrznie posiadającą metodę magiczną.

Możemy nadpisać metody magiczne, aby zapewnić własną funkcjonalność.

Photo by Cristian Escobar on Unsplash

Wyjaśnię koncepcje metod magicznych poprzez stworzenie klasy niestandardowej, a następnie przedstawię przegląd najważniejszych metod magicznych w liczbie całkowitej, łańcuchu, liście i słowniku.

W miarę postępów w dół artykułu, zacznie budować znacznie jaśniejszy obraz tego, dlaczego metody magiczne istnieją i jak z nich korzystać.

Jeśli chcesz zrozumieć język programowania Python od poziomu początkującego do zaawansowanego, to gorąco polecam poniższy artykuł:

Zacznę temat metod magicznych od stworzenia klasy niestandardowej, a następnie wyjaśnię, jak są używane metody magiczne.

Przypadek użycia do zrozumienia metod magicznych

W poniższym snippecie utworzyłem klasę o nazwie Human, a następnie stworzyłem instancję klasy Human.

Ten fragment kodu posłuży nam do zrozumienia metod magicznych.

Zauważ, że id obiektu Human jest typu całkowitego, atrybut name jest typu string, właściwość addresses jest typu listowego, a właściwość maps jest typu słownikowego.

Dla ułatwienia czytania pogrupowałem metody magiczne w różne sekcje typów danych. Jednak te same metody magiczne znajdują się w różnych typach danych.

Metody magiczne mogą zostać nadpisane w celu wzbogacenia funkcjonalności i stworzenia niestandardowej logiki, która najlepiej odpowiada potrzebom biznesowym.

Klasa:

Zrozummy metody magiczne, które są związane z obiektem człowiek. Jeśli wykonam metodę dir(human), wyświetli ona listę wszystkich funkcji i nazw atrybutów obiektu human.

Istnieje w sumie 29 metod/atrybutów, które są związane z obiektem human. Wśród nich 26 to metody magiczne.

To całkiem spora liczba metod specjalnych. Metody te są dziedziczone z typu bazowego klasy Human. Stąd są to predefiniowane metody, których możemy używać/override w celu wzbogacenia klas.

W następnej części rozdziału wyjaśnimy kluczowe metody magiczne.

__delattr__

Ta metoda jest wywoływana, gdy próbujemy usunąć atrybut z klasy.

Możemy nadpisać tę funkcjonalność, implementując metodę w klasie Human:

def __delattr__(self, item):
print('Deleting attribute ' + item)
return object.__delattr__(self, item)

Teraz, przy każdej próbie usunięcia atrybutu, będzie ona wyświetlała komunikat: Usuwanie atrybutu

Istnieje również metoda __setattr__() do przypisania wartości do atrybutu oraz __getattr__() do uzyskania wartości z atrybutu.

Przypadkiem użycia metody __delattr__() może być uniemożliwienie usunięcia poszczególnych atrybutów obiektu lub gdy chcemy wykonać określone akcje, gdy dany atrybut zostanie usunięty.

__dict__

Metoda ta zwraca słownik reprezentujący obiekt. Klucze słownika to atrybuty obiektu, a wartości to wartości atrybutów.

Jako instancja:

human = Human(2, 'Malik').__dict__
print(human)

Powyższy kod zwraca:

{’id’: 2, 'name’: 'Malik’, 'addresses’: , 'maps’: {}}

__dir__

Możemy nadpisać metodę dir() poprzez nadpisanie metody __dir__() w klasie. Jako przykład, możemy usunąć wewnętrzne metody z wyniku zwracanego przez metodę dir():

__eq__

Ta metoda jest wywoływana, gdy próbujemy wykonać operację ==. Rozważmy, że dwa obiekty ludzkie są równe, gdy ich atrybut id jest równy, nawet jeśli ich nazwy są różne.

Możemy nadpisać metodę __eq__(), aby osiągnąć tę funkcjonalność:

def __eq__(self, other):
return self.id == other.id

Ta metoda będzie teraz zwracać True:

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
print(first == second)

__format__

Kiedy próbujemy wykonać string.format(), metoda __format__() jest wywoływana wewnętrznie.

__ge__

Jako przykład załóżmy, że istnieją dwa obiekty typu Human:

first = Human(1, 'Farhad')
second = Human(2, 'Malik')

Zastanówmy się również, że nasz projekt ma zasadę, że obiekt typu Human z większym Id jest uważany za większy niż drugi obiekt typu Human. Dlatego możemy nadpisać metodę __gt__() i zaimplementować niestandardową logikę:

def __ge__(self, other):
return self.id >= other.id

Teraz zwróci ona False, ponieważ id drugiego obiektu ludzkiego jest większe niż pierwszego obiektu ludzkiego:

print(first >= second)
Returns False

Istnieje również metoda __lt__(), która jest wykonywana, gdy wykonywany jest operator ≤.

__hash__

Hashing jest używany do konwersji obiektu na liczbę całkowitą. Haszowanie jest wykonywane, gdy próbujemy ustawić element w słowniku/zestawie.

Dobry algorytm haszowania powoduje mniejszą liczbę kolizji haszowania. Możemy dostarczyć nasz własny algorytm haszujący poprzez nadpisanie metody __hash__().

Zastanówmy się, że id obiektu Human ma być unikalne w naszej aplikacji. Algorytm __hash__() może zostać nadpisany, aby zwrócić self.id jako liczbę całkowitą hash:

def __hash__(self):
return self.id

Możemy utworzyć dwa obiekty i zapisać je w kolekcji set. Kiedy zapytamy o długość zestawu, będziemy oczekiwać dwóch elementów w zestawie, ponieważ oba obiekty mają różne id.

first = Human(1, 'Farhad')
second = Human(2, 'Malik')
my_set = set()
print(len(my_set))
#returns 2

Jeśli teraz ustawimy id na 1 dla obu obiektów i powtórzymy ćwiczenie, wtedy zobaczymy tylko 1 element w zestawie, ponieważ oba obiekty mają ten sam klucz hash, ponieważ ich atrybut id jest taki sam, mimo że ich atrybut name jest inny.

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
my_set = set()
print(len(my_set))
#returns 1

__init__

Metoda __init__() jest wykonywana, gdy chcemy zainicjować nową instancję klasy poprzez wywołanie jej konstruktora.

Jako instancja, gdy próbowaliśmy wykonać:

human = Human(1, 'farhad')

Wtedy została wykonana metoda __init__().

Możemy nadpisać tę funkcjonalność i przekazać w niej również nasze własne niestandardowe argumenty i zachowanie.

Jako instancja, metoda __init__() klasy Human to:

def __init__(self, id, name, addresses=, maps={}):
self.id = id
self.name = name
self.addresses = addresses
self.maps = maps

Photo by Cederic X on Unsplash

__init_subclass__

Jest to jeden z przypadków użycia metaklasy. Kiedy klasa jest podklasowana i jej obiekt jest tworzony, wtedy wywoływana jest metoda __init_subclass__().

Podstawowo, metoda informuje rodzica, że została podklasowana. Ten hak może następnie zainicjalizować wszystkie podklasy danej klasy. Metoda ta służy więc do rejestrowania podklas i przypisywania domyślnych wartości do atrybutów w podklasach. Stąd, pozwala nam to na dostosowanie inicjalizacji podklas.

Wyjaśnię jak działają metaklasy w moim następnym artykule.

__new__

Gdy chcemy zainicjować/stworzyć nową instancję klasy wtedy wykonywana jest metoda __new__(cls).

Jako instancję rozważmy, że chcemy wypisać 'Człowiek tworzący…’ za każdym razem, gdy wywoływany jest konstruktor Human().

Możemy nadpisać funkcjonalność metody __new__(cls), jak pokazano poniżej:

def __new__(cls, *args, **kwargs):
print('Human creating...')
return object.__new__(cls)

W rezultacie, Human creating… jest wypisywany, gdy próbujemy utworzyć instancję:

human = Human(1, 'Farhad', , {'London':2, 'UK':3})

__sizeof__

Ta metoda jest wywoływana, gdy wykonujemy sys.getsizeof(). Zwraca ona rozmiar obiektu w pamięci, w bajtach.

__str__

Wyświetliła: id=1. name=Farhad.

Funkcja __str__() powinna próbować zwrócić przyjazną dla użytkownika reprezentację obiektu.

__weakref__

Ten obiekt __weakref__ zwraca listę słabych referencji do obiektu docelowego. Zasadniczo pomaga odśmiecaniu poinformować słabe referencje, że referent został zebrany. W związku z tym uniemożliwia obiektom dostęp do bazowych wskaźników.

Photo by Yousef Espanioly on Unsplash

Integer

To prowadzi nas do kolejnej części artykułu. Widzimy, że właściwość id obiektu human jest typu int. Jeśli następnie wykonamy dir() na właściwości id i odfiltrujemy metody, które są otoczone podwójnymi podkreślnikami, napotkamy, że w sumie istnieją 62 metody magiczne.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.id)))))

Wyjaśnię tutaj kluczowe metody:

__add__

Ta metoda jest wywoływana, gdy próbujemy dodać dwie liczby.

Jako przykład:

human.id + 2 jest takie samo jak human.id.__add__(2)

__and__

Ta metoda jest wykonywana, gdy próbujemy użyć operatora & np.g.:

return self & another_value

__bool__

Ta metoda jest wykonywana, gdy próbujemy wykonać sprawdzenie boolean na obiekcie np.

self != 123

__floordiv__

Ta metoda jest wykonywana, gdy wykonujemy //operator.

Photo by Johannes Plenio on Unsplash

__getnewargs__

Okresowo w Pythonie piklujemy obiekty. Piklowanie tworzy w pamięci reprezentację strumienia bajtów obiektu, który w razie potrzeby można zapisać na dysku.

Metoda __getnewargs__() informuje proces piklowania, w jaki sposób musi on wczytać z powrotem piklowany obiekt, aby odtworzyć obiekt docelowy. W szczególności, w jaki sposób obiekt musi zostać utworzony poprzez przekazanie argumentów do metody new(). Stąd nazwa 'get new args’.

__index__

Następnie, obiekt może zostać zamieniony na liczbę całkowitą poprzez wykonanie metody __index__(). Możemy również nadpisać tę metodę i dostarczyć własną funkcjonalność tego, jak ma być generowany indeks.

__invert__

Ta metoda jest wykonywana, gdy używamy operatora ~.

Jako instancja:

first = Human(1, 'Farhad')
print(~first.id)

Jest to to samo co wykonanie:

first = Human(1, 'Farhad')
print(first.id.__invert__())

__lshift__

Ta metoda daje nam przesunięcie w lewo wartości np. self << wartość. Możemy przeciążyć operator << przez nadpisanie metody __lshift__().

Uwaga: __rshift__() jest wykonywana, gdy wykonujemy operator >>.

__mod__

Ta metoda jest wywoływana, gdy używamy operatora %.

__neg__

Ta metoda jest wykonywana, gdy używamy ujemnego operatora – np.

first.id — second.id

__subclasshook__

Ta metoda może być nadpisana, aby dostosować metodę issubclass(). Zasadniczo, zwraca ona True jeśli klasa jest podklasą i False jeśli nie jest. Metoda zwraca również NotImplemented, co pozwala na użycie istniejącego algorytmu.

Metoda ta może dostosować wynik metody issubclass().

Photo by Patrick Selin on Unsplash

String

To prowadzi nas do kolejnej części artykułu.

Widzimy, że właściwość name obiektu human jest typu string. Jeśli następnie wykonamy dir(human.name) na tej właściwości i odfiltrujemy metody, które są otoczone podwójnymi podkreślnikami, zauważymy, że istnieją w sumie 33 metody magiczne.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.name)))))

Wyjaśnię tutaj 4 kluczowe metody, ponieważ reszta artykułu podkreśliła już większość metod magicznych.

__contains__

Ta metoda jest wykonywana, gdy próbujemy sprawdzić, czy dany znak istnieje.

__len__

Ta metoda zwraca długość łańcucha. Jest ona wykonywana, gdy wykonujemy metodę len().

Jeśli chcemy liczyć tylko określone znaki, aby obliczyć długość łańcucha, wtedy metoda __()__ może zostać nadpisana, aby zapewnić taką funkcjonalność.

__repr__

Ta metoda jest wykonywana, gdy chcemy stworzyć przyjazną dla dewelopera reprezentację obiektu.

Uwaga, __repr__ jest wykonywana, gdy print(object), jeśli nie mamy implementacji __str__() w naszej klasie.

def __repr__(self):
return f'id={self.id} ({type(self.id)}). name={self.name} ({type(self.name)})'print(Human(1, 'Farhad'))

To spowoduje wydrukowanie:

id=1 (<klasa 'int’>). name=Farhad (<klasa 'str’>)

Metoda __repr__() powinna być przeznaczona do tworzenia oficjalnej reprezentacji obiektu.

__iadd__

Okcydentalnie, obok przypisania e używamy operatora dodawania.g.

self.id += 1

Jest to równoważne self.id = self.id + 1

Metoda iadd() jest wykonywana, gdy wykonujemy dodawanie z przypisaniem.

Ponadto, metoda __ipow__() jest wykonywana, gdy wykonywane jest **=.

Magiczna metoda __iand__() służy do wykonywania bitowego AND z przypisaniem, a __ior__() jest wywoływana, gdy próbujemy zrobić != np: i != j

List

To prowadzi nas do kolejnej części artykułu. Właściwość addresses obiektu human jest typu list. Jeśli następnie wykonamy dir(human.addresses) na właściwości addresses i odfiltrujemy metody, które są otoczone podwójnymi podkreślnikami, napotkamy, że istnieje w sumie 35 metod magicznych.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.addresses)))))

Wyjaśnię tutaj kluczowe metody:

__reduce__

Gdy obiekt jest wytrawiany, wykonywana jest metoda __reduce__(), aby zwrócić obiekt, który pomaga wytrawiaczowi zrozumieć, jak skonstruować go z powrotem.

__reduce_ex__

Metoda __reduce_ex__() jest preferowana przez pickle w stosunku do metody __reduce__().

Metoda __reduce_ex__() przyjmuje argument liczby całkowitej, który jest wersją protokołu. Zapewnia ona wsteczną kompatybilność dla trawienia i jest używana do konstruowania wytrawionego strumienia bajtów do obiektu.

__reversed__

Metoda __reversed__() jest wykonywana, gdy próbujemy odwrócić kolekcję w odwrotnej kolejności.

Wykonywana jest, gdy wywoływana jest metoda reversed(collection) lub collection.reverse(). Czasami postanawiamy zmienić funkcjonalność metody reversed().

Przez nadpisanie metody __reversed__() możemy osiągnąć pożądany rezultat.

Słownik

To prowadzi nas do piątej części artykułu.

Słownik jest jednym z głównych typów wbudowanych w Pythonie. Jeśli wykonamy dir(human.maps) i odfiltrujemy metody, które są otoczone podwójnymi podkreślnikami, napotkamy, że jest w sumie 29 magicznych metod.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.maps)))))

Pośród 29 magicznych metod, wyjaśnię tutaj 4 kluczowe metody:

__delitem__

Ta metoda jest wykonywana, gdy usuwamy element np.g.

del dictionary

__getitem__

Ta metoda jest wykonywana, gdy próbujemy uzyskać element dla klucza:

item = dictionary

__setitem__

Ta metoda jest wykonywana, gdy próbujemy ustawić element w słowniku:

dictionary = item

__iter__

Ta metoda zwraca iterator dla kolekcji. Iterator pomaga nam iterować po kolekcji.

Możemy nadpisać sposób, w jaki wykonywany jest iterator(), nadpisując metodę __iter__().

Na koniec uwaga o __call__()

Co by było, gdybyśmy chcieli uczynić nasz obiekt wywoływalnym? Zastanówmy się, że chcieliśmy zrobić z obiektu human wywoływalną funkcję human()?

Metoda __call__() pozwala nam traktować klasy jak funkcje.

human = Human(1, 'Farhad')
human()

Możemy osiągnąć tę funkcjonalność, dostarczając implementację magicznej metody __call__() w naszej klasie Human.

Wyświetli to:

Próbowałeś wywołać
Argumenty: ()
Słowo kluczowe Argumenty: {}
id=1 (<klasa 'int’>). name=Farhad (<klasa 'str’>)
Połączenie zakończone

Przez nadpisanie metody __call__() możemy teraz zaimplementować dekorator, aby zwrócić obiekt jako funkcję lub nawet wywołać te biblioteki, które akceptują funkcje jako argumenty, przekazując rzeczywiste obiekty.

Photo by Dollar Gill on Unsplash

Podsumowanie

To jest temat na poziomie zaawansowanym dla programistów Pythona i polecam go każdemu, kto używa/ lub zamierza używać języka programowania Python.

Ten artykuł miał na celu wyjaśnienie, czym są metody magiczne i jak można ich używać do budowania aplikacji w Pythonie. Zawierał przegląd najczęściej używanych metod magicznych w klasie niestandardowej, liczb całkowitych, łańcuchów, list i słownikowych typów danych.

Pomimo że każda metoda w Pythonie jest publiczna, konwencją kodowania jest otaczanie wszystkich metod prywatnych podwójnym podkreśleniem __<metoda>__()

To sugeruje, że metody magiczne mają być metodami prywatnymi. Oznacza to również, że wywołujący obiekt nie powinien bezpośrednio wywoływać tej metody, a metoda jest wywoływana wewnętrznie przez klasę, która posiada metodę magiczną. Metody magiczne pozwalają nam mieć większą kontrolę nad tym, jak zachowuje się nasza aplikacja.

Metody magiczne mogą być nadpisane, aby wzbogacić funkcjonalność i stworzyć niestandardową logikę, która najlepiej odpowiada potrzebom biznesowym.

Celem nakreślenia kluczowych metod magicznych jest dla nas zrozumienie, czy chcemy nadpisać te metody w naszych niestandardowych klasach, aby wzbogacić aplikacje.

.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.