13. díl - Magické metody v Pythonu kolekce a deskriptory

Python Objektově orientované programování Magické metody v Pythonu kolekce a deskriptory

V tomto dílu se podíváme na magické metody kolekcí a jejich abstraktní bázové třídy.

Magické metody

__len__(self)

Vrací počet položek v dané kolekci.

__getitem__(self, key)

Vrací položku z kolekce podle zadaného klíče, syntaxe platí pro třídy podobné slovníkům. Při neúspěchu má vracet KeyError.

__mising__(self, key)

Metoda je volána metodou __getitem__(), pokud daný klíč v podtřídě slovníku chybí.

__getitem__(self, index)

Vrací položku z kolekce pro danou pozici. Při neúspěchu má vracet IndexError (ve for cyklu označí IndexError konec posloupnosti/řady).

__setitem__(self, key, value)

Nastaví hodnotu položky v kolekci určenou klíčem, syntaxe platí pro třídy podobné slovníkům. Při neúspěchu má vracet KeyError.

__setitem__(self, index, value)

Nastaví hodnotu položky v kolekci určenou pozicí. Při neúspěchu má vracet IndexError.

__delitem__(self, key)

Smaže z kolekce položku s daným klíčem, syntaxe platí pro třídy podobné slovníkům. Při neúspěchu má vracet KeyError.

__delitem__(self, index)

Smaže z kolekce položku s danou pozicí. Při neúspěchu má vracet IndexError

__iter__(self)

Vrací iterátor pro kolekci. Iterátor lze vytovřit za pomoci funkce iter() z objektu, který má magickou metodu __iter__() nebo __getitem__().

__reversed__(self)

Vrací obrácený iterátor pro kolekci, mělo by se reimplementovat, pokud existuje rychlejší implementace než:

for i in reversed(range(len(self))):
    yield self[i]

Abstraktní bázové třídy

Abstraktní bázové třídy nám poskytují seznam rozhraní, které bychom měli implementovat. Rozhraní jsou všechny veřejné metody, včetně metod magických. Pokud implemetujeme potřebné minimum, další metody se z nich odvodí.

Třída dědí od abstraktní metody odvozené metody
Container   __contains__  
Hashable   __hash__  
Iterable   __iter__  
Iterator Iterable __next__ __iter__
Sized   __len__  
Callable   __call__  
Sequence Sized, Iterable, Container __getitem__, __len__ __contains__, __iter__, __reversed__, index, and count
MutableSequence Sequence __getitem__, __setitem__, __delitem__, __len__, insert metody 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 metoda Set + clear, pop, remove, __ior__, __iand__, __ixor__, and __isub__
Mapping Sized, Iterable, Container __getitem__, __iter__, __len__ __contains__, keys, items, values, get, __eq__ a __ne__
MutableMapping Mapping __getitem__, __setitem__, __delitem__, __iter__, __len__ metody Mapping + pop, popitem, clear, update a setdefault
MappingView Sized   __len__
ItemsView MappingView, Set   __contains__, __iter__
KeysView MappingView, Set   __contains__, __iter__
ValueView MappingView   __contains__, __iter__

Řízení atributů

__getattr__(self, name)

Tato metoda se volá, pokud selže hledání atributu přes obvyklé metody. Metoda by měla vrátit hodnotu atributu nebo vyvolat AttributeError.

__setattr__(self, name, value)

Volá se při nastavování atributů namísto přiřazování proměnných do __dict__. Pokud chce třída atribut uložit, měla by zavolat metodu __setattr__() nadtřídy.

__delattr__(self, name)

Mělo by se implementovat pouze v případě, když:

del obj.name

má nějaký smysluplný význam pro třídu.

Ukázka:

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í přepsání přístupu k atributům za pomoci jiné třídy. Můžeme se tak vyhnout zbytečnému opakování property. Třída musí definovat minimálně jednu z metod __get__(), __set__() nebo __delete__(). Díky deskriptoru můžeme vylepšit validování hodnot atributů. Vytvoříme si ukázkový deskriptor Typed, který bude zajišťovat, aby byl atribut 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, instance, owner)

Metoda get přijímá jako parametr instanci a třídu. Třídu instance můžeme získat takto:

owner = type(instance)

V kódu vrátíme hodnotu vlastnosti uloženou v deskriptoru.

__set__(self, instance, value)

Metoda set přijímá jako parametr instanci a hodnotu, na kterou má být atribut nastavený. Při ukládání kontrolujeme, jestli je atribut daného typu. Běžně je nastavený na int (celé číslo). Pokud není, vyvoláme výjimku TypeError.

Tvorba třídy

Nyní si vytvoříme třídu, která bude představovat člověka. Nastavíme mu tři atributy, jméno, věk a váhu. Jméno bude muset být str (řetězec), věk typu int (celé číslo) a váha bude float (desetinné čí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 jméno každé vlastnosti bude deskriptor. Deskriptory vytváříme na úrovni třídy. Každý deskriptor poté obsahuje slovník s instancemi a hodnotami vlastnosti.

Při volání atributů Python pozná, že jde o deskriptory a zavolá metody __get__(), __set__() nebo __delete__().

Samozřejmě můžeme vytvářet i podtřídy, bez nutného redefinování vlastností.

Například podtřída s dodatečnou vlastností iq:

class CleverPerson(Person):

    iq = Typed(ty=int)

    def __init__(self, name, age, weight, iq):
        super().__init__(name, age, weight)
        self.iq = iq

Samozřejmě můžeme vytvářet i podtřídy deskriptorů:

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 referencí

Pro ukládání atributů používáme slovník slabých referencí (WeakKeyDictionary) z modulu weakref. Ten funguje jako normální slovník, avšak liší se v jednom detailu. Pokud pro daný objekt (klíč) ve slovníku neexistuje více referencí než ta ve slovníku, tak se objekt ze slovníku smaže. Díky tomu zamezíme memory leakům v paměti. Obyčejný slovník by zbytečně uchovával odkaz na instanci i po jejím smazání. Pokud používáme třídu se __slots__, musíme do __slots__ přidat "weakref", aby třída podporovala slabé odkazy.

Získání deskriptoru

Pokud chceme upravit deskriptor, musíme přistupovat přes třídu objektu a lehce upravit metodu __get__

Napří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 deskriptoru vytvářet a volat i své vlastní metody.


 

Stáhnout

Staženo 12x (1.68 kB)
Aplikace je včetně zdrojových kódů v jazyce python

 

  Aktivity (1)

Článek pro vás napsal gcx11
Avatar
(^_^)

Jak se ti líbí článek?
Celkem (4 hlasů) :
4.54.54.54.54.5


 



 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!