Што робіць ключавое слова 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 у 01:21 2008-10-24 01: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 у 01:48 2008-10-24 01: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 з'яўляецца итеративным, паколькі ён рэалізуе пратакол итератора. У якi вызначаецца карыстальнікам класе вы можаце рэалізаваць метад __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() можа выглядаць вельмі складаным і быць схільным памылак. Тут генератары забяспечваюць чыстае і простае рашэнне.

1744
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 01: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 у 09: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.