15. diel - Magické metódy v Pythone - Kolekcia a deskriptory
V minulej lekcii, Magické metódy Pythone - Matematické , sme sa zaoberali matematickými funkciami a operátormi. V tomto Python tutoriálu sa pozrieme na magické metódy kolekcií a ich abstraktné bázovej triedy.
Magické metódy
__len __ (self)
Vracia počet položiek v danej kolekcii.
__getitem __ (self, key)
Vracia položku z kolekcie podľa zadaného kľúča, syntax platí pre triedy podobné slovníkom. Pri neúspechu má vyvolať KeyError.
__missing __ (self, key)
Metóda je volaná metódou __getitem __ (), ak daný kľúč v podtriede slovníku chýba.
__getitem __ (self, index)
Vracia položku z kolekcie pre danú pozíciu. Pri neúspechu má vyvolať IndexError (vo for cykle označí IndexError koniec postupnosti / radu).
__setitem __ (self, key, value)
Nastaví hodnotu položky v kolekciu určenú kľúčom, syntax platí pre triedy podobné slovníkom. Pri neúspechu má vyvolať KeyError.
__setitem __ (self, index, value)
Nastaví hodnotu položky v kolekciu určenú pozíciou. Pri neúspechu má vyvolať IndexError.
__delitem __ (self, key)
Zmaže z kolekcie položku s daným kľúčom, syntax platí pre triedy podobné slovníkom. Pri neúspechu má vracať KeyError.
__delitem __ (self, index)
Zmaže z kolekcie položku s danou pozíciou. Pri neúspechu má vracať IndexError
__iter __ (self)
Vracia iterátor pre kolekciu. Iterátor možno vytovřit za pomoci funkcie iter () z objektu, ktorý má magickú metódu __iter __ () alebo __getitem __ ().
__reversed __ (self)
Vracia obrátený iterátor pre kolekciu, malo by sa reimplementovať, ak existuje rýchlejší implementácie ako:
for i in reversed(range(len(self))): yield self[i]
Abstraktné bázovej triedy
Abstraktné bázovej triedy nám poskytujú zoznam rozhranie, ktoré by sme mali implementovať. Rozhrania sú všetky verejné metódy, vrátane metód magických. Ak implementujeme potrebné minimum, ďalšie metódy sa z nich odvodí.
trieda | dedí od | abstraktné metódy | odvodené metódy |
---|---|---|---|
Container | __contains__ | ||
Hashable | __hash__ | ||
Iterable | __iter__ | ||
Iterator | Iterable | __next__ | __iter__ |
sized | __len__ | ||
callable | __call__ | ||
Sequence | Sized, Iterable, Container | __getitem__, __len__ | __contains__, __iter__, __reversed__, index, a count |
MutableSequence | Sequence | __getitem__, __setitem__, __delitem__, __len__, insert | metódy Sequence + append, reverse, extend, pop, remove a __iadd__ |
set | Sized, Iterable, Container | __contains__, __iter__, __len__ | __le__, __lt__, __eq__, __ne__, __gt__, __ge__, __and__, __or__, __sub__, __xor__ a isdisjoint |
MutableSet | set | __contains__, __iter__, __len__, add, discard | metóda Set + clear, pop, remove, __ior__, __iand__, __ixor__, a __isub__ |
mapping | Sized, Iterable, Container | __getitem__, __iter__, __len__ | __contains__, keys, items, values, get, __eq__ a __ne__ |
MutableMapping | mapping | __getitem__, __setitem__, __delitem__, __iter__, __len__ | metódy Mapping + pop, vypiť, clear, update a setdefault |
MappingView | sized | __len__ | |
ItemsView | MappingView, Set | __contains__, __iter__ | |
KeysView | MappingView, Set | __contains__, __iter__ | |
ValueView | MappingView | __contains__, __iter__ |
__getattr __ (self, name)
Táto metóda sa volá, ak zlyhá hľadanie atribútu cez obvyklé metódy. Metóda by mala vrátiť hodnotu atribútu alebo vyvolať AttributeError.
__setattr __ (self, name, value)
Volá sa pri nastavovaní atribútov namiesto priraďovanie premenných do __dict__. Ak chce trieda atribút uložiť, mala by zavolať metódu __setattr __ () nadtřídy.
__delattr __ (self, name)
Malo by sa implementovať len v prípade, keď:
del obj.name
má nejaký zmysluplný význam pre triedu.
ukážka:
class Foo: def __setattr__(self, name, value): print("Setting") super().__setattr__(name, value) def __getattr__(self, name): print("Getting") return 1 foo = Foo() print(foo.x) print(foo.__dict__) foo.x = 2 print(foo.x) print(foo.__dict__)
Deskriptory
Deskriptory umožňujú prepísanie prístupu k atribútom za pomoci inej triedy. Môžeme sa tak vyhnúť zbytočnému opakovaniu property. Trieda musí definovať minimálne jednu z metód __get __ (), __set __ () alebo __delete __ (). Vďaka deskriptora môžeme vylepšiť validovanie hodnôt atribútov. Vytvoríme si ukážkový deskriptor Typed, ktorý bude zabezpečovať, aby bol atribút určitého typu:
from weakref import WeakKeyDictionary class Typed: dictionary = WeakKeyDictionary() def __init__(self, ty=int): self.ty = ty def __get__(self, instance, owner=None): print("Getting instance") return self.dictionary[instance] def __set__(self, instance, value): print("Setting instance", instance, "to", value) if not isinstance(value, self.ty): raise TypeError("Value must be type {}".format(self.ty)) self.dictionary[instance] = value
__get __ (self, inštancie, owner)
Metóda get prijíma ako parameter inštanciu a triedu. Triedu inštancie môžeme získať takto:
owner = type(instance)
V kóde vrátime hodnotu vlastnosti uloženú v deskriptora.
__set __ (self, inštancie, value)
Metóda set prijíma ako parameter inštanciu a hodnotu, na ktorú má byť atribút nastavený. Pri ukladaní kontrolujeme, či je atribút daného typu. Bežne je nastavený na int (celé číslo). Ak nie je, vyvoláme výnimku TypeError.
Tvorba triedy
Teraz si vytvoríme triedu, ktorá bude predstavovať človeka. Nastavíme mu tri atribúty, meno, vek a váhu. Meno bude musieť byť str (reťazec), vek typu int (celé číslo) a váha bude float (desatinné číslo):
class Person: name = Typed(ty=str) age = Typed() weight = Typed(ty=float) def __init__(self, name, age, weight): self.name = name self.age = age self.weight = weight
Deskriptory vyrobíme tak, že meno každé vlastnosti bude deskriptor. Deskriptory vytvárame na úrovni triedy. Každý deskriptor potom obsahuje slovník s inštanciami a hodnotami vlastnosti.
Pri volaní atribútov Python spozná, že ide o deskriptory a zavolá metódy __get __ (), __set __ () alebo __delete __ ().
Samozrejme môžeme vytvárať aj podtriedy, bez nutného redefinovaní vlastností.
Napríklad podtrieda s dodatočnou vlastnosťou iq:
class CleverPerson(Person): iq = Typed(ty=int) def __init__(self, name, age, weight, iq): super().__init__(name, age, weight) self.iq = iq
Samozrejme môžeme vytvárať aj podtriedy deskriptorov:
class Ranged(Typed): def __init__(self, ty=int, min=0, max=100): super().__init__(ty) self.min = min self.max = max def __set__(self, instance, value): if value < self.min: raise TypeError("Value must be greater than {}".format(self.min) elif value > self.max: raise TypeError("Value must be lower than {}".format(self.max) super().__set__(instance, value)
Slovník slabých referencií
Pre ukladanie atribútov používame slovník slabých referencií (WeakKeyDictionary) z modulu weakref. Ten funguje ako normálny slovník, avšak líšia sa v jednom detaile. Ak pre daný objekt (kľúč) v slovníku neexistuje viac referencií ako tá v slovníku, tak sa objekt zo slovníka vymaže. Vďaka tomu zamedzíme memory leakům v pamäti. Obyčajný slovník by zbytočne bránil odstránenie objektu z pamäte, aj keď by sa už nikde inde nepoužíval, pretože práve slovník by ho používal. Ak používame triedu sa __slots__, musíme do __slots__ pridať "weakref", aby trieda podporovala slabé odkazy.
Získanie deskriptora
Ak chceme upraviť deskriptor, musíme pristupovať cez triedu objektu a ľahko upraviť metódu __get__
napríklad:
from weakref import WeakKeyDictionary class Typed: dictionary = WeakKeyDictionary() def __init__(self, ty=int): self.ty = ty def __get__(self, instance, owner=None): if instance is None: # přistupujeme přes třídu return self print("Getting instance") return self.dictionary[instance] ... CleverPerson.name # získá deskriptor
Takto môžeme na deskriptora vytvárať a volať aj svoje vlastné metódy.
V nasledujúcom cvičení, Riešené úlohy k 12.-15. lekciu OOP v Pythone, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.