# Вредно:
some_lock.acquire()
try:
# Сделать что-то...
finally:
some_lock.release()
# Лучше:
with some_lock:
# Сделать что-то...
В обоих случаях использование инструкции
with
позволяет абстрагиро-
ваться от большей части логики обработки ресурса. Вместо необходимо-
сти всякий раз писать явную инструкцию
try…finally
, инструкция
with
выполняет эту работу за нас.
Инструкция
with
может сделать программный код, который работает
с системными ресурсами, более удобочитаемым. Она также помогает из-
40 Глава 2 • Шаблоны для чистого Python
бежать ошибок или утечек, делая практически невозможными ситуации,
когда по разным причинам забывают выполнить очистку или высвобож-
дение ресурсов после того, как они стали больше ненужными.
Поддержка инструкции with в собственных объектах
Нужно сказать, что в функции
open()
или классе
threading.Lock
нет
ничего особенного или чудесного, равно как и в том, что они могут при-
меняться вместе с инструкцией
with
. Ту же самую функциональность
можно обеспечить в собственных классах и функциях путем реализации
так называемых менеджеров контекста (context managers)
1
.
Что такое менеджер контекста? Это простой «протокол» (или интерфейс),
который ваш объект должен соблюдать для того, чтобы поддерживать
инструкцию
with
. В сущности, если вы хотите, чтобы объект функциони-
ровал как менеджер контекста, от вас требуется только одно — добавить
в него методы
__enter__
и
__exit__
. Python будет вызывать эти два метода
в соответствующих случаях в цикле управления ресурсом.
Давайте посмотрим, как это выглядит на практике. Вот пример простой
реализации контекстного менеджера
open()
:
class ManagedFile:
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
Наш класс
ManagedFile
подчиняется протоколу менеджера контекста и те-
перь поддерживает инструкцию
with
точно так же, как и первоначальный
пример с функцией
open()
:
1
См. документацию Python «Менеджеры контекста инструкции with»:
https://docs .python .
org/3/reference/datamodel .html#with-statement-context-managers
2 .3 . Менеджеры контекста и инструкция with 41
>>> with ManagedFile('hello.txt') as f:
... f.write('привет, мир!')
... f.write('а теперь, пока!')
Python вызывает
__enter__
, когда поток исполнения входит в контекст
инструкции
with
и наступает момент получения ресурса. Когда поток
исполнения снова покидает контекст, Python вызывает
__exit__
, чтобы
высвободить этот ресурс.
Написание менеджера контекста на основе класса не является единствен-
ным способом поддержки инструкции
with
в Python. Служебный модуль
contextlib
1
стандартной библиотеки обеспечивает еще несколько аб-
стракций, надстроенных поверх базового протокола менеджера контекста.
Он может слегка облегчить вашу жизнь, если ваши варианты применения
совпадают с тем, что предлагается модулем
contextlib
.
Например, вы можете применить декоратор
contextlib.contextmanager
,
чтобы определить для ресурса фабричную функцию на основе генератора,
которая затем будет автоматически поддерживать инструкцию
with
. Вот
как выглядит пример нашего контекстного менеджера
ManagedFile
, пере-
писанный в соответствии с этим приемом:
from contextlib import contextmanager
@contextmanager
def managed_file(name):
try:
f = open(name, 'w')
yield f
finally:
f.close()
>>> with managed_file('hello.txt') as f:
... f.write('привет, мир!')
... f.write('а теперь, пока!')
В данном случае
managed_file()
является генератором, который сначала
получает ресурс. После этого он временно приостанавливает собственное
1
См. документацию Python «contextlib»:
https://docs .python .org/3/library/contextlib .html
42 Глава 2 • Шаблоны для чистого Python
исполнение и передает ресурс инструкцией
yield
, чтобы его использовал
источник вызова. Когда источник вызова покидает контекст
with
, гене-
ратор продолжает выполняться до тех пор, пока не произойдут любые
оставшиеся шаги очистки, после чего ресурс будет высвобожден и воз-
вращен системе.
Реализации на основе класса и на основе генератора по своей сути экви-
валентны. Вы можете предпочесть тот или иной вариант в зависимости
от того, какой подход вы считаете более удобочитаемым.
Оборотной стороной реализации на основе
@contextmanager
может яв-
ляться то, что такая реализация требует некоторого вникания в продви-
нутые понятия языка Python, такие как декораторы и генераторы. Если
чувствуете, что вам необходимо в них разобраться, то не стесняйтесь по-
менять маршрут и перейти к соответствующим главам книги.
Повторю еще раз: правильный выбор реализации здесь сводится к тому,
с какой из них вы и ваша команда чувствуете себя комфортно и какую из
них вы считаете наиболее удобочитаемой.
Написание красивых API с менеджерами контекста
Менеджеры контекста обладают достаточной гибкостью, и если к при-
менению инструкции
with
подойти творчески, то для своих модулей
и классов вы сможете определять удобные API.
Например, что, если «ресурсом», которым мы хотели бы управлять, яв-
ляются уровни отступа текста в некоей программе — генераторе отчетов?
Что, если бы для этого мы смогли написать исходный код, который вы-
глядит вот так:
with Indenter() as indent:
indent.print('привет!')
with indent:
indent.print('здорово')
with indent:
indent.print('бонжур')
indent.print('эй')
2 .3 . Менеджеры контекста и инструкция with 43
Он читается почти как предметно-ориентированный язык (DSL) для
расстановки отступов. Кроме того, обратите внимание, как этот код не-
сколько раз входит в тот же самый менеджер контекста и покидает, чтобы
изменить уровни отступа. Выполнение этого фрагмента кода должно при-
вести к указанному ниже результату и распечатке в консоли аккуратно
отформатированного текста:
привет!
здорово
бонжур
эй
Итак, каким образом вы реализовали бы менеджер контекста, который
поддерживал бы эту функциональность?
Между прочим, для вас это может быть прекрасным упражнением, чтобы
понять, как именно работают менеджеры контекста. Поэтому перед тем,
как вы обратитесь к моей реализации, приведенной ниже, возьмите паузу
и попробуйте реализовать это самостоятельно в качестве задания.
Если вы готовы взглянуть на мою реализацию, то ниже показано, как
можно воплотить эту функциональность, использовав менеджер контек-
ста на основе класса:
class Indenter:
def __init__(self):
self.level = 0
def __enter__(self):
self.level += 1
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.level -= 1
def print(self, text):
print(' ' * self.level + text)
Неплохо, правда? Надеюсь, что сейчас вы чувствуете себя увереннее при
использовании менеджеров контекста Python и инструкции
with
в соб-
44 Глава 2 • Шаблоны для чистого Python
ственных программах. Это превосходное функциональное средство языка,
позволяющее решать задачи по управлению ресурсами в гораздо более
питоновском и удобном в сопровождении стиле.
Если вы ищете другое упражнение, чтобы понять тему глубже, то попро-
буйте реализовать менеджер контекста, измеряющий время исполнения
блока программного кода с использованием функции
time.time
. Обяза-
тельно напишите его в двух вариантах: на основе декоратора и на основе
класса, чтобы усвоить разницу между ними.
Ключевые выводы
Инструкция
with
упрощает обработку исключений путем инкапсуля-
ции стандартных случаев применения инструкций
try/finally
в так
называемые менеджеры контекста.
Чаще всего менеджер контекста используется для управления без-
опасным получением и высвобождением системных ресурсов. Ресурсы
выделяются при помощи инструкции
with
и высвобождаются автома-
тически, когда поток исполнения покидает контекст
with
.
Эффективное применение инструкции
with
помогает избежать утечки
ресурсов и облегчает ее восприятие.
2 .4 . Подчеркивания, дандеры и другое
У символов одинарного и двойного подчеркивания в Python есть осо-
бый смысл в именах переменных и методов. Отчасти этот смысл суще-
ствует исключительно по договоренности и предназначен в качестве
подсказки программисту — и частично он обеспечивается интерпрета-
тором Python.
Если вам интересно, каков смысл символов одинарного и двойного под-
черкивания в именах переменных и методов, то здесь я приложу все уси-
лия, чтобы ответить на ваш вопрос. В этом разделе мы обсудим следую-
2 .4 . Подчеркивания, дандеры и другое 45
щие ниже шаблоны подчеркивания и согласованные правила именования
и то, как они влияют на поведение ваших программ Python:
Одинарный начальный символ подчеркивания:
_var
.
Одинарный замыкающий символ подчркивания:
var_
.
Двойной начальный символ подчеркивания:
__var
.
Двойной начальный и замыкающий символ подчеркивания:
__var__
.
Одинарный символ подчеркивания:
_
.
1 . Одинарный начальный символ подчеркивания: _var
В том, что касается имен переменных и методов, префикс, состоящий из
одинарного символа подчеркивания, имеет свой смысл только по дого-
воренности и представляет собой подсказку программисту — он означает
то, что он должен означать по общему согласию сообщества Python, но на
поведение ваших программ он не влияет.
Префикс, состоящий из символа подчеркивания, подразумевается как
Do'stlaringiz bilan baham: |