Повернення і прибутковість в одній функції


77

Що саме відбувається, коли yield і return використовуються в одній функції в Python, наприклад?

def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1: return
        yield start
        start += len(sub) # use start += 1 to find overlapping matches

Це все-таки генератор?

Відповіді:


75

Так, це все ще генератор. Це return(майже) еквівалентно підвищенню StopIteration.

PEP 255 пише:

Специфікація: Повернення

Функція генератора може також містити оператори повернення у вигляді:

"return"

Зверніть увагу, що перелік_виразів заборонений для операторів return у тілі генератора (хоча, звичайно, вони можуть з'являтися в тілах негенераторних функцій, вкладених у генератор).

Коли зустрічається оператор return, керування виконується, як і у будь-якій функції повернення, виконуючи відповідні пункти final (якщо такі існують). Потім піднімається виняток StopIteration, що сигналізує про те, що ітератор вичерпано. Виняток StopIteration також застосовується, якщо керування стікає з кінця генератора без повернення експлікта.

Зверніть увагу, що return означає "я закінчив і не маю чого цікавого повернути" як для генераторських функцій, так і для негенераторних функцій.

Зверніть увагу, що повернення не завжди еквівалентно підвищенню StopIteration: різниця полягає в тому, як обробляються конструкції, що включають try / Osim. Наприклад,

>>> def f1():
...     try:
...         return
...     except:
...        yield 1
>>> print list(f1())
[]

тому що, як і в будь-якій функції, return просто виходить, але

>>> def f2():
...     try:
...         raise StopIteration
...     except:
...         yield 42
>>> print list(f2())
[42]

тому що StopIteration фіксується голим "за винятком", як і будь-який виняток.


4
Чи знаєте ви, що сталося б, якби returnсварилися?
zwol

15
@Zack В Python 2.x, це було б SyntaxError: SyntaxError: 'return' with argument inside generator. Це дозволено в Python 3.x, але в першу чергу призначене для використання з підпрограмами - ви здійснюєте асинхронні дзвінки до інших підпрограм за допомогою yield coroutine()(або yield from coroutine(), залежно від асинхронного фреймворку, який ви використовуєте), і повертаєте все, що ви хочете повернути з підпрограми використання return value. У Python 2.x вам потрібно використовувати фокус, як raise Return(value)повернення значень із корутинів.
dano

31

Так, це все ще генератор. Порожній returnабо return Noneможе бути використаний для завершення функції генератора. Це еквівалентно підняттю StopIteration(див. Відповідь @ NPE для деталей).

Зверніть увагу, що повернення з аргументами non-None є SyntaxErrorверсією Python до 3.3.

Як зазначав @BrenBarn у коментарях, починаючи з Python 3.3, тепер повертається значення StopIteration.

З PEP 380 :

У генераторі - твердження

return value

семантично еквівалентно

raise StopIteration(value)

1
Чи знаєте ви, що сталося б, якби хтось returnмав суперечку (крім None)?
zwol

6
У Python 3.3 та новіших returnверсіях ви можете використовувати з аргументом передачу аргументу до StopIteration, що викликається. Дивіться це питання .
BrenBarn

@BrenBarn Цікаво, не знав цього.
Ashwini Chaudhary

1
@AshwiniChaudhary Реалізація спільної програми в новому asyncioмодулі побудована на цій функції (разом із yield fromключовим словом).
dano

3
@AshwiniChaudhary Це увімкнуло основні підпрограми в Python - можливість надсилати значення / винятки в генератори та отримувати їх через value = yieldтощо. Введення yield fromта можливість returnзначень з генераторів поставляються з PEP 380 , обидва з яких використовуються за допомогою asyncio. Ви все ще можете мати надійну спільну реалізацію лише з функціями, передбаченими PEP 343, просто трохи менше їх написання.
dano

11

Існує спосіб досягти методу yield і return у функції, яка дозволяє повернути значення або генератор.

Можливо, він не такий чистий, як хотілося б, але робить те, що очікуєш.

Ось приклад:

def six(how_many=None):
    if how_many is None or how_many < 1:
        return None  # returns value

    if how_many == 1:
        return 6  # returns value

    def iter_func():
        for count in range(how_many):
            yield 6
    return iter_func()  # returns generator

не ясно, "що він робить те, що ти очікуєш". чи не могли б ви навести приклад того, як ваш підхід можна ефективно використати?
ShpielMeister

"що він робить те, що ви очікуєте", як у темі запитання "Повернення та прибутковість у тій самій функції". Особисто я перейшов на Haskell, і цим можна було б добре керувати / керувати з алгебраїчним типом даних, але з Python досить складно управляти типами, і це не вкладається в декларації типу Python. Отже, якщо ви ставите питання про те, як це можна використати, будь ласка, не використовуйте його. В іншому випадку це дозволяє повернути значення no, одиночне або кілька. Це може бути використано для ефективного обходу дерева.
Вільям Руснак,

0

Примітка: ви не отримаєте StopIterationвиняток із прикладом нижче.

def odd(max):
    n = 0
    while n < max:
        yield n
        n = n + 1
    return 'done'


for x in odd(3):
    print(x)

forЦикл ловить його. Це його сигнал зупинитися

Але ви можете зловити це таким чином:

g = odd(3)

while True:
    try:
        x = next(g)
        print(x)
    except StopIteration as e:
        print("g return value:", e.value)
        break
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.