Що робить ключове слово yield?

Яка користь від ключового слова 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 метод _get_child_candidates ? Список повернутий? Єдиний елемент? Це називається знову? Коли наступні дзвінки припиняться?


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

8911
заданий Alex. 24 окт. S. 24 Жовтня. 2008-10-24 01:21 '08 в 1:21 2008-10-24 1:21
@ 46 відповідей
  • 1
  • 2

Щоб зрозуміти, що yield , ви повинні розуміти, що таке генератори. І до генераторів приходять ітератори.

ітеріруемимі

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

 >>> 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... ", є ітеративним; lists , strings , файли ...

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

Генератори

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

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

Це те ж саме, за винятком того, що ви використовували () замість [] . АЛЕ, ви не можете виконати for я 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 object that will return the generator # 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 each time you use the generator object # 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 node object on its left # 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 node object on its right # 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 the current object reference # 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 the list # 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 list # 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 will have looked # 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 the 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 продовжує створювати нові об'єкти генератора, які будуть генерувати значення, відмінні від попередніх, оскільки він не застосовується до одного і того ж вузол .

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

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

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

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

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

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

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

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

 >>> class Bank(): # Let create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # When everything 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 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 ... much as you want >>> class Bank(): # Let create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # When everything 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 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 ... business >>> class Bank(): # Let create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # When everything 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 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, твій кращий друг

Модуль itertools містить спеціальні функції для управління ітераціями. Ви коли-небудь хотіли дублювати генератор? Ланцюжок двох генераторів? Групувати значення у вкладеному списку з однією лінією? 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__() ). Ітерації - це будь-які об'єкти, від яких ви можете отримати итератор. Ітератори - це об'єкти, які дозволяють повторювати ітерації.

Існує більше про це в цій статті про те , як for петлю роботи .

13022
24 окт. відповідь дан e-satis 24 Жовтня. 2008-10-24 01:48 '08 о 1:48 2008-10-24 1:48

Ярлик до Гроккінг 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) → повертає об'єкт з методом next() (або __next__() в Python 3).

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

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

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

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

border=0

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

Так що в протоколі ітератора багато об'єктів реалізують цей протокол:

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

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

 def f123(): yield 1 yield 2 yield 3 for item in f123(): print item 

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

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

Навіщо використовувати генератори?

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

1 744
26 окт. відповідь дан user28409 26 Жовтня. 2008-10-26 00:22 '08 в 0:22 2008-10-26 00:22

Думайте про це так:

Итератор - це просто химерний термін для об'єкта, у якого є метод 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 the 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 object returned by this method 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 the 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 

Це має більше сенсу або просто збиває вас з пантелику? :)

Мушу зазначити, що це спрощення в ілюстративних цілях. :)

441
24 окт. відповідь дан Jason Baker 24 Жовтня. 2008-10-24 01:28 '08 в 1:28 2008-10-24 1:28

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

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

У двох словах: генератор - це ледачий, поступово збільшується список, а оператори 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 яка схожа на range Python. Виклик 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 при chain.from_iterable гарантії). Наприклад, ви можете навіть використовувати генератори для реалізації нескінченно довгих ледачих списків, таких як itertools.count() . Ви можете реалізувати свій власний def enumerate(iterable): zip(count(), iterable) або, альтернативно, зробити це за допомогою ключового слова yield в циклі while.

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


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

Ось як працює "Протокол ітерації 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() objects .next() , яка є частиною "протоколу ітерації" і зустрічається на всіх Ітератор. Ви можете вручну використовувати функцію next() (і інші частини протоколу ітерації) для реалізації незвичайних речей, зазвичай за рахунок читабельності, тому постарайтеся не робити цього ...


дрібниці

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

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

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

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

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

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

378
19 июня '11 в 9:33 2011-06-19 09:33 відповідь дан ninjagecko 19 червня '11 в 9:33 2011-06-19 9:33

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

Схема відповіді / Резюме

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

Генератори:

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

Идея для генераторов исходит из других языков (см. Сноску 1) с различными реализациями. В Python Generators выполнение кода заморожено в точке выхода. Когда вызывается генератор (методы обсуждаются ниже), выполнение возобновляется, а затем останавливается при следующем выходе.

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 an iterable True >>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3) True # implements the iterator protocol.