Nacházíte se zde: Domů ‣ Ponořme se do Pythonu 3 ‣
Úroveň obtížnosti: ♦♦♦♦♦
❝ 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
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.
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)
|
__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__()
.
__repr__()
by podle konvence měla vracet řetězec, který je platným pythonovským výrazem.
__str__()
se volá také v případě, kdy použijeme print(x)
.
bytes
.
decimal.py
z pythonovské standardní knihovny má svou vlastní metodu __format__()
.
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__()
|
__iter__()
se volá, kdykoliv vytváříme nový iterátor. Je to dobré místo pro nastavení počátečních hodnot iterátoru.
__next__()
se volá, kdykoliv se snažíme o získání nové hodnoty iterátoru.
__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čí.
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__()
|
__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).
__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.
__setattr__()
se volá, kdykoliv chceme atributu přiřadit nějakou hodnotu.
__delattr__()
se volá, kdykoliv chceme atribut zrušit.
__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'
__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.)
__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.)
__getattr__()
, která vrátí vypočítanou hodnotu.
__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'
__getattribute__()
.
__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
__getattribute__()
, která vždy vyvolá výjimku AttributeError
. Hledání každého atributu nebo metody skončí neúspěšně.
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í.
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])) ④
_ZipDecryptor
udržuje stav v podobě tří rotujících klíčů, které se později aktualizují metodou _UpdateKeys()
(zde neukázána).
__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.
_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íčů.
__call__()
, která aktualizuje vnitřní stav instance a 12krát vrací výsledný bajt.
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()) ⑤
cgi.FieldStorage
, můžeme použít operátor „in
“ pro ověření, zda se v řetězci s dotazem nachází určitý parametr.
__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.
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.
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.
self.keys()
kontroluje, zda self.list is None
(zda seznam vůbec existuje), takže metoda __len__
nemusí uvedenou kontrolu chyb dublovat.
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
cgi.FieldStorage
, ale přesto můžeme používat výrazy jako fs['q']
.
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.
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:
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...
x.__itruediv__(y)
. Pokud je metoda definována a vrátila hodnotu jinou než NotImplemented
, je to hotové.
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
.
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__()]
|
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 jex <= y
, Python nezavolá postupně__lt__()
a__eq__()
. Zavolá pouze metodu__le__()
.
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ů.
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() ③
__enter__()
, tak metodu __exit__()
. Metoda __enter__()
kontroluje, zda je soubor otevřen. Pokud ne, vyvolá metoda _checkClosed()
výjimku.
__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.
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ř blokuwith
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.
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
.
Moduly zmíněné v této příloze (standardní dokumentace):
zipfile
cgi
collections
math
pickle
copy
abc
(„Abstract Base Classes“; abstraktní bázové třídy)
Další objasňující čtení (standardní dokumentace):
© 2001–11 Mark Pilgrim