Nacházíte se zde: Domů Ponořme se do Pythonu 3

Úroveň obtížnosti: ♦♦♦♦♦

Jména speciálních metod

My specialty is being right when other people are wrong.
(Mou specialitou je mít pravdu, když se ostatní lidé mýlí.)
George Bernard Shaw

 

Ponořme se

V celé knize jsme se setkávali s příklady „speciálních metod“ — v jistém smyslu „magických“ metod, které Python vyvolává, když použijeme určitou syntaxi. Pokud vaše třídy použijí speciální metody, mohou se chovat jako množiny, jako slovníky, jako funkce, jako iterátory nebo dokonce jako čísla. Tato příloha slouží jako referenční příručka ke speciálním metodám, se kterými jsme se už setkali, a jako stručný úvod k některým esoteričtějším speciálním metodám.

Základy

Pokud jste už četli úvod k třídám, už jste se setkali s nejběžnější speciální metodou, s metodou __init__(). Většina tříd, které píšeme, nakonec potřebuje nějakou inicializaci. Existuje několik dalších základních speciálních metod, které jsou zvlášť užitečné při ladění našich uživatelsky definovaných tříd.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
inicializace instance x = MyClass() x.__init__()
„oficiální“ řetězcová reprezentace repr(x) x.__repr__()
„neformální“ řetězcová podoba str(x) x.__str__()
„neformální“ podoba v poli bajtů bytes(x) x.__bytes__()
hodnota jako naformátovaný řetězec format(x, format_spec) x.__format__(format_spec)
  1. Metoda __init__() se volá až poté, co byla instance vytvořena. Pokud chceme ovládat proces skutečného vytváření instance, musíme použít metodu __new__().
  2. Metoda __repr__() by podle konvence měla vracet řetězec, který je platným pythonovským výrazem.
  3. Metoda __str__() se volá také v případě, kdy použijeme print(x).
  4. Novinka v Pythonu 3, která souvisí se zavedením typu bytes.
  5. Podle konvence by měl být format_spec v souladu s minijazykem pro specifikaci formátu. Modul decimal.py z pythonovské standardní knihovny má svou vlastní metodu __format__().

Třídy, které se chovají jako iterátory

V kapitole o iterátorech jsme si ukázali, jak můžeme vytvořit iterátor od základů s využitím metod __iter__() a __next__().

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
iterování přes posloupnost iter(seq) seq.__iter__()
získání další hodnoty iterátoru next(seq) seq.__next__()
vytvoření iterátoru procházejícího v opačném pořadí reversed(seq) seq.__reversed__()
  1. Metoda __iter__() se volá, kdykoliv vytváříme nový iterátor. Je to dobré místo pro nastavení počátečních hodnot iterátoru.
  2. Metoda __next__() se volá, kdykoliv se snažíme o získání nové hodnoty iterátoru.
  3. Metoda __reversed__() se běžně nepoužívá. Vezme existující posloupnost a vrací iterátor, který produkuje prvky posloupnosti v opačném pořadí, tj. od posledního k prvnímu.

Jak jsme si ukázali v kapitole o iterátorech, cyklus for se může chovat jako iterátor. V následujícím cyklu:

for x in seq:
    print(x)

Python 3 vytvoří iterátor voláním seq.__iter__() a potom bude získávat hodnoty x voláním jeho metody __next__(). Jakmile metoda __next__() vyvolá výjimku StopIteration, cyklus for spořádaně skončí.

Vypočítávané atributy

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
získat vypočítaný atribut (nepodmíněně) x.my_property x.__getattribute__('my_property')
získat vypočítaný atribut (fallback) x.my_property x.__getattr__('my_property')
nastavit hodnotu atributu x.my_property = value x.__setattr__('my_property', value)
zrušit atribut del x.my_property x.__delattr__('my_property')
vypsat seznam atributů a metod dir(x) x.__dir__()
  1. Pokud třída definuje metodu __getattribute__(), zavolá ji Python při každém odkazu na libovolný atribut nebo jméno metody (s výjimkou jmen speciálních metod, protože by tím vznikl nepříjemný nekonečný cyklus).
  2. Pokud třída definuje metodu __getattr__(), bude ji Python volat až poté, co atribut nenajde na některém z běžných míst. Pokud instance x definuje atribut color, nepovede použití x.color k volání x.__getattr__('color'). Jednoduše se vrátí již definovaná hodnota x.color.
  3. Metoda __setattr__() se volá, kdykoliv chceme atributu přiřadit nějakou hodnotu.
  4. Metoda __delattr__() se volá, kdykoliv chceme atribut zrušit.
  5. Metoda __dir__() je užitečná v případech, kdy definujeme metodu __getattr__() nebo metodu __getattribute__(). Normálně bychom voláním funkce dir(x) získali jen seznam běžných atributů a metod. Pokud například metoda __getattr__() vytváří atribut color dynamicky, nevypisoval by se color v seznamu vraceném funkcí dir(x) jako jeden z dostupných atributů. Předefinování metody __dir__() nám umožní vypsat color jako dostupný atribut. Může to být užitečné pro jiné programátory, kteří si přejí používat naši třídu, aniž by museli zkoumat její vnitřní možnosti.

Rozdíl mezi metodami __getattr__() a __getattribute__() je jemný, ale důležitý. Vysvětlíme si ho na dvou příkladech:

class Dynamo:
    def __getattr__(self, key):
        if key == 'color':         
            return 'PapayaWhip'
        else:
            raise AttributeError   

>>> dyn = Dynamo()
>>> dyn.color                      
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
>>> dyn.color                      
'LemonChiffon'
  1. Jméno atributu se předá metodě __getattr__() jako řetězec. Pokud je jméno rovno 'color', vrátí metoda hodnotu. (V tomto případě se jedná o pevně zadaný řetězec, ale normálně bychom zde provedli nějaký výpočet a vrátili bychom řetězec.)
  2. Pokud jméno atributu neznáme, musí metoda __getattr__() vyvolat výjimku AttributeError. V opačném případě by náš kód při přístupu k nedefinovanému atributu potichu selhal. (Pokud metoda nevyvolá výjimku nebo explicitně nevrátí nějakou hodnotu, pak — z technického hlediska — vrací None, což je pythonovská hodnota null. To znamená, že by všechny atributy, které by nebyly explicitně definovány, nabývaly hodnoty None. To téměř určitě nechceme.)
  3. Instance dyn nemá atribut jménem color, takže se zavolá metoda __getattr__(), která vrátí vypočítanou hodnotu.
  4. Jakmile explicitně nastavíme dyn.color, přestane se metoda __getattr__() pro získání hodnoty dyn.color volat, protože atribut dyn.color už je v instanci definován.

Ve srovnání s tím je metoda __getattribute__() absolutní a nepodmíněná.

class SuperDynamo:
    def __getattribute__(self, key):
        if key == 'color':
            return 'PapayaWhip'
        else:
            raise AttributeError

>>> dyn = SuperDynamo()
>>> dyn.color                      
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
>>> dyn.color                      
'PapayaWhip'
  1. Pro získání hodnoty dyn.color se volá metoda __getattribute__().
  2. Dokonce i když explicitně nastavíme dyn.color, bude se pro získávání hodnoty dyn.color stále volat metoda __getattribute__(). Pokud je metoda __getattribute__() definována, volá se nepodmíněně při hledání každého atributu nebo metody. Platí to i pro atributy, které jsme po vytvoření instance explicitně nastavili (a tím vytvořili).

Pokud vaše třída definuje metodu __getattribute__(), pak pravděpodobně chcete definovat také metodu __setattr__(). Pro udržení přehledu o hodnotách atributů musíte mezi těmito metodami zajistit spolupráci. V opačném případě by se atributy nastavené po vytvoření instance ztrácely v černé díře.

U metody __getattribute__() musíme být velmi pečliví, protože ji Python používá i při hledání jmen metod třídy.

class Rastan:
    def __getattribute__(self, key):
        raise AttributeError           
    def swim(self):
        pass

>>> hero = Rastan()
>>> hero.swim()                        
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __getattribute__
AttributeError
  1. Tato třída definuje metodu __getattribute__(), která vždy vyvolá výjimku AttributeError. Hledání každého atributu nebo metody skončí neúspěšně.
  2. Pokud zavoláme hero.swim(), začne Python v třídě Rastan hledat metodu swim(). Hledání prochází metodou __getattribute__(), protože hledání všech atributů a metod prochází metodou __getattribute__(). V tomto případě metoda __getattribute__() vyvolá výjimku AttributeError, takže hledání metody selže a tím pádem selže i její volání.

Třídy, které se chovají jako funkce

Pokud třída definuje metodu __call__(), můžeme instanci třídy volat (callable), jako kdyby to byla funkce.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
„volat“ instaci jako funkci my_instance() my_instance.__call__()

Modul zipfile tento způsob používá pro definici třídy, která umí zadaným heslem dešifrovat (decrypt) zašifrovaný (encrypted) zip soubor. Dešifrovací algoritmus pro zip vyžaduje, aby se během dešifrování ukládal stav. Pokud dešifrátor (decryptor) definujeme jako třídu, může si stav uchovávat uvnitř instance své třídy. Stav se inicializuje v metodě __init__() a aktualizuje se během dešifrování souboru. Ale protože je třída definována jako „volatelná“ (jako funkce), můžeme instanci třídy předat jako první argument funkce map() takto:

# excerpt from zipfile.py
class _ZipDecrypter:
.
.
.
    def __init__(self, pwd):
        self.key0 = 305419896               
        self.key1 = 591751049
        self.key2 = 878082192
        for p in pwd:
            self._UpdateKeys(p)

    def __call__(self, c):                  
        assert isinstance(c, int)
        k = self.key2 | 2
        c = c ^ (((k * (k^1)) >> 8) & 255)
        self._UpdateKeys(c)
        return c
.
.
.
zd = _ZipDecrypter(pwd)                    
bytes = zef_file.read(12)
h = list(map(zd, bytes[0:12]))             
  1. Třída _ZipDecryptor udržuje stav v podobě tří rotujících klíčů, které se později aktualizují metodou _UpdateKeys() (zde neukázána).
  2. Třída definuje metodu __call__(), která způsobuje, že instance třídy můžeme volat, jako kdyby to byly funkce. V tomto případě metoda __call__() dešifruje jeden bajt ze zip souboru a potom aktualizuje rotující klíče podle hodnoty dešifrovaného bajtu.
  3. zd je instancí třídy _ZipDecryptor. Proměnná pwd (password; heslo) je předána metodě __init__(), která její obsah uloží a použije jej pro první aktualizaci rotujících klíčů.
  4. Máme prvních 12 bajtů zip souboru. Dešifrujeme je zobrazením bajtů přes zd. To znamená, že se 12krát „volá“ zd, což znamená, že se 12krát volá metoda __call__(), která aktualizuje vnitřní stav instance a 12krát vrací výsledný bajt.

Třídy, které se chovají jako množiny

Pokud se naše třída chová jako kontejner pro množinu hodnot — tj. pokud má smysl ptát se, zda naše třída „obsahuje“ hodnotu — , pak by pravděpodobně měla definovat následující speciální metody, které způsobí, že se bude chovat jako množina.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
počet položek len(s) s.__len__()
test, zda posloupnost obsahuje určitou hodnotu x in s s.__contains__(x)

Modul cgi tyto metody používá ve své třídě FieldStorage, která reprezentuje všechna pole formuláře nebo parametry dotazu, které byly zaslány na dynamickou webovou stránku.

# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:                                               
  do_search()

# An excerpt from cgi.py that explains how that works
class FieldStorage:
.
.
.
    def __contains__(self, key):                            
        if self.list is None:
            raise TypeError('not indexable')
        return any(item.name == key for item in self.list)  

    def __len__(self):                                      
        return len(self.keys())                             
  1. Jakmile vytvoříme instanci třídy cgi.FieldStorage, můžeme použít operátor „in“ pro ověření, zda se v řetězci s dotazem nachází určitý parametr.
  2. Kouzlem, které to umožní, je metoda __contains__(). Pokud napíšeme if 'q' in fs, hledá Python metodu __contains__() objektu fs, který je definován v cgi.py. Hodnota 'q' je předána metodě__contains__() jako argument key.
  3. Funkce any() přebírá generátorový výraz. Pokud generátor vyplivne nějaké položky, vrací hodnotu True. Funkce any() je dost chytrá na to, aby zastavila, jakmile je nalezena první shoda.
  4. Stejná třída FieldStorage podporuje také vracení své délky, takže můžeme napsat len(fs) a zavolá se metoda __len__() třídy FieldStorage, která vrátí počet rozpoznaných parametrů dotazu.
  5. Metoda self.keys() kontroluje, zda self.list is None (zda seznam vůbec existuje), takže metoda __len__ nemusí uvedenou kontrolu chyb dublovat.

Třídy, které se chovají jako slovníky

Když předchozí možnosti trošku rozšíříme, můžeme definovat třídy, které nejenže reagují na operátor „in“ a na funkci len(), ale které se mohou chovat jako plnohodnotné slovníky vracející hodnoty vázané na klíče.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
získat hodnotu podle klíče x[key] x.__getitem__(key)
nastavit hodnotu vázanou na klíč x[key] = value x.__setitem__(key, value)
zrušit dvojici klíč-hodnota del x[key] x.__delitem__(key)
vrátit výchozí hodnotu pro chybějící klíče x[nonexistent_key] x.__missing__(nonexistent_key)

Třída FieldStorage z modulu cgi definuje rovněž tyto speciální metody, což znamená, že můžeme dělat například následující věci:

# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:
  do_search(fs['q'])                              

# An excerpt from cgi.py that shows how it works
class FieldStorage:
.
.
.
    def __getitem__(self, key):                   
        if self.list is None:
            raise TypeError('not indexable')
        found = []
        for item in self.list:
            if item.name == key: found.append(item)
        if not found:
            raise KeyError(key)
        if len(found) == 1:
            return found[0]
        else:
            return found
  1. Objekt fs je instancí cgi.FieldStorage, ale přesto můžeme používat výrazy jako fs['q'].
  2. fs['q'] zavolá metodu __getitem__() s parametrem key nastaveným na 'q'. Potom se ve vnitřním seznamu parametrů dotazu (self.list) hledá položka, jejíž atribut .name je roven zadanému klíči.

Třídy, které se chovají jako čísla

Při použití příslušných speciálních metod můžeme definovat své vlastní třídy, které se chovají jako čísla. To znamená, že je můžeme sčítat, odčítat a provádět s nimi další matematické operace. Tímto způsobem jsou implementovány věci v modulu fractions — třída Fraction implementuje speciální metody, které nám umožňují provádět takovéto věci:

>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> x / 3
Fraction(1, 9)

Zde je úplný seznam speciálních metod, které musí implementovat třída chovající se jako číslo.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
sčítání x + y x.__add__(y)
odčítání x - y x.__sub__(y)
násobení x * y x.__mul__(y)
dělení x / y x.__truediv__(y)
celočíselné dělení (floor division) x // y x.__floordiv__(y)
modulo (zbytek) x % y x.__mod__(y)
celočíselné dělení a zbytek divmod(x, y) x.__divmod__(y)
umocnění na x ** y x.__pow__(y)
bitový posun doleva x << y x.__lshift__(y)
bitový posun doprava x >> y x.__rshift__(y)
logický součin po bitech (and) x & y x.__and__(y)
xor po bitech x ^ y x.__xor__(y)
logický součet po bitech (or) x | y x.__or__(y)

Pokud je x instancí třídy, která tyto metody implementuje, bude to fungovat bez problémů. Ale co když třída některou z těchto metod neimplementuje? Nebo ještě hůř — co když je implementuje, ale neporadí si s některými druhy argumentů? Například:

>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> 1 / x
Fraction(3, 1)

Tohle není případ, kdy se vezme Fraction a dělí se celým číslem (jako v předchozím příkladu). Minulý příklad byl přímočarý: x / 3 volá x.__truediv__(3) a metoda __truediv__() třídy Fraction provede matematickou operaci. Ale objekty typu celé číslo (int) „neumí“ dělat aritmetické operace se zlomky. Takže jak je možné, že ten příklad funguje?

Existuje druhá sada aritmetických speciálních metod s obrácenými operandy (reflected operands). Pokud matematická operace vyžaduje dva operandy (například x / y), dá se to řešit dvěma způsoby:

  1. Řekneme x, aby podělilo samo sebe hodnotou y, nebo
  2. řekneme y, aby se zachovalo jako dělitel hodnoty x.

Výše uvedená sada speciálních metod používá první přístup: pokud máme x / y, poskytují metody způsob, jak může x říci: „Já vím, jak vydělit sebe hodnotou y.“ Následující sada speciálních metod se pouští do druhého přístupu — metody poskytují způsob, jakým může y vyjádřit: „Já vím, jak být dělitelem a podělit sebou hodnotu x.“

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
sčítání x + y y.__radd__(x)
odčítání x - y y.__rsub__(x)
násobení x * y y.__rmul__(x)
dělení x / y y.__rtruediv__(x)
celočíselné dělení (floor division) x // y y.__rfloordiv__(x)
modulo (zbytek) x % y y.__rmod__(x)
celočíselné dělení a zbytek divmod(x, y) y.__rdivmod__(x)
umocnění na x ** y y.__rpow__(x)
bitový posun doleva x << y y.__rlshift__(x)
bitový posun doprava x >> y y.__rrshift__(x)
logický součin po bitech (and) x & y y.__rand__(x)
xor po bitech x ^ y y.__rxor__(x)
logický součet po bitech (or) x | y y.__ror__(x)

Ale moment! Ono je toho ještě víc! Pokud provádíme operace „přímo nad proměnnou“ (in-place, in situ, na místě samém), jako například x/=3, můžeme definovat ještě další speciální metody.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
sčítání nad proměnnou x += y x.__iadd__(y)
odčítání nad proměnnou x -= y x.__isub__(y)
násobení nad proměnnou x *= y x.__imul__(y)
dělení nad proměnnou x /= y x.__itruediv__(y)
celočíselné dělení nad proměnnou (floor division) x //= y x.__ifloordiv__(y)
modulo nad proměnnou x %= y x.__imod__(y)
umocnění nad proměnnou x **= y x.__ipow__(y)
bitový posun doleva nad proměnnou x <<= y x.__ilshift__(y)
bitový posun doprava nad proměnnou x >>= y x.__irshift__(y)
logický součin po bitech nad proměnnou (and) x &= y x.__iand__(y)
xor po bitech nad proměnnou x ^= y x.__ixor__(y)
logický součet po bitech nad proměnnou (or) x |= y x.__ior__(y)

Poznámka: Ve většině případů se implementace „in situ“ metod nevyžaduje. Pokud pro určitou operaci příslušnou „in situ“ metodu (tj. nad proměnnou) nedefinujeme, Python se ji pokusí nahradit. Například při provádění výrazu x /= y Python...

  1. Vyzkouší zavolat x.__itruediv__(y). Pokud je metoda definována a vrátila hodnotu jinou než NotImplemented, je to hotové.
  2. Vyzkouší zavolat x.__truediv__(y). Pokud je metoda definována a vrátila hodnotu jinou než NotImplemented, je původní hodnota x zahozena a je nahrazena výslednou hodnotou — jako kdybychom místo toho napsali x = x / y.
  3. Vyzkouší zavolat y.__rtruediv__(x). Pokud je metoda definována a vrátila hodnotu jinou než NotImplemented, je původní hodnota x zahozena a je nahrazena výslednou hodnotou.

Takže „in situ“ metodu jako __itruediv__() definujeme jen v případech, kdy chceme pro in situ operandy provádět nějakou speciální optimalizaci. V opačném případě Python v podstatě přeformuluje požadavek provedení operandu nad proměnnou na běžnou podobu operandu s přiřazením výsledku do proměnné.

Objekty, které se chovají jako číslo, mohou nad sebou provádět také pár „unárních“ matematických operací.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
unární minus (záporné číslo) -x x.__neg__()
unární plus (kladné číslo) +x x.__pos__()
absolutní hodnota abs(x) x.__abs__()
inverze ~x x.__invert__()
převod na komplexní číslo complex(x) x.__complex__()
převod na celé číslo int(x) x.__int__()
převod na reálné číslo float(x) x.__float__()
převod na nejbližší celé číslo zaokrouhlením round(x) x.__round__()
převod na nejbližší číslo zaokrouhlením na n desetinných míst round(x, n) x.__round__(n)
nejmenší celé číslo >= x math.ceil(x) x.__ceil__()
největší celé číslo <= x math.floor(x) x.__floor__()
odseknutí x na nejbližší celé číslo směrem k 0 math.trunc(x) x.__trunc__()
PEP 357 číslo jako index seznamu a_list[x] a_list[x.__index__()]

Třídy, které se dají porovnávat

Tuto část jsem od předchozí oddělil, protože porovnání se neomezuje jen na čísla. Porovnávat se dají hodnoty mnoha datových typů — řetězce, seznamy a dokonce i slovníky. Pokud vytváříme svou vlastní třídu a má smysl uvažovat o porovnávání našeho objektu s jinými objekty, můžeme porovnání implementovat následujícími speciálními metodami.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
rovnost x == y x.__eq__(y)
různost (nerovnost) x != y x.__ne__(y)
menší než x < y x.__lt__(y)
menší než nebo rovno x <= y x.__le__(y)
větší než x > y x.__gt__(y)
větší než nebo rovno x >= y x.__ge__(y)
pravdivostní hodnota v booleovském kontextu if x: x.__bool__()

Pokud definujeme metodu __lt__(), ale nedefinujeme metodu __gt__(), použije Python metodu __lt__() s přehozenými operandy. Ale Python neprovádí kombinaci metod. Pokud například definujeme metodu __lt__() a metodu __eq__() a pokusíme se otestovat, zda je x <= y, Python nezavolá postupně __lt__() a __eq__(). Zavolá pouze metodu __le__().

Třídy, které podporují serializaci

Python podporuje serializaci a deserializaci libovolných objektů. (Většina pythonovských příruček tento proces nazývá „pickling“ a „unpickling“.) Může to být užitečné pro uložení stavu objektu do souboru a jeho pozdější obnovení. Všechny přirozené datové typy již „piklení“ podporují. Pokud vytvoříte uživatelskou třídu a chcete ji umět serializovat, přečtěte si něco o pickle protokolu, abyste věděli, kdy a jak se volají následující speciální metody.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
uživatelská kopie objektu copy.copy(x) x.__copy__()
uživatelská kopie objektu do hloubky (deep copy) copy.deepcopy(x) x.__deepcopy__()
* zjištění stavu objektu před serializací pickle.dump(x, file) x.__getstate__()
* serializace objektu pickle.dump(x, file) x.__reduce__()
* serializace objektu (nový serializační protokol) pickle.dump(x, file, protocol_version) x.__reduce_ex__(protocol_version)
* kontrola nad vytvářením objektu během deserializace (unpickling) x = pickle.load(file) x.__getnewargs__()
* obnovení stavu objektu po deserializaci x = pickle.load(file) x.__setstate__()

* Při znovuvytváření serializovaného objektu musí Python nejdříve vytvořit nový objekt, který vypadá jako ten serializovaný, a potom musí nastavit hodnoty všech jeho atributů. Metoda __getnewargs__() řídí způsob vytváření objektu. Metoda __setstate__() poté řídí obnovení hodnot atributů.

Třídy, které mohou být použity v bloku with

Blok with definuje operační kontext (runtime context). „Vstupujeme“ do něj (enter) v okamžiku provádění příkazu with a „vystupujeme“ z něj (exit) po provedení posledního příkazu v jeho bloku.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
udělej něco speciálního při vstupu do bloku with with x: x.__enter__()
udělej něco speciálního při opouštění bloku with with x: x.__exit__(exc_type, exc_value, traceback)

Obrat with soubor funguje následovně:

# výňatek z io.py
def _checkClosed(self, msg=None):
    '''Internal: raise an ValueError if file is closed
    '''
    if self.closed:
        raise ValueError('I/O operation on closed file.'
                         if msg is None else msg)

def __enter__(self):
    '''Context management protocol.  Returns self.'''
    self._checkClosed()                                
    return self                                        

def __exit__(self, *args):
    '''Context management protocol.  Calls close()'''
    self.close()                                       
  1. Objekt souboru definuje jak metodu __enter__(), tak metodu __exit__(). Metoda __enter__() kontroluje, zda je soubor otevřen. Pokud ne, vyvolá metoda _checkClosed() výjimku.
  2. Metoda __enter__() by měla téměř vždy vrátit self, což je objekt, který bude v bloku with použit pro práci s vlastnostmi (properties) a k volání metod.
  3. Po ukončení bloku with se souborový objekt automaticky uzavře. Jak se to udělá? V metodě __exit__() se zavolá self.close().

Metoda __exit__() se zavolá vždy, dokonce i když je uvnitř bloku with vyvolána výjimka. Ve skutečnosti je to tak, že při vyvolání výjimky je informace o výjimce předána metodě __exit__(). Další detaily naleznete ve standardní dokumentaci: With Statement Context Managers (správci kontextu příkazu with).

O správcích kontextu se dozvíte víc v části Automatické zavírání souborů a Přesměrování standardního výstupu.

Opravdu esoterické věci

Pokud víme, co děláme, můžeme získat téměř úplnou kontrolu nad tím, jak jsou třídy porovnávány, jak jsou definovány atributy a jaký druh tříd se považuje za podtřídy naší třídy.

Poznámky To, co chceme… Takže napíšeme… A Python zavolá…
konstruktor třídy x = MyClass() x.__new__()
* destruktor třídy del x x.__del__()
definovat jen určité atributy x.__slots__()
uživatelská heš-hodnota hash(x) x.__hash__()
získat hodnotu vlastnosti (property) x.color type(x).__dict__['color'].__get__(x, type(x))
nastavit hodnotu vlastnosti x.color = 'PapayaWhip' type(x).__dict__['color'].__set__(x, 'PapayaWhip')
zrušit vlastnost del x.color type(x).__dict__['color'].__del__(x)
zkontrolovat, zda je nějaký objekt instancí naší třídy isinstance(x, MyClass) MyClass.__instancecheck__(x)
zkontrolovat, zda je nějaká třída podtřídou naší třídy issubclass(C, MyClass) MyClass.__subclasscheck__(C)
zkontrolovat, zda je nějaká třída podtřídou naší abstraktní bázové třídy issubclass(C, MyABC) MyABC.__subclasshook__(C)

* Okolnosti toho, kdy přesně Python volá speciální metodu __del__(), jsou neuvěřitelně komplikované. Abyste tomu porozuměli úplně, musíte vědět, jakým způsobem Python sleduje objekty v paměti. Tady najdete dobrý článek o mechanismu automatického uvolňování paměti (garbage collection) a o destruktorech tříd v jazyce Python (anglicky). Měli byste si také přečíst o slabých referencích (weak references), o modulu weakref a navrch pravděpodobně také o modulu gc.

Přečtěte si

Moduly zmíněné v této příloze (standardní dokumentace):

Další objasňující čtení (standardní dokumentace):

© 2001–11 Mark Pilgrim