Значение оператора "with" без ключевого слова "as"
Я знаком с использованием оператора with
в Python как средством обеспечения завершения работы объекта в случае возникновения исключения. Обычно это выглядит так:
with file.open('myfile.txt') as f:
делаем что-то...
что является сокращенной формой следующего кода:
f = file.open('myfile.txt')
try:
делаем что-то...
finally:
f.close()
или любой другой процедуры завершения работы, которую может предложить класс.
Недавно я наткнулся на фрагмент кода, связанный с OpenGL, который выглядит следующим образом:
with self.shader:
(Много команд OpenGL)
Обратите внимание на отсутствие ключевого слова as
. Указывает ли это на то, что методы __enter__
и __exit__
класса все еще будут вызываться, но объект никогда явно не используется в блоке (т.е. работа происходит через глобальные или неявные ссылки)? Или есть какое-то другое значение, которое ускользает от меня?
2 ответ(ов)
Контекстный менеджер может по желанию возвращать объект, который будет присвоен идентификатору, указанному в as
. И именно возвращаемый объект методом __enter__
присваивается этому идентификатору, а не обязательно сам контекстный менеджер.
Использование конструкции as <идентификатор>
полезно, когда вы создаете новый объект, как это делает вызов open()
, но не все контекстные менеджеры создаются только для контекста. Они могут быть переиспользуемыми и уже существовать, например.
Рассмотрим подключение к базе данных. Вы создаете соединение с базой данных один раз, но многие адаптеры базы данных позволяют использовать соединение как контекстный менеджер: входя в контекст, начинается транзакция, а выходя из него, транзакция либо фиксируется (при успехе), либо откатывается (при возникновении исключения):
with db_connection:
# выполняем действия с базой данных
Здесь не нужно создавать новые объекты; контекст вступает с помощью db_connection.__enter__()
и выходит из него с помощью db_connection.__exit__()
, но у нас уже есть ссылка на объект соединения.
Теперь может случиться так, что объект соединения возвращает объект курсора, когда вы входите в контекст. В этом случае имеет смысл присвоить этот объект курсора как локальную переменную:
with db_connection as cursor:
# используем курсор для внесения изменений в базу данных
db_connection
здесь снова не был вызван, он уже существовал ранее, и у нас уже есть ссылка на него. Но что бы ни вернул db_connection.__enter__()
, теперь это будет присвоено cursor
, и его можно использовать далее.
То же самое происходит и с файловыми объектами: open()
возвращает файловый объект, и fileobject.__enter__()
возвращает сам файловый объект, поэтому вы можете использовать вызов open()
в операторе with
и присвоить ссылку на вновь созданный объект за один шаг, а не за два. Без этой небольшой хитрости вам пришлось бы делать:
f = open('myfile.txt')
with f:
# используем `f` в блоке
Применяя все это к вашему примеру шейдера: у вас уже есть ссылка на self.shader
. Очень вероятно, что self.shader.__enter__()
снова возвращает ссылку на self.shader
, но поскольку у вас уже есть полностью работоспособная ссылка, зачем создавать новую локальную переменную для этого?
В вышеуказанном ответе все очень хорошо изложено.
Единственное, что я постоянно спрашивал себя во время чтения, так это где подтверждение следующего сценария. Если в теле контекста оператора with
есть присваивание, то все, что находится справа от присваивания, сначала "привязывается" к контексту. Так, в следующем примере:
with db_connection():
result = select(...)
... select
фактически эквивалентен ref_to_connection.select(...)
.
Я решил оставить это здесь для тех, кто, как и я, периодически переходит между языками программирования и может получить пользу от быстрого напоминания о том, как читать и отслеживать ссылки в данном контексте.
Объяснение '__enter__' и '__exit__' в Python
Несколько переменных в операторе 'with'?
Что делает yield без значения в контекстном менеджере?
Как клонировать список, чтобы он не изменялся неожиданно после присваивания?
Ошибка: "'dict' объект не имеет метода 'iteritems'"