The return value (generator) of a function that combines finally and yield must not be passed directly to next

There is code like this.

def yielder():
    #try:
    print('yielder setup')
    yield 'text'
    #finally:
    print('yielder teardown')

def yielder_with_finally():
  try:
    print('yielder setup')
    yield 'text'
  finally:
    print('yielder teardown')

def f(text):
  print(text)

When calling yielder, if you say for y in yielder (): f (y), the output will be

yielder setup
text
yielder teardown

However, if you use it so that next is called only once (* 3 of the 4 methods described at the end excluding the former),

yielder setup
text

become that way. teardown is not called. By adding finally, teardown will be called.

However, when calling this function, with for y in yielder_with_finally (): f (y) or y = yielder_with_finally (); f (next (y))

yielder setup
text
yielder teardown

However, if you call t = next (yielder_with_finally ()); f (t) or f (next (yielder_with_finally ())),

yielder setup
yielder teardown
text

It becomes an unexpected execution order. It behaved similarly in CPython2 / 3. However, if teardown is executed by calling next only once, debugging becomes difficult (in IPython etc.), so it may not be possible to use it in this way in the first place. ** It may be a good idea not to use finally for teardown when yielding. ** **

Note that the yield fixture of pytest is implemented so that it turns twice properly, so this problem does not occur. https://docs.pytest.org/en/latest/_modules/_pytest/fixtures.html


In PyPy, with or without finally, it behaved the same as without finally in CPython.

I verified it with Ruby, but the behavior did not change with or without ensure (similar to PyPy).

def yielder()
  return to_enum(:yielder) if !block_given?
  #begin
    puts('yielder setup')
    yield 'text'
  #ensure
    puts('yielder teardown')
  #end
end

def yielder_with_ensure()
  return to_enum(:yielder_with_ensure) if !block_given?
  begin
    puts('yielder setup')
    yield 'text'
  ensure
    puts('yielder teardown')
  end
end

def f(text)
  puts(text)
end

Recommended Posts

The return value (generator) of a function that combines finally and yield must not be passed directly to next
[Linux] [C / C ++] How to get the return address value of a function and the function name of the caller
[C / C ++] Pass the value calculated in C / C ++ to a python function to execute the process, and use that value in C / C ++.
[Python] A program to find the number of apples and oranges that can be harvested
When incrementing the value of a key that does not exist
Addictive note: max (max (list)) must not be used when maxing the value of a 2D array
How to create a wrapper that preserves the signature of the function to wrap
Why the activation function must be a non-linear function
Return value of quit ()-Is there anything returned by the "function that ends everything"?
A solution to the problem that files containing [and] are not listed in glob.glob ()
[Python of Hikari-] Chapter 06-02 Function (argument and return value 1)
# Function that returns the character code of a string
Implementing a generator using Python> link> yield and next ()> yield
The story that the return value of tape.gradient () was None
The value of meta when specifying a function with no return value in Dask dataframe apply
I also tried to imitate the function monad and State monad with a generator in Python
How to suppress Pycharm's "pep8 indentation is not a multiple of four" and "Variable in function should be lower case" warnings
I made a function to check the model of DCGAN
I wrote AWS Lambda, and I was a little addicted to the default value of Python arguments
[Python] The role of the asterisk in front of the variable. Divide the input value and assign it to a variable
A simple reason why the return value of round (2.675,2) is 2.67 in python (it should be 2.68 in reality ...)