Питання Що робить ключове слово "дохід"?


Що таке використання yield ключове слово в Python? Що це робить?

Наприклад, я намагаюся зрозуміти цей код1:

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

І це викликає:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Що відбувається при застосуванні методу _get_child_candidates називається? Чи повернувся список? Єдиний елемент? Чи викликається це знову? Коли наступні дзвінки зупиняться?


1. Код поставляється з Jochen Schulz (jrschulz), який створив велику бібліотеку Python для метричних простору. Це посилання на повне джерело: Модуль mspace.


8324
2017-10-23 22:21


походження




Відповіді:


Щоб зрозуміти що yield чи, треба розуміти що генератори є А перед генераторами прийти iterables.

Iterables

Коли ви створюєте список, ви можете читати його один за одним. Читання його елементів по черзі називається ітерацією:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist є ітерабельний. Коли ви використовуєте усвідомлення списку, ви створюєте список, а отже ітерабельний:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

Все, що ви можете використовувати "for... in..."on iterable; lists, strings, файли ...

Ці ітерабелі зручні, тому що ви можете їх прочитати настільки, скільки хочете, але ви зберігаєте всі значення в пам'яті, і це не завжди те, що ви хочете, коли маєте багато значень.

Генератори

Генератори є ітераторами, свого роду ітерабельними ви можете тільки повторювати один раз. Генератори не зберігають всі значення в пам'яті, вони генерують значення на льоту:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

Це точно так само, як ви використовували () замість []. Але ти не можу виконувати for i in mygenerator другий раз, оскільки генератори можуть використовуватися лише один раз: вони обчислюють 0, потім забудуть про нього і обчислюють 1, а кінцеве обчислення 4 - один за іншим.

Прибутковість

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

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Ось це даремний приклад, але це зручно, коли ви знаєте, що ваша функція поверне величезний набір цінностей, які вам потрібно буде лише прочитати один раз.

Освоїти yield, ви повинні це зрозуміти коли ви викликаєте функцію, код, який ви написали в тілі функції, не запускається. Функція повертає тільки об'єкт генератора, це трохи складно :-)

Тоді ваш код буде запускатися щоразу, коли for використовує генератор

Тепер важка частина:

Перший раз for викликає об'єкт генератора, створений за вашою функцією, він запускатиме код у вашій функції від початку, доки він не потрапляє yield, то він поверне перше значення циклу. Потім, кожен другий дзвінок буде виконувати цикл, який ви вже написали в функції ще раз, і повернете наступне значення, доки немає значення, яке потрібно повернути.

Генератор вважається пустим, коли функція запускається, але не потрапляє yield більше Це може бути тому, що цикл закінчився, або тому, що ви не задовольняєте "if/else" більше


Ваш код пояснено

Генератор:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Зателефонувач:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Цей код містить кілька розумних частин:

  • Цикл ініціюється в списку, але список розширюється, поки цикл повторюється :-) Це стислий спосіб пройти всі ці вкладені дані, навіть якщо це трохи небезпечно, оскільки ви можете закінчити безконечним циклом. В цьому випадку, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) вичерпує всі значення генератора, але while продовжує створювати нові об'єкти генератора, які будуть створювати різні значення від попередніх, оскільки вони не застосовуються до одного вузла.

  • The extend() Метод - це метод об'єкта списку, який очікує ітерабельність і додає свої значення до списку.

Зазвичай ми передаємо йому список:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Але у вашому коді він отримує генератор, що добре, тому що:

  1. Вам не потрібно двічі прочитати значення.
  2. У вас може бути багато дітей, і ви не хочете, щоб всі вони зберігалися в пам'яті.

І це працює, оскільки Python не хвилює, чи аргумент методу є списком чи ні. Python очікує iterables, так що він буде працювати з рядками, списками, кортежами та генераторами! Це називається типом качки і є однією з причин, чому Python настільки класний. Але це ще одна історія, для іншого питання ...

Ви можете зупинитися тут або почитати трохи, щоб побачити розширене використання генератора:

Контроль генератора виснаження

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Примітка: Для Python 3 використовуйтеprint(corner_street_atm.__next__()) або print(next(corner_street_atm))

Це може бути корисним для різних речей, таких як контроль доступу до ресурсу.

Ітертолс, твій найкращий друг

Модуль itertools містить спеціальні функції для маніпулювання iterables. Ви коли-небудь хочете дублювати генератор? Ланцюг два генератори? Групувати значення в вкладеному списку з одним вкладишем? Map / Zip не створюючи іншого списку?

Тоді просто import itertools.

Приклад? Давайте розглянемо можливі замовлення прибуття для чотириборства:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

Розуміння внутрішніх механізмів ітерації

Ітерація - це процес, що передбачає ітерації (реалізує __iter__() метод) і ітератори (реалізує __next__() метод) Iterables - це будь-які об'єкти, з яких ви можете отримати ітератор. Ітератори - це об'єкти, які дозволяють вам ітерації на iterables.

Про це в цій статті докладніше як for робочі цикли.


12188
2017-10-23 22:48



Все ітератори можуть бути повторені лише один раз, а не тільки ті, що виробляються за допомогою функцій генератора. Якщо ви не вірите мені, зателефонуйте iter() на будь-якому ітерамерному об'єкті і спробуйте повторювати результат над неодноразово. - augurar
@Craicerjack Ваші умови переплутані. Ітерабельність - це щось з __iter__ метод Ітератор є результатом дзвінка iter() на ітерабельному. Ітератори можна повторювати лише один раз. - augurar
yield це не так магічно, що дає така відповідь. Коли ви викликаєте функцію, яка містить a yield Заявка скрізь, ви отримуєте об'єкт генератора, але код не запускається. Потім кожен раз, коли ви витягуєте об'єкт з генератора, Python виконує код у функції, доки він не приходить до a yield твердження, потім призупиняє та доставляє об'єкт. Коли ви вилучаєте інший об'єкт, Python відновлює відразу після yield і триває, поки не досягне іншого yield (часто однакова, але одна ітерація пізніше). Це триває, поки функція не закінчиться, і тоді генератор вважатиметься вичерпаним. - Matthias Fripp
@MatthiasFripp вказував саме те, що змусило мене спотикатися, читаючи цю відповідь. "Ваш код буде запускатися кожен раз" - це введення в оману. "Ваш код буде продовжувати, звідки він залишився" - це більш точний спосіб його висловити. - Indigenuity
"Ці ітерабелі зручні ... але ви зберігаєте всі значення в пам'яті, і це не завжди те, що ви хочете", це неправильно або заплутано. Ітерабельність повертає ітератор після виклику ітера () на ітерабельному, і ітератор не завжди повинен зберігати свої значення в пам'яті залежно від реалізації ітера метод, він також може генерувати значення в послідовності на вимогу. - picmate 涅


Ярлик на Грокінг  yield

Коли ви бачите функцію з yield заяви, застосуйте цей простий трюк, щоб зрозуміти, що станеться:

  1. Вставте рядок result = [] на початку функціонування.
  2. Замініть кожен yield expr з result.append(expr).
  3. Вставте рядок return result внизу функції.
  4. Yay - не більше yield заяви! Прочитайте і зрозумійте код.
  5. Порівняйте функції з оригінальним визначенням.

Цей трюк може дати вам уявлення про логіку виконання функції, але те, що насправді відбувається yield істотно відрізняється від того, що відбувається в основі підходу, який спирається на список. У багатьох випадках підхід до виходу буде набагато більш ефективним і швидшим для пам'яті. В інших випадках цей трюк змусить вас застрягти в нескінченному циклі, навіть якщо оригінальна функція працює просто добре. Читайте далі, щоб дізнатися більше ...

Не плутайте свої ітератори, ітератори та генератори

По-перше, ітератор протокол - коли ти пишеш

for x in mylist:
    ...loop body...

Python виконує два етапи:

  1. Отримує ітератор для mylist:

    Зателефонувати iter(mylist) -> це повертає об'єкт з a next() метод (або __next__() в Python 3).

    [Це крок, який більшість людей забули розповісти вам про]

  2. Використовує ітератор для переміщення елементів:

    Продовжуйте телефонувати next() Метод на ітераторі повертається з кроку 1. Повертає значення з next() призначений x і виконується тіло петлі. Якщо виняток StopIteration піднімається зсередини next(), це означає, що не існує більше значень в ітераторі, а цикл вийшов.

Справді, Python виконує вищезазначені два кроки, коли він хоче петлі вміст об'єкта - так що це може бути для циклу, але це також може бути як код otherlist.extend(mylist) (де otherlist це список Python).

Ось тут mylist є ітерабельний тому що він реалізує ітераторний протокол. У визначеному користувачем класі, ви можете реалізувати __iter__() метод, який робить примірники вашого класу ітерабельними. Цей метод повинен повернути ітератор. Ітератор - це об'єкт з a next() метод Можна реалізувати обидва __iter__() і next() на одному класі, і є __iter__() повернутися self. Це буде працювати для простих випадків, але не тоді, коли потрібно, щоб два ітератори циклікували один і той же об'єкт одночасно.

Отже, це ітераторний протокол, багато об'єктів реалізують цей протокол:

  1. Вбудовані списки, словники, кортежі, набори, файли.
  2. Користувачі визначають класи, які реалізують __iter__().
  3. Генератори.

Зауважте, що a for цикл не знає, з яким об'єктом він має справу - він просто йде за протоколом ітератора, і з радістю отримує товар за предметом, як його закликає next(). Вбудовані списки повертають свої елементи один за одним, словники повертають ключі один за іншим, файли повертають лінії один за іншим і т. д. І генератори повертаються ... ну ось де yield входить:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Замість yield заяви, якщо у вас було три return заяви в f123() тільки перший буде виконаний, і функція вийде. Але f123() не є звичайною функцією. Коли f123() називається, це не повертайте будь-які значення у звітах про дохідність! Він повертає об'єкт генератора. Також, функція дійсно не виходить - вона переходить у призупинений стан. Коли for петля намагається зациклювати об'єкт генератора, функція відновлює її з призупиненого стану в самому наступному рядку після yield він раніше повернувся з, виконує наступний рядок коду, в цьому випадку a yield твердження, і повертає це як наступний елемент. Це відбувається, поки функція не виходить, і тоді генератор підніметься StopIteration, і цикл виходить.

Таким чином, об'єкт генератора є подібним до адаптера - на одному кінці він демонструє протокол ітератора, виставляючи його __iter__() і next() методи збереження for петля щаслива. Однак на іншому кінці він виконує функцію, достатню для того, щоб вийти з нього з наступного значення, і повертає його в режим призупинення.

Чому використовувати Генератори?

Зазвичай ви можете писати код, який не використовує генератори, але реалізує ту саму логіку. Одним з варіантів є використання тимчасового списку "трюк", про який я вже згадував раніше. Це не буде працювати у всіх випадках, наприклад, для якщо у вас є нескінченні цикли, або це може призвести до неефективного використання пам'яті, коли ви маєте дійсно довгий список. Інший підхід полягає в реалізації нового ітерабельного класу SomethingIter що зберігає державу, наприклад, членів і виконує наступний логічний крок у цьому next() (або __next__() в Python 3). Залежно від логіки, код всередині next() Метод може виявитися дуже складним і схильний до помилок. Тут генератори забезпечують чисте і просте рішення.


1638
2017-10-25 21:22



"Коли ви бачите функцію з виграшем, застосуйте цей простий трюк, щоб зрозуміти, що станеться" Чи не повністю це ігнорує той факт, що ви можете send у генератор, що є величезною частиною точки генераторів? - DanielSank
"це може бути для циклу, але це також може бути як код otherlist.extend(mylist)"-> Це неправильно. extend() модифікує список на місці і не повертає ітерабельний. Спроба переплітати otherlist.extend(mylist) збігається з a TypeError оскільки extend() неявно повертається None, і ти не можеш переплутати None. - Pedro
@pedro Ви неправильно зрозуміли цю пропозицію. Це означає, що пітон виконує дві згадані кроки mylist (не на otherlist) при виконанні otherlist.extend(mylist). - today


Подумайте про це таким чином:

Ітератор - це просто чудовий звуковий термін для об'єкта, який має метод next (). Отже, функція yield-ed закінчується щось на зразок цього:

Оригінальна версія:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Це, в основному, робить інтерпретатор Python з наведеним вище кодом:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Щоб дізнатися більше про те, що відбувається позаду сцен, for цикл може бути переписаний до цього:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Чи це має сенс або просто заплутує вас? :)

Я повинен зауважити, що це є надмірне спрощення для ілюстративних цілей. :)


397
2017-10-23 22:28



__getitem__ можна визначити замість __iter__. Наприклад: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i), Він буде друкувати: 0, 10, 20, ..., 90 - jfs
Я спробував цей приклад у Python 3.6, і якщо я створюю iterator = some_function(), змінна iterator не має функції з назвою next() більше, але тільки а __next__() функція Думаю, я б це згадав. - Peter


The yield ключове слово скорочується до двох простих фактів:

  1. Якщо компілятор виявляє yield ключове слово де завгодно всередині функції ця функція більше не повертається через return заява Замість цього, це негайно повертає a лінивий об'єкт "очікуваного списку" називається генератором
  2. Генератор ітерабельний. Що таке? ітерабельний? Це щось подібне до list або set або range або dict-view, з a вбудований протокол для відвідування кожного елемента в певному порядку.

Коротко: генератор - лінивий, список, що очікує на поступово, і yield висловлювання дозволяють використовувати позначення функції для програмування значень списку генератор повинен поступово виплюнути.

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Приклад

Давайте визначимо функцію makeRange це як Python's range. Дзвінок makeRange(n) ПОВТОРЮЄ ГЕНЕРАТОРА:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Щоб змусити генератор негайно повернути очікувані значення, ви можете його передати list() (як і будь-який ітерабельний):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Порівнюючи приклад "просто повертаючи список"

Наведений вище приклад можна розглядати як просто створити список, який ви додаєте та повертаєте:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

Існує одна основна відмінність, однак; див. останній розділ.


Як ви можете використовувати генератори

Ітерабельна остання частина розуміння списку, і всі генератори ітерабельні, тому вони часто використовуються так:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Щоб краще відчути генератори, ви можете грати з ними itertools модуль (обов'язково використовуйте chain.from_iterable а не chain коли це виправдано). Наприклад, ви навіть можете використовувати генератори для реалізації нескінченно довгих лених списків, таких як itertools.count(). Ви могли б реалізувати своє власне def enumerate(iterable): zip(count(), iterable), або ж зробити це за допомогою yieldключове слово в той час як цикл.

Зверніть увагу: генератори можуть бути використані для багатьох інших речей, таких як впровадження корунтів або недетерміноване програмування чи інші елегантні речі. Проте, погляд на "лінивих списках", який я представляю тут, є найпоширенішим використанням, яке ви знайдете.


За лаштунками

Так працює "протокол ітерації Python". Тобто, що відбувається, коли ви робите list(makeRange(5)). Це я вже описав раніше як "лінивий, поступовий список".

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Вбудована функція next() просто викликає об'єкти .next() функція, яка є частиною "протоколу ітерації" і знаходиться на всіх ітераторів. Ви можете вручну використовувати next() функція (та інші частини протоколу ітерації) для реалізації фантастичних речей, як правило, за рахунок читабельності, тому постарайтеся уникнути цього.


Мінус

Як правило, більшість людей не піклуються про такі відмінності і, мабуть, хочуть припинити читати тут.

У Python-говорять, а ітерабельний це будь-який об'єкт, який "розуміє поняття" for-loop ", як список [1,2,3], а також ітератор це конкретний екземпляр запрошеного для циклу, як [1,2,3].__iter__(). А. генератор точно так само, як і для будь-якого ітератора, за винятком способу написання (з синтаксисом функції).

Коли ви запитуєте ітератор зі списку, він створює новий ітератор. Проте, коли ви запитуєте ітератор з ітератора (який ви рідко це зробите), він просто дає вам копію сама.

Таким чином, в малоймовірній ситуації ви не спроможні зробити щось схоже на це ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... Тоді пам'ятайте, що генератор є ітератор; Тобто, це одноразове використання. Якщо ви хочете його повторно використовувати, ви повинні зателефонувати myRange(...) знову Якщо вам потрібно двічі використати результат, перетворіть результат у список і збережіть його в змінній x = list(myRange(5)). Той, хто абсолютно потребує клонування генератора (наприклад, хто робить жахливо хакерівську метапрограмування), може використовувати itertools.tee якщо це абсолютно необхідно, оскільки копіювальний ітератор Python PEP Пропозиція щодо стандартів була відкладена.


348
2018-06-19 06:33





Що таке yield ключове слово в Python?

Відповідь на схему / резюме

  • Функція з yield, коли називається повертає a Генератор.
  • Генератори є ітераторами, оскільки вони реалізують ітератор протокол, так що ви можете перейти на них.
  • Генератор також може бути надіслана інформація, що робить його концептуально а корутин.
  • У Python 3 ви можете делегувати від одного генератора до іншого в обох напрямках з yield from.
  • (Додаток критикує пару відповідей, включаючи перший, і обговорює використання return у генераторі.)

Генератори:

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

Ідея для генераторів походить з інших мов (див. Виноску 1) з різними реалізаціями. У генераторі Python, виконання коду є заморожений в точці виходу. Коли генератор називається (методи обговорюються нижче), виконання відновлюється, а потім зависає при наступному виході.

yield забезпечує легкий спосіб реалізація протоколу ітератора, визначається двома способами: __iter__ і next (Python 2) або __next__ (Python 3). Обидва ці методи зробіть об'єкт ітератором, який ви можете перевірити за типом Iterator Абстрактна база Клас з collections модуль

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Тип генератора - це підтип ітератора:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

І, якщо необхідно, ми можемо перевірити такий тип:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Особливість а Iterator  це колись виснажений, ви не можете повторно використовувати або скинути його:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Вам доведеться зробити ще одне, якщо ви знову хочете використовувати його функціональність (див. Виноску 2):

>>> list(func())
['I am', 'a generator!']

Можна дати дані програмно, наприклад:

def func(an_iterable):
    for item in an_iterable:
        yield item

Вищезгаданий простий генератор також еквівалентний нижченаведеному - як Python 3.3 (і недоступний в Python 2), ви можете використовувати yield from:

def func(an_iterable):
    yield from an_iterable

Однак yield from також дозволяє делегування субгенераторів, що буде пояснено в наступному розділі про делегування кооперативу з підкорекціями.

Коринти:

yield формує вираз, який дозволяє передавати дані в генератор (див. виноску 3)

Ось приклад, зверніть увагу на received змінна, яка вказує на дані, що надсилаються генератору:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

По-перше, ми повинні чергувати генератор з вбудованою функцією, next. Це буде назвіть відповідний next або __next__ метод, залежно від версії Ви використовуєте Python:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

І тепер ми можемо надсилати дані в генератор. (Відправлення None є так само, як дзвонити next.):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Кооперативна делегація в Sub-Coroutine з yield from

Тепер згадаймо це yield from доступний у Python 3. Це дозволяє нам делегувати підсумки для підкатегорії:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

І тепер ми можемо делегувати функціональні можливості суб-генератору і його можна використовувати генератором, як зазначено вище:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Ви можете дізнатись більше про точну семантику Росії yield from в PEP 380.

Інші методи: закрити і кинути

The close метод піднімається GeneratorExit в точці функції виконання було заморожено. Це також буде викликано __del__ так ти може поставити будь-який код очищення, де ви керуєте GeneratorExit:

>>> my_account.close()

Ви також можете викинути виняток, яким можна керувати генератором або поширюється назад користувачеві:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Висновок

Я вірю, що я охоплював всі аспекти наступного питання:

Що таке yield ключове слово в Python?

Виявляється, що yield робить багато. Я впевнений, що зможу додати ще більше ретельні приклади до цього. Якщо ви хочете отримати більше або отримати конструктивну критику, дайте мені знати, коментуючи нижче.


Додаток:

Критика верхнього / прийнятого відповіді **

  • Це сплутано з тим, що робить ітерабельний, просто використовуючи список як приклад. Перегляньте мої посилання вище, але коротко: ітерабельний має __iter__ метод повернення а ітератор. Ан ітератор забезпечує a .next (Python 2 або .__next__ (Python 3), який неявно називається forпетлі, поки воно не підніметься StopIteration, і як тільки це станеться, він буде продовжувати це робити.
  • Потім він використовує вираз генератора, щоб описати, що таке генератор. Оскільки генератор - це просто зручний спосіб створити ітератор, це лише плутає справу, і ми до сих пір ще не дізналися yield частина
  • В Контроль генератора виснаження він називає .next метод, коли замість нього він повинен використовувати побудовану функцію, next. Це був би відповідний шар неорієнтованості, оскільки його код не працює в Python 3.
  • Ітертолс? Це не стосується того, що yield робить взагалі.
  • Немає обговорення методів, які yield забезпечує разом з новою функціональністю yield from в Python 3. Вершина / прийнята відповідь є дуже неповною відповіддю.

Критика відповіді пропонуючи yield в генераторі вираження або розуміння.

Граматика в даний час дозволяє будь-яке вираження в розумінні списку.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Оскільки врожай є вираженням, то деякі з них цікаві як цікаві для розуміння або вираження генератора - незважаючи на те, що це не особливо корисне використання.

Основними розробниками CPython є обговорюючи припинення її допомоги. Ось відповідний пост із списку розсилки:

30 січня 2017 року в 19:05 Бретт Кеннон пише:

На Сонці, 29 січня 2017 року о 16:39 Крейг Родрігес писав:

Я добре з будь-яким підходом. Залиште речі так, як вони знаходяться в Python 3       не добре, іхо.

Мій голос - це синтаксична помилка, оскільки ви не отримуєте те, що ви очікуєте     синтаксис

Я згоден, що це розумне місце для нас, як будь-який код   спираючись на поточну поведінку, дуже розумно бути   обслуговується.

З точки зору попадання туди ми, ймовірно, хочемо:

  • Синтаксиспопередження або занепокоєння в 3.7
  • Попередження Py3k в 2.7.x
  • Синтаксичний випадок у 3.8

Ура, Нік.

- Нік Коглан | ncoghlan at gmail.com | Брісбен, Австралія

Крім того, є випуск емісії (10544) який, здається, вказує в напрямку цього ніколи це хороша ідея (PyPy, реалізація Python, написана на Python, вже підвищує синтаксичні попередження).

Підсумок, поки розробники CPython не скажуть нам про інше: Не кладіть yield в генераторі вираження або розуміння.

The return заява в генераторі

В Python 2:

У функції генератора return твердження не дозволено включати в себе expression_list. У цьому контексті голі return вказує на те, що генератор виконаний і буде викликаний StopIteration бути піднятий

Ан expression_list це в основному будь-яке число виразів, розділених комами - по суті, в Python 2 ви можете зупинити генератор з return, але ви не можете повернути значення.

В Python 3:

У функції генератора return Заява вказує на те, що генератор виконаний і буде викликаний StopIteration бути піднятий Повернутий знак (якщо є) використовується як аргумент для побудови StopIteration і стає StopIteration.valueатрибут

Виноски

  1. Мова CLU, Sather та Icon була вказана в пропозиції представити Python концепцію генераторів. Загальною ідеєю є що функція може підтримувати внутрішній стан і отримати проміжну характеристику дані вказують на вимогу користувача. Це обіцяло бути чудовий у виконанні на інші підходи, включаючи Python threading, який навіть не доступний для деяких систем.

  2.  Це означає, наприклад, що xrange об'єкти (range у Python 3) немає IteratorS, навіть якщо вони ітерабельні, тому що їх можна використовувати повторно. Як списки, їх __iter__ методи повернення ітераторних об'єктів.

  3. yield був спочатку представлений як заява, що означає, що це могла з'явитись лише на початку рядка в кодовому блоці. Зараз yield створює витримку врожаю. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt  Ця зміна була запропонований щоб користувач міг надсилати дані в генератор так само, як і можна отримати це. Для передачі даних потрібно мати можливість присвоїти йому щось, а також для цього заява просто не спрацює.


254
2018-06-25 06:11





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

У випадку вашого коду функція get_child_candidates діє як ітератор, так що, коли ви розширюєте свій список, воно додає один елемент одночасно до нового списку.

list.extend закликає ітератор до його вичерпання. У випадку викладеного вами зразка коду, було б набагато чіткіше, щоб просто повернути кортеж і додати його до списку.


230
2017-10-23 22:24



Це близько, але не правильно. Кожного разу, коли ви викликаєте функцію з вираженням доходу у ньому, він повертає новий об'єкт генератора. Тільки тоді, коли ви називаєте метод генератора .next (), що виконання виконується після останнього прибутковості. - kurosch
Щось, здається, відсутнє "відновить наступного разу функцію". Якщо це буде "буде відновлено наступного разу функцію бігає"? - Peter Mortensen


Є ще одна додаткова річ згадати: функція, яка дає, насправді не повинна закінчитися. Я написав код таким чином:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Потім я можу використовувати його в іншому коді, як це:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Це дійсно допомагає спростити деякі проблеми та полегшує роботу деяких справ.


182
2017-10-24 08:44





Для тих, хто вважає за краще мінімальний робочий приклад, медитуйте на цьому інтерактивному Python сесія:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed

155
2018-01-18 17:25



Це не відповідає на питання - ppperry


Врожай дає вам генератор.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Як ви бачите, у першому випадку foo тримає весь список у пам'яті відразу. Це не велика справа для списку з 5 елементами, але що робити, якщо ви хочете перерахувати 5 мільйонів? Це не просто величезний запам'ятовник пам'яті, він також коштує багато часу на побудову в той час, коли ця функція називається. У другому випадку бар просто дає вам генератор. Генератор є ітерабельним, що означає, що ви можете використовувати його в циклі для циклу тощо, але кожне значення можна отримати лише один раз. Всі значення також не зберігаються в пам'яті одночасно; об'єкт генератора "запам'ятовує", де воно було в циклі останнього разу, коли ви його називаєте - таким чином, якщо ви використовуєте ітерабельний (скажімо) рахунок до 50 мільярдів, вам не треба розраховувати до 50 мільярдів відразу ж і зберігайте 50 мільярдів номерів, щоб розраховувати. Знову ж таки, це досить прикладний приклад, ви, напевно, будете користуватися itertools, якщо ви дійсно хочете розраховувати до 50 мільярдів. :)

Це найпростіший спосіб використання генераторів. Як ви сказали, це може бути використано для написання ефективних перестановок, використовуючи вихідні дані, щоб підштовхнути процес через стек викликів замість використання певної змінної стека. Генератори також можуть бути використані для спеціалізованого переміщення дерева, і всілякі інші речі.


133
2018-01-16 06:42