IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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__
Riadenie atribútov

__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í.


 

Predchádzajúci článok
Magické metódy Pythone - Matematické
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Riešené úlohy k 12.-15. lekciu OOP v Pythone
Článok pre vás napísal gcx11
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
(^_^)
Aktivity