Различное поведение для возврата и выхода при вводе инструкции «with»

#python

#python

Вопрос:

Когда я создаю класс, который поддерживает with область видимости, я получаю различное и необъяснимое поведение, когда я использую yield и return в качестве последних вызовов в __enter__ методе, code:

 class TestWith:
    def __init__(self, val):
        self.val=val

    def __enter__(self):
        print("Entered!")
        yield
        # return

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"In exit:, type: {exc_type}, val: {exc_val}, tb: {exc_tb}")


with TestWith(4):
    print("printme")
  

Вывод:

 printme
In exit:, type: None, val: None, tb: None
  

т. е. «Введено!» не печатается, изменяя __enter__ метод на:

     def __enter__(self):
        print("Entered!")
        # yield
        return
  

приводит к:

 Entered!
printme
In exit:, type: None, val: None, tb: None
  

Почему существует различное поведение для этих двух реализаций? также я бы предположил, что «Введено!» будет напечатано независимо от того, что, поскольку оно вызывается перед return / yield

Комментарии:

1. Ну, yield и return делают разные вещи, так почему вы думаете, что они оба должны работать здесь?

2. @KonradRudolph они такие. Как они влияют на вызов функции, происходящий до их фактического вызова?

3. Потому что return и yield не являются вызовами функций . Это инструкции потока управления, что означает, что они управляют тем, как ведет себя вся функция . В конкретном случае yield его использование приводит к тому, что функция имеет совершенно другую семантику, включая другое возвращаемое значение и другую последовательность инструкций.

4. Это не связано с контекстными менеджерами, которые только усложняют пример. Вопрос в основном заключается в том, «что такое функция генератора».

Ответ №1:

Использование yield в определении функции превращает функцию в генератор и работает она совершенно по-другому. Вот пример:

 &&t;&&t;&&t; def func1():
...   print('hi')
...   yield
...
&&t;&&t;&&t; def func2():
...   print('hi')
...   return
...
&&t;&&t;&&t; func1()  # returns a &enerator...doesn't execute the function!
<&enerator object func1 at 0x000002555B088510&&t;
&&t;&&t;&&t; func2()
hi
&&t;&&t;&&t; &=func1()  # save the &enerator
&&t;&&t;&&t; next(&)    # execute it to the next yield
hi
&&t;&&t;&&t; next(&)    # No more yields throws an exception
Traceback (most recent call last):
  File "<stdin&&t;", line 1, in <module&&t;
StopIteration
  

__enter__ предполагается, что это обычная функция, а не генератор, поэтому не используйте yield в этом случае.

Ответ №2:

Возможно, вы путаете «синтаксис yield» для создания контекстного менеджера (добавление with поддержки функции без необходимости создания класса).

 from contextlib import contextmana&er

@contextmana&er
def TestWith(val):
    print("Entered!")
    yield
    print("Exit")


with TestWith(4):
    print("printme")
  

Вывод:

 Entered!
printme
Exit
  

Комментарии:

1. Действительно, использование context mana&er привело меня к этому странному сценарию, но на самом деле это не дает ответа на данный вопрос. Спасибо за пример