Какво прави ключовата дума за доходност?

Каква е ползата от ключовата дума 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 .

8911
дадено от Алекс. 24 окт. S. 24 oct. 2008-10-24 01:21 '08 в 1:21 am 2008-10-24 01:21
@ 46 отговора
  • 1
  • 2

За да разберете какъв yield , трябва да разберете какво са генераторите. И преди генераторите да итератори.

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

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

генератори

Генераторите са итератори, вид повторение, което можете да повторите само веднъж . Генераторите не съхраняват всички стойности в паметта, а генерират стойности в движение :

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

Това е същото, с изключение на това, че сте използвали () вместо [] . НО, не можете да го направите for я in mygenerator втори път 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 , и след това връща първата стойност на цикъла. След това, всяко следващо повикване ще започне цикъла, който сте написали на функцията отново и ще върне следващата стойност, докато стойността се върне.

Генераторът се счита за празен, след като функцията е стартирана, но вече не попада в резултат. Това може да се дължи на факта, че цикълът е приключил, или поради факта, че вече не отговаряте на "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 

Caller:

 # 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 ... 

Забележка. За 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__() ). Итерациите са всички обекти, от които можете да получите итератор. Итератори са обекти, които ви позволяват да повторите повторенията.

В тази статия има повече информация за това как да свържете работата .

13022
24 окт. Отговорът е даден от e-satis 24 октомври. 2008-10-24 01:48 '08 в 1:48 2008-10-24 01:48

Отбелязвайте yield на Grocking

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

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

Тази техника може да ви даде представа за логиката на дадена функция, но това, което действително се случва с yield е значително по-различно от това, което се случва в подхода, основан на списък. В много случаи подходът на доходност ще бъде много по-ефективен и по-бърз. В други случаи този трик ще се забие в безкраен цикъл, дори ако оригиналната функция работи добре. Прочетете, за да научите повече ...

Не бъркайте итераторите, итераторите и генераторите.

Първо, протокола iterator - когато пишете

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

Python изпълнява следните две стъпки:

  1. Получава итератор за mylist :

    Извикването на iter(mylist) → връща обект с метода next() (или __next__() в Python 3).

    [Това е стъпка, за която повечето хора забравят да говорят]

  2. Използва итератор за елементите на цикъла:

    Продължете да извиквате метода next() на итератора, който се връща от стъпка 1. Връщаната стойност next() присвоява на x и тялото на цикъла се изпълнява. Ако изключението StopIteration извика от вътре в next() , това означава, че няма повече стойности в итератора и цикълът завършва.

Истината е, че Python изпълнява горните две стъпки по всяко време, когато иска да повтори съдържанието на даден обект - така че може да бъде за цикъл, но може да бъде и код като otherlist.extend(mylist) (където otherlist е списък с Python) )

border=0

Тук, mylist е итеративен, тъй като реализира протокола на iterator. В дефиниран от потребителя клас, можете да приложите метода __iter__() , за да направите вашите инстанции на класове итеративни. Този метод трябва да върне итератор. Итераторът е обект с метод next() . Можете да реализирате както __iter__() и next() в същия клас и да се __iter__() . Това ще работи за прости случаи, но не и когато искате два итератора да циклират един и същ обект едновременно.

Така че в протокола iterator много обекти прилагат този протокол:

  1. Вградени списъци, речници, кортежи, комплекти, файлове.
  2. Потребителски класове, които изпълняват __iter__() .
  3. Генератори.

Забележете, че for loop не знае с кой обект се занимава - просто следва протокола iterator и е щастлив да получи елемент по елемент, когато извиква next() . Вградените списъци връщат елементите си един по един, речниците връщат ключовете един по един, файловете връщат низове един по един и т.н. И генераторите се връщат ... добре, когато yield идва:

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

Вместо yield за yield , ако имаше три return f123() оператора в f123() само първата и функцията f123() . Но f123() не е обикновена функция. Когато f123() , не връща стойност в извлеченията за доходност! Връща обект-генератор. Освен това функцията не излиза в действителност - тя влиза в състояние на изчакване. Когато цикъла for опитва да обвърже обекта на генератора, функцията се връща от своето състояние на пауза на следващия ред след предишния върнат резултат, изпълнява следващия ред код, в този случай yield , и го връща като следващ елемент. Това се случва, докато функцията се освободи и в този момент генераторът StopIteration цикълът StopIteration .

По този начин обектът на генератора е подобен на адаптер - от единия край той демонстрира протокол за итератор, осигуряващ __iter__() и next() за поддържане на цикъл в добро състояние. На другия край обаче започва функция, която е достатъчна за получаване на следващата стойност, и я връща в режим на готовност.

Защо да използваме генератори?

Обикновено можете да пишете код, който не използва генератори, но изпълнява същата логика. Един от вариантите е да използвате временния списък с трикове, който споменах по-рано. Това няма да работи във всички случаи, например, ако имате безкрайни цикли, или може да доведе до неефективно използване на паметта, когато имате наистина дълъг списък. Друг подход е да се реализира новият итеративен клас SomethingIter който запазва състоянието в елементите на инстанцията и изпълнява следващата логическа стъпка в нея чрез метода next() (или __next__() в Python 3). В зависимост от логиката, кодът в метода next() може да изглежда много сложен и податлив на грешки. Тук генераторите осигуряват чисто и просто решение.

1744
26 окт. отговор, даден от user28409 26 октомври 2008-10-26 00:22 '08 в 0:22 2008-10-26 00:22

Мислете за това по следния начин:

Итераторът е просто фантастичен термин за обект, който има метод next (). Така че, ефективността на функцията в крайна сметка изглежда така:

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

 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 loop може да бъде пренаписан по следния начин:

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

Има ли повече смисъл или просто ви обърква? :)

Трябва да отбележа, че това е опростяване с илюстративни цели. :)

441
24 окт. Отговорете на Джейсън Бейкър на 24 октомври 2008-10-24 01:28 '08 в 1:28 2008-10-24 01:28

Ключовата дума за yield се свежда до две прости факти:

  1. Ако компилаторът открие ключовата дума yield където и да е вътре във функция, тази функция вече не се връща чрез return . Вместо това, той незабавно връща мързеливия обект на списъка на изчакване, наречен генератор.
  2. Генераторът се повтаря. Какво е повторимо? Това е нещо като range от list set list или преглед с диктовка с вграден протокол за посещение на всеки елемент в определен ред.

Накратко: генераторът е мързелив, постепенно увеличаващ се списък , а yield за 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. 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() просто извиква .next() обекти .next() , която е част от "итерационния протокол" и се появява на всички итератори. Можете ръчно да използвате функцията next() (и други части на протокола за итерация), за да реализирате необичайни неща, обикновено за сметка на четливостта, затова се опитайте да не правите това ...


малки неща

Обикновено повечето хора не се интересуват от следните различия и вероятно ще искат да спрат да четат тук.

В Python, iterative е всеки обект, който "разбира концепцията за for loop", например списък [1,2,3] , а итераторът е специфичен екземпляр на искания цикъл, например [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 09: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.