Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why "yield from" does not work as expected in all() or any()? [duplicate]

Today I was debugging a strange issue. The program is complex, but I have simplified the part in question to just few lines reproducing the strange behaviour.

In the example I test a random generator three times in a row. If all three tests return True, the test is completed. If not, the test must be repeated from the beginning.

Function func1 works OK. Function func2 with any() should be equivalent to func1, but it isn't. It does not work, it produces an error. The func3 is broken as well, this one is an infinite busy loop.

Where is the problem? It is legal to use yield from in other ways than value = yield from ... ? I did not found anything in the docs (so far):

When yield from is used, it treats the supplied expression as a subiterator. All values produced by that subiterator are passed directly to the caller of the current generator’s methods.


# Python 3.3 or newer
import random

def yield_random():
    if random.choice((True, False)):
        yield "OK"
        return True
    return False

def func1():
    # only this function works fine
    ok3 = False
    while not ok3:
        for i in range(3):
            ok1 = yield from yield_random()
            if not ok1:
                print("-- not ok")
                break
        else:
            print("All 3 ok !")
            ok3 = True

def func2():
    # does not work
    ok3 = False
    while not ok3:
        ok3 = all((yield from yield_random()) for i in range(3))
    print("All 3 ok !")

def func3():
    # does not work
    while any(not (yield from yield_random()) for i in range(3)):
        print("-- not ok")
    print("All 3 ok !")

for x in func1():
    print("got:", x)
like image 963
VPfB Avatar asked Nov 27 '25 21:11

VPfB


1 Answers

func1 is a function returning a generator while func2 and func3 are regular functions:

>>> type(func1())
<class 'generator'>
>>> type(func2())
All 3 ok !
<class 'NoneType'>

That's because:

ok3 = all((yield from yield_random()) for i in range(3))

is actually equivalent to:

def _gen():
    for i in range(3):
        r = yield from yield_random()
        yield r
ok3 = all(_gen())

This code doesn't yield anything since the yield from statement is encapsulated in a generator. You can even run it outside of a function, in your console for instance:

>>> all((yield from yield_random()) for i in range(3))
False

Though it probably doesn't do what you expect:

>>> list((yield from yield_random()) for i in range(3))
['OK', True, False, False]
like image 62
Vincent Avatar answered Nov 29 '25 09:11

Vincent



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!