Tento článek upozorňuje na speciální metody Pythonu, které musí znát každý programátor v Pythonu
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
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.
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
__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.
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.
__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().
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ů.
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.