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

Я знаком с использованием оператора Python with как средства обеспечения завершения объекта в случае возникновения исключения. Обычно это выглядит как

with file.open('myfile.txt') as f:
    do stuff...

что является сокращением для

f = file.open('myfile.txt'):
try:
    do stuff...
finally:
    f.close()

или любую другую процедуру завершения, которую может представить класс.

Недавно я наткнулся на фрагмент кода, относящийся к OpenGL, который представил это:

with self.shader:
    (Many OpenGL commands)

Обратите внимание на отсутствие какого-либо ключевого слова as. Означает ли это, что методы __enter__ и __exit__ класса все еще вызываются, но объект никогда не используется в блоке явно (т. е. он работает через глобальные переменные или неявные ссылки)? Или есть какой-то другой смысл, который ускользает от меня?


person KDN    schedule 13.10.2014    source источник
comment
Если вам не нужно использовать псевдоним диспетчера контекста внутри блока with, это нормально — см. также, например. contextlib.suppress. Строго говоря, вы могли бы сделать with open(...): ..., хотя, поскольку тогда вы не можете получить доступ к обработчику файлов, в этом нет особого смысла!   -  person jonrsharpe    schedule 13.10.2014


Ответы (2)


Менеджер контекста может необязательно возвращать объект, который будет назначен идентификатору, названному as. И именно объект, возвращаемый методом __enter__, назначается as, а не обязательно самим менеджером контекста.

Использование as <identifier> помогает при создании нового объекта, как это делает вызов open(), но не все менеджеры контекста создаются только для контекста. Например, они могут быть многоразовыми и уже созданы.

Воспользуйтесь подключением к базе данных. Вы создаете соединение с базой данных только один раз, но многие адаптеры баз данных позволяют использовать это соединение в качестве менеджера контекста; войдите в контекст, и транзакция запустится, выйдите из него, и транзакция либо будет зафиксирована (в случае успеха), либо откатится (при возникновении исключения):

with db_connection:
    # do something to the database

Здесь не нужно создавать новые объекты, вход в контекст осуществляется с помощью db_connection.__enter__(), а выход из него — с помощью db_connection.__exit__(), но у нас уже есть ссылка на объект подключения.

Теперь может быть так, что объект подключения создает объект курсора при вводе. Теперь имеет смысл присвоить этому объекту курсора локальное имя:

with db_connection as cursor:
    # use cursor to make changes to the database

db_connection еще не вызывался здесь, он уже существовал раньше, и у нас уже есть ссылка на него. Но все, что произвел db_connection.__enter__(), теперь назначается cursor и с этого момента может использоваться.

Это то, что происходит с файловыми объектами; open() возвращает файловый объект, а fileobject.__enter__() возвращает файловый объект сам, поэтому вы можете использовать вызов open() в операторе with и назначить ссылку на вновь созданный объект в одном шаг, а не два. Без этого маленького трюка вам пришлось бы использовать:

f = open('myfile.txt')
with f:
    # use `f` in the block

Применив все это к вашему примеру шейдера; у вас уже есть ссылка на self.shader. Вполне вероятно, что self.shader.__enter__() снова возвращает ссылку на self.shader, но поскольку у вас уже есть вполне пригодная ссылка, зачем создавать для нее новый локальный объект?

person Martijn Pieters    schedule 13.10.2014

Приведенный выше ответ хорошо поставлен.

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

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

... select is ~ ref_to_connection.select(...)

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

person Edmund's Echo    schedule 27.05.2021