Zdefiniowałem funkcję f jako

def f(flag):
    n = 10
    if flag:
        for i in range(n):
            yield i
    else:
        return range(n)

Ale f zwraca generator bez względu na to, co jest flag

>>> f(True)
<generator object f at 0x0000000003C5EEA0>

>>> f(False)
<generator object f at 0x0000000007AC4828>

A jeśli ITERE Wracam zwrócony obiekt:

# prints normally
for i in f(True):
    print(i)

# doesn't print
for i in f(False):
    print(i)

Wygląda na f(False) zwraca generator, który został zleczony. Jaki jest powód? Dziękuję Ci.

9
ziyuang 14 sierpień 2014, 20:41

2 odpowiedzi

Najlepsza odpowiedź

Funkcja zawierająca oświadczenie yield zawsze zwraca obiekt generatora.

Tylko wtedy, gdy przetestujesz ten obiekt generatora, zostanie wykonany kod w funkcji. Do tego czasu nie wykonuje żadnego kodu w funkcji, a Python nie mogę poznać , że po prostu wrócisz.

Należy pamiętać, że przy użyciu return w funkcji generatora ma różne semantyki niż w regularnej funkcji; return W tym przypadku jest widoczny jako "wyjście z generatora tutaj"; Wartość powrotowa jest odrzucana jako generator może produkować tylko wartości za pomocą wyrażeń yield.

Wygląda na to, że chcesz użyć yield from zamiast tego:

def f(flag):
    n = 10
    if flag:
        for i in range(n):
            yield i
    else:
        yield from range(n)

yield from Wymaga Pythona 3.3 lub w górę.

Zobacz yield Expression:

Korzystanie z wyrażenia yield w organizmie funkcji powoduje, że funkcja jest generatorem.

Gdy wywoływana jest funkcja generatora, zwraca iterator znany jako generator. Generator ten kontroluje wykonanie funkcji generatora. Wykonanie rozpoczyna się, gdy wywołany jest jeden z metod generatora. W tym czasie wykonanie wpływa do wyrażenia yield, gdzie jest ponownie zawieszony, zwracając wartość wyrażenia_Listów do rozmówcy generatora.

Iteracja nad generatorem wywołuje {x0}} Metoda, wykonanie wyzwalania.

Jeśli chcesz zwrócić generator niektóre czas , a następnie nie używaj yield w tej funkcji. Produkujesz generator za pomocą innych środków; Na przykład przy użyciu oddzielnej funkcji lub za pomocą wyrażenia generatora być może:

def f(flag):
    n = 10
    if flag:
        return (i for i in range(n))
    else:
        return range(n)

Teraz nr yield jest używany w f i nie będzie już wytworzyć obiektu generatora bezpośrednio. Zamiast tego wyrażenie generatora (i for i in range(n)) wytwarza go, ale tylko warunkowo.

13
Martijn Pieters 14 sierpień 2014, 16:52

Możesz pracować wokół tego, używając zagnieżdżonej funkcji, która faktycznie używa yield:

def f(flag):
    def gen():
        for i in range(n):
            yield i
    n = 10
    if flag:
        return gen()
    else:
        return range(n)

>>> f(True)
<generator object gen at 0x7f62017e3730>
>>> f(False)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Gdy Martijn wskazuje, że każda funkcja zawierająca yield zawsze zwróci obiekt generatora, więc jeśli w niektórych przypadkach chcesz, aby ciało {X1}}, aby faktycznie zostać wykonane, gdy f() jest raczej nazywa Niż zostanie wykonany tylko wtedy, gdy się czuje, musisz użyć tego podejścia.

Standardowa metoda instancji biblioteki {x0}} z concurrent.Futures.ThreadPoolExecutor / concurrent.Futures.ThreadPoolExecutor Używa tego, aby zapewnić przedstawienie kontraktów terminowych, jak tylko map jest nazywany, a nie tylko wtedy, gdy próbujesz odzyskać wyniki z niego, na przykład:

def map(self, fn, *iterables, timeout=None):
    if timeout is not None:
        end_time = timeout + time.time()

    fs = [self.submit(fn, *args) for args in zip(*iterables)]

    # Yield must be hidden in closure so that the futures are submitted
    # before the first iterator value is required.
    def result_iterator():
        try:
            for future in fs:
                if timeout is None:
                    yield future.result()
                else:
                    yield future.result(end_time - time.time())
        finally:
            for future in fs:
                future.cancel()
    return result_iterator()
5
dano 14 sierpień 2014, 16:56