0

Значение оператора "with" без ключевого слова "as"

1

Я знаком с использованием оператора 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 ответ(ов)

0

Контекстный менеджер может по желанию возвращать объект, который будет присвоен идентификатору, указанному в 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, но поскольку у вас уже есть полностью работоспособная ссылка, зачем создавать новую локальную переменную для этого?

0

В вышеуказанном ответе все очень хорошо изложено.

Единственное, что я постоянно спрашивал себя во время чтения, так это где подтверждение следующего сценария. Если в теле контекста оператора with есть присваивание, то все, что находится справа от присваивания, сначала "привязывается" к контексту. Так, в следующем примере:

with db_connection():
   result = select(...)

... select фактически эквивалентен ref_to_connection.select(...).

Я решил оставить это здесь для тех, кто, как и я, периодически переходит между языками программирования и может получить пользу от быстрого напоминания о том, как читать и отслеживать ссылки в данном контексте.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь