Непонятное поведение выхода Python - PullRequest
0 голосов
/ 24 февраля 2019

Сегодня я столкнулся с забавным поведением yield, которое мне не совсем понятно.Вот мой код:

def a():
    def b(x):
        print("entering b.")
        yield 0
        if x == 0:
            print("calling b.")
            b(x + 1)
            print("return from b.")
        print("leaving b.")

    for x in b(0):
        yield x

for x in a():
    print(x)

Это выводит:

entering b.
0
calling b.
return from b.
leaving b.

Что меня смущает, так это то, что явный вызов b(x + 1) не вызывает b (!), А также Python не даетлюбая ошибка или исключение.

Теперь очевидно, что ошибка в приведенном выше коде заключается в том, что b(x + 1) должен действительно давать значение, которое дает b, поэтому он должен читать что-то вроде:

for x in b(x + 1):
  yield x

Тогда все работает.

Тем не менее, это что-то с yield, о котором я должен знать?

Ответы [ 2 ]

0 голосов
/ 25 февраля 2019

Ответ , который вы получили до сих пор, является правильным (и я проголосовал за него), но я вижу, что вы все еще немного боретесь с этим, поэтому давайте попробуем этот вариант:

def a():
    def b(x):
        print("entering b.")
        yield 0
        if x == 0:
            print("calling b.")
            temp = b(x + 1)
            print("calling b resulted in temp =", temp)
            print("return from b.")
        print("leaving b.")

    for x in b(0):
        yield x

for x in a():
    print(x)

Теперь давайте запустим это в Python 3.x:

entering b.
0
calling b.
calling b resulted in temp = <generator object a.<locals>.b at 0x800ac9518>
return from b.
leaving b.

То есть temp устанавливается на результат вызова b(x + 1), а result равноэто <generator object ...> вещь.

Затем вам нужно что-то сделать с объектом-генератором, так что вот еще третий вариант:

def a():
    def b(x):
        print("entering b.")
        yield 0
        if x == 0:
            print("calling b.")
            temp = b(x + 1)
            print("calling b resulted in temp =", temp)
            y = next(temp)
            print("by doing next(temp), I got", y)
            print("return from b.")
        print("leaving b.")

    for x in b(0):
        yield x

for x in a():
    print(x)

Выполнение этого приводит к:

entering b.
0
calling b.
calling b resulted in temp = <generator object a.<locals>.b at 0x800ac9518>
entering b.
by doing next(temp), I got 0
return from b.
leaving b.

Вариант yield from в другом ответе в основном означает «продолжайте вызывать temp и приносить то, что он дает, пока не скажет, что это сделано».Это y = next(temp) называется temp всего один раз.

Упражнение для читателя: Попробуйте четвертый вариант, указанный ниже.Постарайтесь предсказать, прежде чем запустить, что вы увидите.Вы видите, что вы предсказали?

def a():
    def b(x):
        print("entering b.")
        yield 0
        if x == 0:
            print("calling b.")
            temp = b(x + 1)
            print("calling b resulted in temp =", temp)
            y = next(temp)
            print("by doing next(temp), I got", y)
            try:
                print("about to re-enter temp")
                y = next(temp)
                print("with the second next(temp), I got", y)
            except StopIteration:
                print("with the second next(temp), I got StopIteration")
            print("return from b.")
        else:
            print("b had x =", x)
        print("leaving b.")

    for x in b(0):
        yield x

for x in a():
    print(x)
0 голосов
/ 24 февраля 2019

Вызывается b(x + 1), но не выполняется до тех пор, пока не будет получено в контексте вызывающей функции.

Использование yield from для получения всех значений, полученных этим вызовом, для b() и выполнения тела:

def a():
    def b(x):
        print("entering b.")
        yield 0
        if x == 0:
            print("calling b.")
            yield from b(x + 1)
            print("return from b.")
        print("leaving b.")

    for x in b(0):
        yield x

for x in a():
    print(x)
...