Pokročilý Python: Co jsou to magické metody?

Tento článek upozorňuje na speciální metody Pythonu, které musí znát každý programátor v Pythonu

Farhad Malik

Sledovat

16. května, 2020 – 14 minut čtení

Magické metody nám pomáhají obohatit naše aplikace. Nepřímo přidávají kouzlo do našeho kódu v jazyce Python. Jedná se o téma pro pokročilé vývojáře jazyka Python, které doporučuji všem, kteří používají/hodlají používat programovací jazyk Python.

Magické metody nám dávají větší kontrolu nad tím, jak se naše aplikace chová.

Tento článek si klade za cíl vysvětlit, co jsou magické metody a jak je lze použít při vytváření aplikací v jazyce Python. Poskytne přehled nejpoužívanějších magických metod pro řadu datových typů.

Účelem nastínění klíčových magických metod je, abychom pochopili, zda chceme tyto metody přepsat v našich vlastních třídách a obohatit tak aplikace.

Magické metody činí programovací jazyk Python mimořádně výkonným

Foto: Rodion Kutsaev na Unsplash

Co jsou magické metody Pythonu?

Kouzelné metody jazyka Python jsou také známé jako speciální metody nebo dunderské metody. Jsou obklopeny dvojitým podtržítkem, např. __init__().

Objekt může mít několik magických metod.

Pamatujte, že všechno v Pythonu je objekt, včetně proměnné/funkce/třídy atd. Objekty jsou pythonovskou abstrakcí pro data.

Magické metody slouží ke konstrukci a inicializaci nových objektů, pomáhají nám získat objekt jako slovník, slouží mimo jiné ke smazání objektu. Používají se při volání operátoru + nebo i tehdy, když chceme objekt reprezentovat jako řetězec.

Ačkoli je každá metoda v jazyce Python veřejná, kódovací konvencí je obklopit všechny soukromé metody dvojitým podtržítkem __<metoda>__()

To znamená, že magické metody jsou určeny jako soukromé metody. Znamená to také, že volající objekt by neměl metodu volat přímo, protože metoda je určena k tomu, aby ji interně volala třída, která má magickou metodu.

Můžeme magické metody přepsat a poskytnout tak vlastní funkčnost.

Foto: Cristian Escobar na Unsplash

Vysvětlím pojmy magických metod na vytvoření vlastní třídy a poté uvedu přehled nejdůležitějších magických metod u celého čísla, řetězce, seznamu a slovníku.

Pokračováním článku se začne vytvářet mnohem jasnější představa o tom, proč magické metody existují a jak je používat.

Pokud chcete pochopit programovací jazyk Python od začátečnické až po pokročilou úroveň, pak vřele doporučuji následující článek:

Téma magických metod začnu vytvořením vlastní třídy a poté vysvětlím, jak se magické metody používají.

Příklad použití pro pochopení magických metod

V úryvku níže jsem vytvořil třídu s názvem Human a poté jsem instancoval instanci třídy Human.

Tento úryvek kódu nám poslouží k pochopení magických metod.

Všimněte si, že id objektu human je typu integer, atribut name je typu string, vlastnost addresses je typu list a vlastnost maps je typu dictionary.

Magické metody jsem pro snadnější čtení rozdělil do skupin podle různých datových typů. Stejné magické metody se však vyskytují v různých datových typech.

Magické metody lze přepsat a obohatit tak jejich funkčnost a vytvořit vlastní logiku, která nejlépe vyhovuje obchodním potřebám.

Třída:

Pochopíme magické metody, které jsou spojeny s lidským objektem. Pokud provedu metodu dir(human), vypíše mi všechny funkce a názvy atributů objektu human.

S objektem human je spojeno celkem 29 metod/atributů. Mezi nimi je 26 magických metod.

To je docela velký počet speciálních metod. Tyto metody se dědí ze základního typu třídy Human. Jedná se tedy o předdefinované metody, které můžeme použít/přepsat a obohatit tak třídu.

V další části kapitoly si vysvětlíme klíčové magické metody.

__delattr__

Tato metoda se volá, když se pokusíme odstranit atribut třídy.

Můžeme tuto funkci přepsat implementací metody ve třídě Human:

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

Nyní se při každém pokusu o smazání atributu zobrazí zpráva: Existuje také metoda __setattr__() pro přiřazení hodnoty atributu a __getattr__() pro získání hodnoty z atributu.

Případem použití metody__delattr__() může být zabránění smazání určitých atributů objektu nebo když chceme provést konkrétní akce při smazání určitého atributu.

__dict__

Tato metoda vrací slovník, který reprezentuje objekt. Klíče slovníku jsou atributy objektu a hodnoty jsou hodnoty atributů.

Jako instance:

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

Výše uvedený kód vrací:

{‚id‘: 2, ‚name‘: ‚Malik‘, ‚addresses‘: , ‚maps‘: {}}

__dir__

Můžeme přepsat metodu dir() přepsáním metody __dir__() ve třídě. Jako příklad můžeme z výsledku, který vrací metoda dir(), odstranit interní metody:

__eq__

Tato metoda je volána, když se pokusíme provést operaci ==. Uvažujme, že dva lidské objekty jsou si rovny, když je jejich atribut id stejný, i když se jejich jméno liší.

Můžeme přepsat metodu __eq__(), abychom dosáhli této funkce:

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

Tato metoda nyní vrátí hodnotu True:

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

__format__

Kdykoli se pokusíme provést operaci string.format(), bude interně vyvolána metoda __format__().

__ge__

Například předpokládejme, že existují dva objekty Human:

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

Uvažujme také, že náš projekt má pravidlo, že objekt Human s větším Id je považován za větší než druhý objekt Human. Proto můžeme přepsat metodu __gt__() a implementovat vlastní logiku:

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

Tato metoda nyní vrátí False, protože id druhého lidského objektu je větší než prvního lidského objektu:

print(first >= second)
Returns False

Existuje také metoda __lt__(), která se provede, když se provede operátor ≤.

__hash__

Hashing se používá k převodu objektu na celé číslo. Hashování se provádí, když se pokoušíme nastavit položku ve slovníku/souboru.

Dobrý hashovací algoritmus vede k nižšímu počtu kolizí při hashování. Vlastní hashovací algoritmus můžeme zajistit přepsáním metody __hash__().

Uvažujme, že id objektu Human má být v naší aplikaci jedinečné. Algoritmus __hash__() můžeme přepsat tak, aby vracel self.id jako celé číslo hash:

def __hash__(self):
return self.id

Můžeme vytvořit dva objekty a uložit je do kolekce množin. Při dotazu na délku množiny budeme očekávat dva prvky v množině, protože oba objekty mají jiné id.

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

Pokud nyní nastavíme id na hodnotu 1 pro oba objekty a zopakujeme cvičení, pak uvidíme pouze 1 prvek v množině, protože oba objekty mají stejný hash klíč, protože jejich atribut id je stejný, i když jejich atribut name je jiný.

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

__init__

Metoda __init__() se provádí, když chceme instancovat novou instanci třídy voláním jejího konstruktoru.

Když jsme se pokusili vykonat instanci:

human = Human(1, 'farhad')

Tak se vykonala metoda __init__().

Můžeme tuto funkci přepsat a předat do ní i vlastní argumenty a chování.

Jako příklad lze uvést metodu __init__() třídy Human:

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

Foto: Cederic X on Unsplash

__init_subclass__

To je jeden z případů použití metatřídy. Když je třída podtříděna a je vytvořen její objekt, pak je zavolána metoda __init_subclass__().

V podstatě tato metoda informuje nadřazenou třídu, že byla podtříděna. Tento háček pak může inicializovat všechny podtřídy dané třídy. Metoda tedy slouží k registraci podtříd a přiřazení výchozích hodnot atributům na podtřídách. Proto nám umožňuje přizpůsobit inicializaci podtříd.

V dalším článku vysvětlím, jak fungují metatřídy.

__new__

Když chceme instancovat/vytvořit novou instanci třídy, pak se provede metoda __new__(cls).

Příklad uvažujme, že chceme vypsat ‚Human creating…‘, kdykoli je zavolán konstruktor Human().

Můžeme přepsat funkčnost metody __new__(cls), jak je uvedeno níže:

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

Výsledkem je vypsání Human creating…, když jsme se pokusili vytvořit instanci:

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

__sizeof__

Tato metoda je volána, když provedeme sys.getsizeof(). Vrací velikost objektu v paměti v bytech.

__str__

Vypisuje: id=1. name=Farhad.

Funkce __str__() by se měla pokusit vrátit uživatelsky přívětivou reprezentaci objektu.

__weakref__

Tento objekt __weakref__ vrací seznam slabých referencí na cílový objekt. V podstatě pomáhá garbage collection informovat slabé reference o tom, že referent byl sebrán. Proto zabraňuje objektům přistupovat k podkladovým ukazatelům.

Foto: Yousef Espanioly on Unsplash

Integer

Tím se dostáváme k další části článku. Vidíme, že vlastnost id objektu human je typu int. Pokud pak provedeme nad vlastností id funkci dir() a odfiltrujeme metody, které jsou obklopeny dvojitým podtržítkem, narazíme na to, že magických metod je celkem 62.

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

Vysvětlím zde klíčové metody:

__add__

Tato metoda se volá, když se pokoušíme sečíst dvě čísla.

Jako příklad:

human.id + 2 je stejné jako human.id.__add__(2)

__and__

Tato metoda se provede, když se pokusíme použít operátor & e.G.:

return self & another_value

__bool__

Tato metoda se provede, když se pokoušíme provést kontrolu boolean na objektu např.

self != 123

__floordiv__

Tato metoda se provede, když provádíme //operátor.

Foto: Johannes Plenio on Unsplash

__getnewargs__

Občas v Pythonu piklujeme objekty. Piklování vytvoří v paměti reprezentaci objektu ve formě proudu bajtů, kterou lze v případě potřeby uložit na disk.

Metoda __getnewargs__() informuje proces piklování o tom, jak je třeba načíst zpět piklovaný objekt, aby se znovu vytvořil cílový objekt. Zejména o tom, jak je třeba objekt vytvořit předáním argumentů metodě new(). Odtud název „get new args“.

__index__

Následně lze objekt převést na celé číslo provedením metody __index__(). Tuto metodu můžeme také přepsat a zajistit vlastní funkčnost způsobu, jakým má být index generován.

__invert__

Tato metoda se provede, když použijeme operátor ~.

Jako instance:

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

Je to stejné, jako když provedeme:

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

__lshift__

Tato metoda nám poskytne levý posun hodnoty např. self << hodnoty. Operátor << můžeme přetížit přepsáním metody __lshift__().

Poznámka: __rshift__() se provede, když provedeme operátor >>.

__mod__

Tato metoda se volá, když použijeme operátor %.

__neg__

Tato metoda se provede, když použijeme záporný operátor – např.

first.id — second.id

__subclasshook__

Tuto metodu lze přepsat a přizpůsobit tak metodu issubclass(). V podstatě vrací True, pokud je třída podtřídou, a False, pokud není. Metoda také vrací NotImplemented, což umožňuje použít stávající algoritmus.

Metoda může přizpůsobit výsledek metody issubclass().

Foto: Patrick Selin on Unsplash

String

Tím se dostáváme k další části článku.

Vidíme, že vlastnost name objektu human je typu string. Pokud pak nad touto vlastností provedeme dir(human.name) a odfiltrujeme metody, které jsou obklopeny dvojitým podtržítkem, zjistíme, že magických metod je celkem 33.

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

Vysvětlím zde 4 klíčové metody, protože ve zbytku článku je již na většinu magických metod upozorněno.

__contains__

Tato metoda se provádí, když se snažíme ověřit, zda daný znak existuje.

__len__

Tato metoda vrací délku řetězce. Provádí se, když provádíme metodu len().

Pokud chceme pro výpočet délky řetězce počítat pouze konkrétní znaky, pak lze metodu __()__ přepsat a zajistit tuto funkci.

__repr__

Tato metoda se provádí, když chceme vytvořit vývojářsky přívětivou reprezentaci objektu.

Poznamenejme, že metoda __repr__ se provede při print(object), pokud v naší třídě nemáme implementaci __str__().

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

Takto se vypíše:

id=1 (<třída ‚int‘>). name=Farhad (<třída ‚str‘>)

Metoda __repr__() by měla sloužit k vytvoření oficiální reprezentace objektu.

__iadd__

Občas vedle přiřazení e používáme operátor sčítání.g.

self.id += 1

Toto je ekvivalentní self.id = self.id + 1

Metoda iadd() se provede, když provedeme sčítání s přiřazením.

Dále se metoda __ipow__() provede, když se provede **=.

Kouzelná metoda __iand__() slouží k provedení bitového AND s přiřazením a __ior__() se volá, když se pokoušíme provést !=, například: i != j

Seznam

Tím se dostáváme k další části článku. Vlastnost adresy objektu human je typu seznam. Pokud pak na vlastnost adresy provedeme dir(human.addresses) a odfiltrujeme metody, které jsou obklopeny dvojitým podtržítkem, narazíme na to, že magických metod je celkem 35.

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

Vysvětlím zde klíčové metody:

__reduce__

Když je objekt piklován, provede se metoda __reduce__(), která vrátí objekt, jenž pomůže piklu pochopit, jak jej zkonstruovat zpět.

__reduce_ex__

Metodu __reduce_ex__() upřednostňuje pickle před metodou __reduce__().

Metoda __reduce_ex__() přijímá celočíselný argument, kterým je verze protokolu. Zajišťuje zpětnou kompatibilitu pro piklování a používá se ke konstrukci piklovaného byte-streamu k objektu.

__reversed__

Metoda __reversed__() se provádí, když se pokoušíme reverzovat kolekci v reverzní posloupnosti.

Provádí se při volání reversed(collection) nebo collection.reverse(). Někdy se rozhodneme změnit funkčnost metody reversed().

Přepsáním metody __reversed__() můžeme dosáhnout požadovaného výsledku.

Slovník

Tím se dostáváme k páté části článku.

Slovník je jedním z hlavních vestavěných typů jazyka Python. Pokud provedeme dir(human.maps) a odfiltrujeme metody, které jsou obklopeny dvojitým podtržítkem, narazíme na to, že magických metod je celkem 29.

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

Mezi 29 magickými metodami zde vysvětlím 4 klíčové metody:

__delitem__

Tato metoda se provede, když smažeme položku e.g.

del dictionary

__getitem__

Tato metoda se provede, když se pokusíme získat položku pro klíč:

item = dictionary

__setitem__

Tato metoda se provede, když se pokusíme nastavit položku ve slovníku:

dictionary = item

__iter__

Tato metoda vrací iterátor pro kolekci. Iterátor nám pomáhá iterovat po kolekci.

Způsob provedení iterátoru() můžeme přepsat přepsáním metody __iter__().

Nakonec poznámka k __call__()

Co kdybychom chtěli, aby byl náš objekt volatelný? Uvažujme, že bychom chtěli z objektu člověk udělat volatelnou funkci human()?

Metoda __call__() nám umožňuje zacházet se třídami jako s funkcemi.

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

Této funkce můžeme dosáhnout tím, že v naší třídě Human poskytneme implementaci magické metody __call__().

Toto vypíše:

Pokusili jste se zavolat
Argumenty: ()
Klíčové slovo Argumenty: {
id=1 (<třída ‚int‘>). name=Farhad (<třída ‚str‘>)
Volání dokončeno

Přepsáním metody __call__() můžeme nyní implementovat dekorátor, který vrátí objekt jako funkci, nebo dokonce volat ty knihovny, které přijímají funkce jako argumenty, předáním skutečných objektů.

Foto: Dollar Gill na Unsplash

Shrnutí

Téma je pro vývojáře Pythonu na pokročilé úrovni a doporučuji ho všem, kteří používají/hodlají používat programovací jazyk Python.

Tento článek si kladl za cíl vysvětlit, co jsou to magické metody a jak je lze použít při vytváření aplikací v jazyce Python. Poskytl přehled nejpoužívanějších magických metod ve vlastní třídě, celých čísel, řetězců, seznamů a slovníkových datových typů.

Ačkoli je každá metoda v jazyce Python veřejná, kódovací konvencí je obklopit všechny soukromé metody dvojitým podtržítkem __<metoda>__()

To znamená, že magické metody jsou určeny jako soukromé metody. Znamená to také, že volající objekt by neměl metodu přímo volat a metoda je volána interně třídou, která má magickou metodu. Magické metody nám umožňují mít větší kontrolu nad tím, jak se naše aplikace chová.

Magické metody lze přepsat, abychom obohatili funkčnost a vytvořili vlastní logiku, která nejlépe vyhovuje obchodním potřebám.

Účelem nastínění klíčových magických metod je, abychom pochopili, zda chceme tyto metody přepsat v našich vlastních třídách, abychom obohatili aplikace.

Magické metody se dají přepsat i v jiných třídách.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.