Пориньте у Python 3/Імена магічних методів
Моя професія - бути правим, коли всі інші помиляються.
Бернард Шоу
Іноді в цій книжці ви бачили приклади "спеціальних методів" - деяких "магічних методів" які Python викликає коли ви використовуєте спеціальний синтаксис. Використовуючи ці спеціальні методи ваші класи можуть поводитись як словники, як функції, як ітератори, чи навіть як числа. Цей додаток слугує одночасно і як довідник по тих методах які ми вже розглядали, і як короткий вступ до більш езотеричних.
Базові
ред.Якщо ви читали вступ до класів, ви вже знайомі з найбільш поширеним магічним методом - __init__()
. Більшість класів які я писав потребували якоїсь ініціалізації. Також є кілька базових магічних методів які особливо корисні при зневадженні ваших класів.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Ініціалізувати екземпляр | x = MyClass()
|
x.__init__()
|
Метод __init__()
викликається після створення екземпляра. Якщо ви хочете конролювати сам процес створення екземпляра, використайте метод __new__()
.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
"офіційне" представлення у вигляді рядка | repr(x)
|
x.__repr__()
|
За домовленістю, __repr__()
повинен повертати рядок, який є правильним виразом Python.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
"неофіційне" представлення у вигляді рядка | str(x)
|
x.__str__()
|
Метод __str__()
також викликається коли ви виконуєте print(x)
.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
"неофіційне" представлення у вигляді масиву байт | bytes(x)
|
x.__bytes__()
|
А цей магічний метод з’явився в Python 3 разом з типом bytes
.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
значення як форматований рядок | format(x, format_spec)
|
x.__format__(format_spec)
|
За замовчуванням, format_spec
повинен відповідати мінімові специфікації формату. decimal.py
в стандартній бібліотеці мови Python надає власний метод __format__()
.
Класи що поводяться як ітератори
ред.У розділі про ітератори ви читали про те як побудувати ітератор з нуля за допомогою методів __iter__()
та __next__()
.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Ітеруватись через послідовність | iter(seq)
|
seq.__iter__()
|
Метод __iter__
викликається щоразу як ви створюєте новий ітератор. Це гарне місце для того, щоб ініціалізувати ітератор з початковими значеннями.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
отримати наступне значення з ітератора | next(seq)
|
seq.__next__()
|
Метод __next__()
викликається щоразу як ви отримуєте наступне значення з ітератора.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Створити обернений ітератор | reversed(seq)
|
seq.__reversed__()
|
Метод __reversed__
не дуже часто зустрічається. Він приймає існуючу послідовність та повертає ітератор, який видає її елементи в зворотньому порядку, від останнього до першого.
Як ви бачили в розділі про ітератори, цикл for
може працювати над ітератором. В такому циклі:
for x in seq:
print(x)
Python 3 викличе seq.__iter__()
щоб створити ітератор, потім метод __next__()
щоб отримати кожне із значень. Коли метод __next__()
згенерує виняток StopIteration
, цикл for
елегантно завершиться.
Обчислювані атрибути
ред.Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Отримати обчислений атрибут (безумовно) | x.my_attr
|
x.__getattribute__('my_attr')
|
Якщо ваш клас описує метод __getattribute__()
Python викликатиме його при кожному звертанні до будь-якого атрибуту чи методу (окрім спеціальних методів, тому що це може створити негарний нескінченний цикл).
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Отримати обчислений атрибут (якщо інших не знайдеться) | x.my_attr
|
x.__getattr__('my_attr')
|
Якщо клас описує метод __getattr__()
, Python викличе його лише після пошуку атрибута в усіх нормальних місцях. Якщо екземпляр x
описує атрибут color
, x.color
не викличе x.__getattr__('color')
; він просто поверне вже задане значення x.color
.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Задати значення атрибута | x.my_attr = value
|
x.__setattr__('my_attr', value)
|
Метод __setattr__
викликається щоразу як ви пробуєте задати значення атрибута.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Видалити атрибут | del x.my_attr
|
x.__delattr__('my_attr')
|
Метод __delattr__()
викликається щоразу як ви видаляєте атрибут.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
Перелічити всі атрибути та методи | dir(x)
|
x.__dir__()
|
Метод __dir__()
корисний коли ви описуєте методи __getattr__()
чи __getattribute__()
. Зазвичай, виклик dir(x)
дає перелік лише звичайних атрибутів та методів. Якщо ваш метод __getattr__()
працює з атрибутом color
динамічно, dir(x)
не перелічуватиме color
серед доступних атрибутів. Перезадання методу __dir__()
дозволяє вам додати color
до списку доступних атрибутів, що буде корисно для інших людей, які схочуть користуватись вашим класом не вникаючи в його нутрощі.
Різниця між методами __getattr__()
та __getattribute__()
тонка але важлива. Я можу пояснити її двома прикладами:
class Dynamo:
def __getattr__(self, key):
if key == 'color':
return 'PapayaWhip'
else:
raise AttributeError
Ім’я атрибуту передається в метод __getattr__()
як рядок. Якщо ім’я - 'color'
, метод повертає значення. (В даному випадку це жорстко заданий рядок, але ви зазвичайн зробите певні обчислення та повернете результат.)
Якщо ім’я атрибуту невідоме, метод __getattr__()
повинен згенерувати виняток AttributeError
, інакше ваш код працюватиме неправильно. Технічно, якщо метод не генерує виняток, чи явно повертає якесь значення, він повертає None
. Це означає що всі атрибути які не задані явно матимуть значення None
, а це майже напевне не те що вам потрібно.
>>> dyn = Dynamo()
>>> dyn.color
'PapayaWhip'
Екземпляр dyn
не має атрибуту з назвою color
, тому викликається метод __getattr__()
який обчислює необхідне значення атрибуту.
>>> dyn.color = 'LemonChiffon'
>>> dyn.color
'LemonChiffon'
Після явного задання dyn.color
, метод __getattr__()
вже не викликається щоб надати значення dyn.color
, тому що dyn.color
вже заданий в екземплярі.
А метод __getattribute__()
- навпаки, абсолютний та безумовний.
class SuperDynamo:
def __getattribute__(self, key):
if key == 'color':
return 'PapayaWhip'
else:
raise AttributeError
>>> dyn = SuperDynamo()
>>> dyn.color
'PapayaWhip'
Метод __getattribute__()
викликається щоб повернути значення dyn.color
.
>>> dyn.color = 'LemonChiffon'
>>> dyn.color
'PapayaWhip'
І навіть після того як ми явно задали значення dyn.color
, метод __getattribute__()
все ще викликається щоб надати значення для dyn.color
. Якщо присутній, метод __getattribute__()
викликається безумовно для кожного атрибута, навіть якщо ми явно задали його після створення екземпляра.
☞ Якщо ваш клас описує метод
__getattribute__()
, вам напевне варто також описати метод__setattr__()
, та скоординувати їх роботу так аби стежити за значеннями атрибутів. Інакше, будь-які атрибути які ви задаєте після створення екземпляру пропадуть в чорній дірі.
Потрібно бути особливо обережним з методом __getattribute__()
, тому що він також викликається коли Python шукає методи вашого класу.
class Rastan:
def __getattribute__(self, key):
raise AttributeError
def swim(self):
pass
Цей клас описує метод __getattribute__()
, який завжди генерує виняток AttributeError
. Жоден пошук атрибуту чи методу не буде успішним.
>>> hero = Rastan()
>>> hero.swim()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __getattribute__
AttributeError
Коли ви викликаєте hero.swim()
, Python шукає метод swim()
в класі Rastan. Цей пошук йде крізь метод __getattribute__()
, тому що всі атрибути та методи шукаються з його допомогою. В нашому випадку __getattribute__()
генерує виняток AttributeError
тому пошук метода не вдається, і його виклик відповідно теж.
Класи що поводяться як функції
ред.Можна зробити так щоб екземпляр класу можна було викликати як функцію - описавши метод __call__
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
викликати об’єкт як функцію | my_instance()
|
my_instance.__call__()
|
Модуль zipfile
використовує це щоб описати клас що може розшифрувати зашифрований zip-файл переданим йому паролем. Алгоритм шифрування в файлах zip потребує зберігати стан протягом розшифрування. Опис розшифровщика як класу дозволяє зберігати стан в екземплярах цього класу. Стан ініціалізується в методі __init__()
і змінюється протягом розшифровки. Але так як такий клас можна викликати як функцію, можна передавати його екземпляр першим аргументом в функцію map()
, наприклад так як в наступному прикладі:
# шматочок файла zipfile.py
class _ZipDecrypter:
.
.
.
def __init__(self, pwd):
self.key0 = 305419896
self.key1 = 591751049
self.key2 = 878082192
for p in pwd:
self._UpdateKeys(p)
Клас _ZipDecryptor
зберігає стан в формі трьох ключів, які пізніше оновлюються в методі _UpdateKeys()
(тут не показаний).
def __call__(self, c):
assert isinstance(c, int)
k = self.key2 | 2
c = c ^ (((k * (k^1)) >> 8) & 255)
self._UpdateKeys(c)
return c
Клас описує метод __call__()
, який дозволяє викликати екземпляри класу як функції. В нашому випадку метод __call__()
розшифровує один байт zip-файлу, після чого оновлює ключі способом що залежить від розшифрованого байта.
zd = _ZipDecrypter(pwd)
bytes = zef_file.read(12)
zd
- екземпляр класу _ZipDecryptor
. Змінна pwd
передається в метод __init__()
, де вона зберігається та використовується для першого оновлення ключів.
h = list(map(zd, bytes[0:12]))
Маючи перших 12 байт zip-файла, ми розшифровуємо їх map-лячи байти на zd
, що дає ефект дванадцятиразового викликання zd
, що викликає метод __call__()
12 разів, що оновлює внутрішній стан та повертає результуючий байт 12 разів.
Класи що поводяться як множини
ред.Якщо ваш клас працює як контейнер для множини значень, тобто, якщо є сенс перевіряти чи "містить" клас значення, тоді напевне варто описувати наступні спеціальні методи і змусити їх працювати як множину.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
дізнатись кількість елементів | len(s)
|
s.__len__()
|
знати чи містить клас певне значення | x in s
|
s.__contains__(x)
|
Модуль cgi
використовує ці методи в класі FieldStorage
, який представляє всі поля форми, чи параметри запиту які були відправлені динамічній веб-сторінці.
# Скрипт який відповідає на запит http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:
do_search()
Як тільки ви створите екземпляр класу cgi.FieldStorage
, ви можете використати оператор in
щоб перевірити чи певний параметр був переданий в рядку запиту.
# Витяг з cgi.py який пояснює як це працює
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)
Метод __contains__()
- це магія яка приводить все це в дію. Коли ви пишете if 'q' in fs
, Python шукає в об’єкті fs
метод __contains__()
, який описаний в cgi.py
. Значення 'q'
передається в цей метод як аргумент key
.
А функція any()
, приймає генераторний вираз та повертає True
якщо генератор повертає хоча б одне істинне значення. Вона досить розумна щоб одразу після цього зупинитись.
def __len__(self):
return len(self.keys())
Цей же клас FieldStorage
також підтримує повернення своєї довжини, тому ви можете написати len(fs)
, що викличе метод __len__()
для FieldStorage
і поверне кількість параметрів запиту які були знайдені. Метод self.keys()
перевіряє чи self.list is None
, тому метод __len__
не повинен проводити цю перевірку на помилки повторно.
Класи що поводяться як словники
ред.Трішки розширюючи інформацію попереднього параграфа, ви можете описувати класи що відповідають не тільки на оператор in
та функцію len()
, а й працюють як повноцінні словники, повертаючи значення за ключами.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
отримати значення за ключем | x[key]
|
x.__getitem__(key)
|
записати значення за ключем | x[key] = value
|
x.__setitem__(key, value)
|
видалити значення за ключем | del x[key]
|
x.__delitem__(key)
|
надати значення за замовчуванням для неіснуючих ключів | x[nonexistent_key]
|
x.__missing__(nonexistent_key)
|
Клас FieldStorage
з модуля cgi
також описує ці спеціальні методи, що означає, що ви можете робити речі на зразок наступних:
# Скрипт який відповідає на запит http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:
do_search(fs['q'])
Об’єкт fs
є екземпляром cgi.FieldStorage
, але ви все одно можете отримати значення виразу на зразок fs['q']
.
# Шматок з cgi.py який показує як це працює
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
fs['q']
задіює метод __getitem__()
передаючи йому 'q'
в параметр key
. Після чого він шукає у власному списку параметрів запиту (self.list
) елемент, .name
якого відповідає даному ключу.
Класи що поводяться як числа
ред.Використовуючи відповідні спеціальні методи, ви можете описати свої власні класи що поводяться як числа. Тобто, ви можете додавати їх, віднімати та виконувати інші математичні операції. Таким чином реалізовані дроби - клас Fraction
містить реалізацію цих методів, і тому ми можемо робити речі на зразок таких:
>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> x / 3
Fraction(1, 9)
Далі вичерпний список спеціальних методів які потрібні щоб створити числоподібний клас.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
додавання | x + y
|
x.__add__(y)
|
віднімання | x - y
|
x.__sub__(y)
|
множення | x * y
|
x.__mul__(y)
|
ділення | x / y
|
x.__truediv__(y)
|
цілочисельне ділення | x // y
|
x.__floordiv__(y)
|
остачу від ділення | x % y
|
x.__mod__(y)
|
цілочисельне ділення з остачею | divmod(x, y)
|
x.__divmod__(y)
|
піднесення до степеня | x ** y
|
x.__pow__(y)
|
побітовий зсув вліво | x << y
|
x.__lshift__(y)
|
побітовий зсув право | x >> y
|
x.__rshift__(y)
|
побітове "і" | x & y
|
x.__and__(y)
|
побітове виключне "або" | x ^ y
|
x.__xor__(y)
|
побітове або | x | y
|
x.__or__(y)
|
Це все чудово і добре, якщо x
- екземпляр класу що реалізує ці методи. Але що якщо він не реалізує жодного? Чи гірше, що якщо він їх реалізує, але не може обробляти певні види аргументів? Наприклад:
>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> 1 / x
Fraction(3, 1)
Це як в попередньому прикладі, коли ми брали Fraction
та ділили його на ціле. Цей випадок був прямолінійним, x / 3
викликає x.__truediv__(3)
, а метод __truediv__()
класу Fraction
обробляє всю математику. Але цілі не знають як робити арифметичні операції з дробами. То чому цей приклад працює?
Існує інший набір спеціальних арифметичних методів, з оберненими операндами. Маючи арифметичну операцію яка бере два операнди (наприклад x / y
), існує два способи її здійснити:
- Попросити
x
поділити себе наy
, або - Попросити
y
поділити на себеx
Множина спеціальних методів вище обирає перший підхід: маючи x / y
, вони описують те як x
може поділити себе на y
. Наступний набір спеціальних методів обирає інший підхід: вони надають спосіб за допомогою якого y
може сказати "Я знаю як бути знаменником і поділити на себе x
".
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
додавання | x + y
|
y.__radd__(x)
|
віднімання | x - y
|
y.__rsub__(x)
|
множення | x * y
|
y.__rmul__(x)
|
ділення | x / y
|
y.__rtruediv__(x)
|
цілочисельне ділення | x // y
|
y.__rfloordiv__(x)
|
остачу від ділення | x % y
|
y.__rmod__(x)
|
цілочисельне ділення з остачею | divmod(x, y)
|
y.__rdivmod__(x)
|
піднесення до степеня | x ** y
|
y.__rpow__(x)
|
побітовий зсув вліво | x << y
|
y.__rlshift__(x)
|
побітовий зсув право | x >> y
|
y.__rrshift__(x)
|
побітове "і" | x & y
|
y.__rand__(x)
|
побітове виключне "або" | x ^ y
|
y.__rxor__(x)
|
побітове або | x | y
|
y.__ror__(x)
|
Але зачекайте, є ще! Якщо ви використовуєте оператори з присвоєнням ("in-place operator" - оператор на місці), такі як x /= 3
, існує ще більше спеціальних методів які ви можете описати.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
додавання з присвоєнням | x += y
|
x.__iadd__(y)
|
віднімання з присвоєнням | x -= y
|
x.__isub__(y)
|
множення з присвоєнням | x *= y
|
x.__imul__(y)
|
ділення з присвоєнням | x /= y
|
x.__itruediv__(y)
|
цілочисельне ділення з присвоєнням | x //= y
|
x.__ifloordiv__(y)
|
остача від ділення з присвоєнням | x %= y
|
x.__imod__(y)
|
піднесення до степеня з присвоєнням | x **= y
|
x.__ipow__(y)
|
побітовий зсув вліво з присвоєнням | x <<= y
|
x.__ilshift__(y)
|
побітовий зсув право з присвоєнням | x >>= y
|
x.__irshift__(y)
|
побітове "і" з присвоєнням | x &= y
|
x.__iand__(y)
|
побітове виключне "або" з присвоєнням | x ^= y
|
x.__ixor__(y)
|
побітове або з присвоєнням | x |= y
|
x.__ior__(y)
|
Зауваження: у більшості випадків перевантажувати оператори з присвоєнням не є необхідним. Якщо ви не задасте метод для певної операції з присвоєнням, Python спробує інші методи. Наприклад, щоб виконати вираз x /= y
, Python спробує:
- Спробувати викликати
x.__itruediv__(y)
. Якщо цей метод описаний та повертає будь-яке значення окрімNotImplemented
, то на цьому все. - Спробувати викликати
x.__truediv__(y)
. Якщо цей метод описаний та повертає будь-яке значення окрімNotImplemented
, то старе значенняx
відкидається та замінюється повернутим значенням, так ніби ми написалиx = x / y
. - Спробувати викликати
y.__rtruediv__(x)
. Якщо цей метод описаний та повертає будь-яке значення окрімNotImplemented
, то старе значенняx
відкидається та замінюється повернутим значенням.
Тому ви справді потребуєте задавати методи для операторів з присвоєнням на зразок __itruediv__()
, якщо ви хочете зробити спеціальні оптимізації для таких операторів. Інакше Python переформулює ці оператори в звичайні з наступним присвоєнням.
Існує також кілька "унарних" математичних операцій, які можна виконувати на числоподібних об’єктах.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
від’ємне число | -x
|
x.__neg__()
|
додатнє число | +x
|
x.__pos__()
|
побітову інверсію ("не") | ~x
|
x.__invert__()
|
комплексне число | complex(x)
|
x.__complex__()
|
ціле число | int(x)
|
x.__int__()
|
число з плаваючою крапкою | float(x)
|
x.__float__()
|
число округлене до найближчого цілого | round(x)
|
x.__round__()
|
число округлене до найближчого значення з n значущими цифрами |
round(x, n)
|
x.__round__(n)
|
найменше ціле >= x | math.ceil(x)
|
x.__ceil__()
|
найбільше ціле <= x | math.floor(x)
|
x.__round__()
|
обрізати x до найближчого цілого в сторону 0 | math.trunc(x)
|
x.__trunc__()
|
використати як індекс списку (PEP 357) | a_list[x]
|
a_list[x.__index__()]
|
Класи які можна порівнювати
ред.Я виокремив цей параграф від попереднього, тому що порівняння не є строгою компетенцією чисел. Багато типів даних можна порівнювати - рядки, списки, навіть словники. Якщо ви створюєте власний клас і є сенс в порівнянні ваших об’єктів з іншими, ви можете використати наступні спеціальні методи для реалізації порівнянь.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
рівність | x == y
|
x.__eq__(y)
|
нерівність | x != y
|
x.__ne__(y)
|
менше | x < y
|
x.__lt__(y)
|
менше рівно | x <= y
|
x.__le__(y)
|
більше | x > y
|
x.__gt__(y)
|
більше рівно | x >= y
|
x.__ge__(y)
|
значення істинності в булевому контексті | if x:
|
x.__bool__()
|
☞ Якщо ви опишете метод
__lt__()
але не__gt__()
, Python використає метод__lt__()
з переставленими аргументами. Щоправда, Python не комбінуватиме методи. Наприклад, якщо ви опишете методи__lt__()
та__eq__()
та спробуєте перевірити чиx <= y
, Python не викликатиме__lt__()
та__eq__()
один за одним. Він лише викличе метод__le__()
.
Класи які можна серіалізувати
ред.Python підтримує серіалізацію та розсеріалізацію довільних об’єктів. (Також цей процес називають pickling та unpickling відповідно). Це може бути корисним для зберігання стану в файл, та відновлення його пізніше. Всі cтандартні типи даних вже підтримують серіалізацію. Якщо ви створюєте власний клас, та хочете його серіалізувати, прочитайте про протокол pickle щоб побачити коли і як викликаються наступні спеціальні методи.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
копію власного об’єкта | copy.copy(x)
|
x.__copy__()
|
"глибоку" копію власного об’єкта | copy.deepcopy(x)
|
x.__deepcopy__()
|
отримати стан об’єкта перед серіалізацією* | pickle.dump(x, file)
|
x.__getstate__()
|
серіалізувати об’єкт* | pickle.dump(x, file)
|
x.__reduce__()
|
серіалізувати об’єкт (новий протокол pickle)* | pickle.dump(x, file, protocol_version)
|
x.__reduce_ex__(protocol_version)
|
контролювати створення об’єкта під час розсеріалізації* | x = pickle.load(file)
|
x.__getnewargs__()
|
відновити стан об’єкта після розсеріалізації* | x = pickle.load(file)
|
x.__setstate__()
|
- * Щоб відтворити серіалізований об’єкт, Python повинен створити новий об’єкт який виглядає як серіалізований, після чого встановити значення всіх атрибутів нового об’єкта. Метод
__getnewargs__()
контролює процес створення об’єкта, а метод__setstate__()
- процес відновлення значень атрибутів.
Класи що можуть використовуватись в блоці with
ред.
Блок with
утворює контекст виконання; ви входите в контекст при виконанні оператора with
і виходите з нього після виконання останнього оператора з блоку.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
зробити певну дію при вході в блок with
|
with x:
|
x.__enter__()
|
зробити певну дію при виході з блоку with
|
with x:
|
x.__exit__(exc_type, exc_value, traceback)
|
Ось як це працює з файлами:
# витяг з 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
Файловий об’єкт описує як метод __enter__()
так і метод __exit__()
. Метод __enter__()
перевіряє чи файл є відкритим, і якщо ні, метод _checkClosed()
кидає виняток.
Метод __enter__()
завжди повинен повертати self
- це об’єкт який блок with
використовує для доступу до атрибутів об’єкта.
def __exit__(self, *args):
'''Context management protocol. Calls close()'''
self.close()
Після виходу з блоку with
файловий об’єкт автоматично закривається, завдяки коду в методі __exit__()
.
☞ Метод
__exit__()
виконується завжди, навіть якщо всередині блокуwith
трапився виняток. Інформація про цей виняток передається в параметри__exit__()
. Щоб дізнатись деталі дивіться документацію.
Щоб дізнатись більше про керування контекстом читайте розділи Автоматичне закривання файлів та Перенаправлення стандартного потоку виводу
Зовсім езотеричні штуки
ред.Якщо ви знаєте що робите, ви можете отримати майже повний контроль над тим як класи порівнюються, як визначаються їх атрибути, та які види класів вважаються підкласами вашого.
Ви хочете... | Тому ви пишете... | І Python виконує... |
---|---|---|
конструктор класу | x = MyClass()
|
x.__new__()
|
деструктор класу* | del x
|
x.__del__()
|
задати певну множину атрибутів | x.__slots__()
| |
задати власне значення хешу | hash(x)
|
x.__hash__()
|
отримати значення властивості | x.color
|
type(x).__dict__['color'].__get__(x, type(x))
|
встановити значення властивості | x.color = 'PapayaWhip'
|
type(x).__dict__['color'].__set__(x, 'PapayaWhip')
|
видалити властивість | del x.color
|
type(x).__dict__['color'].__del__(x)
|
встановити чи є об’єкт екземпляром класу | isinstance(x, MyClass)
|
MyClass.__instancecheck__(x)
|
встановити чи є клас підкласом класу | issubclass(C, MyClass)
|
MyClass.__subclasscheck__(C)
|
встановити чи є клас підкласом абстрактного базового класу | issubclass(C, MyABC)
|
MyABC.__subclasshook__(C)
|
- * Точний момент коли Python викликає метод
__del__
надзвичайно складно визначити. Щоб повністю це зрозуміти, потрібно знати як Python зберігає об’єкти в пам’яті. Ось гарна стаття про збирання сміття в Python та деструктори класів. Також ви повинні прочитати про слабкі посилання, модульweakref
та, для певності, модульgc
.
Для подальшого читання
ред.Модулі згадані в цьому додатку:
- модуль
zipfile
- модуль
cgi
- модуль
collections
- модуль
math
- модуль
pickle
- модуль
copy
- модуль
abc
(Abstract Base Classes)
Інше неважке читання:
- Мінімова задання формату
- модель даних Python
- Вбудовані типи
- PEP 357: Дозвіл кожному об’єкту бути використаним для зрізів
- PEP 3119: Абстрактні Базові Класи