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


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.