border=0

"93.78.239.52 - 93.78.239.52"

Wat doet het sleutelwoord met opbrengst?

Wat is het gebruik van het sleutelwoord yield in Python? Wat doet het?

Ik probeer bijvoorbeeld deze code 1 te begrijpen:

 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 

En dit is een dialer

 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 

Wat gebeurt er wanneer de _get_child_candidates methode _get_child_candidates ? Is de lijst terug? Eén artikel? Wordt het opnieuw genoemd? Wanneer stoppen de gesprekken?


1. De code is afkomstig van Jochen Schulz (jrschulz), die een uitstekende Python-bibliotheek heeft gemaakt voor metrische spaties. Dit is een link naar de volledige bron: de mspace-module .

8911
gegeven door Alex. 24 окт. S. 24 oktober 2008-10-24 01:21 '08 om 1:21 uur 2008-10-24 01:21 uur
@ 46 antwoorden
  • 1
  • 2

Om te begrijpen wat een yield , moet u weten wat generatoren zijn. En voordat de generators komen iterators.

iterables

Wanneer u een lijst maakt, kunt u de items één voor één lezen. Het één voor één lezen van de elementen wordt een iteratie genoemd:

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

mylist is herhaalbaar. Wanneer u lijstbegrip gebruikt, maakt u een lijst en daarom herhaalbaar:

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

Alles wat u kunt gebruiken " for... in... " is iteratief; lists , strings , bestanden ...

Deze iteraties zijn handig omdat je ze allemaal kunt lezen, maar je behoudt alle waarden in het geheugen en dit is niet altijd wat je wilt als je veel waarden hebt.

generatoren

Generators zijn iterators, een iteratietype dat u slechts eenmaal kunt herhalen . Generatoren slaan niet alle waarden op in het geheugen, ze genereren on-the-fly waarden :

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

Dit is hetzelfde, behalve dat je () hebt gebruikt in plaats van [] . MAAR, je kunt niet for я in mygenerator tweede keer for я in mygenerator , omdat de generators maar één keer kunnen worden gebruikt: ze berekenen 0, vergeten ze vervolgens en berekenen 1, en eindigen uiteindelijk 4, de een na de ander.

geven in

yield is een sleutelwoord dat wordt gebruikt als return , behalve dat de functie een generator zal teruggeven.

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

Hier is een nutteloos voorbeeld, maar het is handig als je weet dat je functie een enorme reeks waarden teruggeeft die je maar één keer hoeft te lezen.

Om het hoofd te bieden aan de yield , moet u begrijpen dat wanneer u een functie aanroept, de code die in de hoofdtekst van de functie is geschreven, niet start. De functie retourneert alleen het object van de generator, het is een beetje ingewikkeld :-)

Daarna gaat uw code verder waar hij gebleven was, elke keer for de generator gebruikt.

Nu het moeilijkste deel:

De eerste keer dat u belt for roept u een generatorobject aan dat is gemaakt met uw functie. Het voert de code vanaf het begin in uw functie uit tot het yield bereikt en retourneert vervolgens de eerste waarde van de lus. Vervolgens start elke volgende oproep de lus die u in de functie opnieuw hebt geschreven en retourneert u de volgende waarde totdat de waarde wordt geretourneerd.

De generator wordt als leeg beschouwd nadat de functie is gestart, maar krijgt niet >yield . Dit kan te wijten zijn aan het feit dat de lus is beëindigd, of omdat u niet >"if/else" .


Uw code uitgelegd

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 

beller:

 # 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 

Deze code bevat verschillende slimme delen:

  • De cyclus wordt herhaald in de lijst, maar de lijst breidt zich uit tijdens de lus-iteratie :-) Dit is een korte manier om al deze geneste gegevens te doorlopen, zelfs als het een beetje gevaarlijk is, omdat je een oneindige lus kunt krijgen. In dit geval gebruikt candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) alle generatorwaarden, maar blijft nieuwe generatorobjecten maken die andere waarden genereren dan de vorige, omdat deze niet van toepassing is op hetzelfde knooppunt .

  • De methode extend() is een methode van een lijstobject dat op iteratie wacht en de waarden aan de lijst toevoegt.

Meestal geven we hem een ​​lijst:

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

Maar in je code krijgt het een generator, wat goed is, omdat:

  1. U hoeft de waarden niet twee keer te lezen.
  2. Je hebt misschien veel kinderen en je wilt niet dat ze allemaal in het geheugen worden bewaard.

En het werkt omdat het Python niet uitmaakt of het argument van de methode een lijst is of niet. Python wacht op iteratie, dus het werkt met strings, lijsten, tuples en generators! Dit wordt duck genoemd en is een van de redenen waarom Python zo cool is. Maar dit is een ander verhaal voor een andere vraag ...

Je kunt hier stoppen of een beetje lezen om het geavanceerde gebruik van de generator te zien:

Generator uitputting monitoring

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

Let op. Gebruik voor Python 3 print(corner_street_atm.__next__()) of print(next(corner_street_atm))

Dit kan handig zijn voor verschillende dingen, zoals het controleren van de toegang tot een bron.

Itertools, je beste vriend

De itertools-module bevat speciale functies voor het beheren van iteraties. Heb je ooit een generator willen dupliceren? Een ketting van twee generatoren? Groepswaarden in een geneste lijst met één regel? Map/Zip zonder een nieuwe lijst te maken?

import itertools dan gewoon.

Een voorbeeld? Laten we eens kijken naar de mogelijke aankomstprocedures voor paardenrennen:

 >>> 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)] 

Inzicht in interne iteratiemechanismen

Iteratie is een proces dat iteraties omvat (implementatie van de __iter__() methode) en iteratoren (implementatie van de __next__() methode). Iteraties zijn alle objecten waarvan u een iterator kunt krijgen. Iterators zijn objecten waarmee u iteraties kunt herhalen.

Hierover gaat meer in dit artikel over loopwerken .

13022
24 окт. Het antwoord wordt e-satis 24 okt gegeven. 2008-10-24 01:48 '08 om 1:48 2008-10-24 01:48

Label voor de yield aan melkproducten

Wanneer u een functie met yield , gebruikt u deze eenvoudige truc om te begrijpen wat er gebeurt:

  1. Plaats de result = [] aan het begin van de functie.
  2. Vervang elke yield expr door result.append(expr) .
  3. Voeg het resultaat van de regel met het return result aan de onderkant van de functie.
  4. Yay - lever geen uitspraken meer uit! Lees en ontdek de code.
  5. Vergelijk de functie met de oorspronkelijke definitie.

Deze techniek kan u een idee geven van de logica van een functie, maar wat er daadwerkelijk gebeurt met een yield verschilt aanzienlijk van wat er gebeurt in de op lijsten gebaseerde benadering. In veel gevallen zal de rendementsbenadering veel efficiënter en sneller zijn. In andere gevallen blijft deze truc vastzitten in een oneindige lus, zelfs als de originele functie prima werkt. Lees verder voor meer informatie ...

Verwar iterators, iterators en generators niet met elkaar.

Allereerst het iterator-protocol - wanneer u schrijft

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

Python voert de volgende twee stappen uit:

  1. Krijgt een iterator voor mijn mylist :

    iter(mylist) → geeft een object met de next() methode (of __next__() in Python 3).

    [Dit is een stap die de meeste mensen vergeten te bespreken]

  2. Gebruikt een iterator om elementen in een lus te zetten:

    Ga door met het aanroepen van de methode next() op de iterator die is geretourneerd bij stap 1. De retourwaarde next() toegewezen aan x en de body van de lus wordt uitgevoerd. Als de StopIteration uitzondering van binnenuit next() StopIteration aangeroepen, betekent dit dat er geen waarden meer in de iterator staan ​​en dat de cyclus eindigt.

De waarheid is dat Python de bovenstaande twee stappen op elk gewenst moment uitvoert wanneer het de inhoud van een object wil herhalen - dus het kan een for-lus zijn, maar het kan ook een code zijn zoals otherlist.extend(mylist) (waarbij een andere lijst een Python-lijst is) )

Hier is mijn lijst iteratief omdat het het iterator-protocol implementeert. In een door de gebruiker gedefinieerde klasse kunt u de __iter__() -methode implementeren om exemplaren van uw klasse-iteratief te maken. Deze methode zou een iterator moeten retourneren. Een iterator is een object met een next() -methode. Je kunt zowel __iter__() en next() in dezelfde klasse implementeren en __iter__() terugkerende self . Dit werkt in eenvoudige gevallen, maar niet wanneer u wilt dat twee iterators hetzelfde object tegelijkertijd laten fietsen.

Dus, in het iterator-protocol, implementeren veel objecten dit protocol:

  1. Ingebouwde lijsten, woordenboeken, tuples, sets, bestanden.
  2. Aangepaste klassen die __iter__() implementeren.
  3. Generators.

Merk op dat de for lus niet weet met welk object het te maken heeft - het volgt gewoon het iterator-protocol en is blij om element voor element te krijgen bij het aanroepen van next() . Ingebouwde lijsten retourneren hun items één voor één, woordenboeken retourneren één voor één sleutels, bestanden retourneren een voor een, enz. En de generators komen terug ... nou, als de yield komt:

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

In plaats van de f123() , als er drie return f123() -operators in f123() alleen de eerste en de f123() -functie. Maar f123() geen gewone functie. Wanneer f123() , retourneert het geen waarde in de rendementsoverzichten! Retourneert een generatorobject. Bovendien komt de functie niet echt uit - hij komt in een staat van wachten. Wanneer de for -lus het generator-object probeert te herhalen, keert de functie terug van de gepauzeerde status op de volgende regel na de eerder geretourneerde resultaatopbrengst, voert de volgende regel code uit, in dit geval de yield , en retourneert deze als het volgende item. Dit gebeurt totdat de functie wordt vrijgegeven en op dit moment de StopIteration en de StopIteration cyclus.

Het generatorobject is dus vergelijkbaar met een adapter - aan het ene uiteinde toont het een iteratorprotocol, dat __iter__() en next() levert om een for lus in goede staat te houden. Aan de andere kant start het echter een functie die voldoende is om de volgende waarde te krijgen en zet deze terug in de standby-modus.

Waarom generatoren gebruiken?

U kunt meestal code schrijven die geen generators gebruikt, maar dezelfde logica implementeert. Een optie is om de tijdelijke tricklist te gebruiken die ik eerder noemde. Dit zal niet in alle gevallen werken, bijvoorbeeld als je oneindige loops hebt, of het kan leiden tot inefficiënt gebruik van het geheugen als je een erg >SomethingIter te implementeren die de staat opslaat in de elementen van de instantie en de volgende logische stap daarin uitvoert met de methode next() (of __next__() in Python 3). Afhankelijk van de logica kan de code in de methode next() er ingewikkeld uitzien en fouten vertonen. Hier bieden generatoren een schone en eenvoudige oplossing.

1744
26 окт. antwoord gegeven door user28409 26 okt. 2008-10-26 00:22 '08 om 0:22 2008-10-26 00:22

Zie het als volgt:

Een iterator is slechts een mooie term voor een object met een next () -methode. De opbrengstgerichte functie ziet er dus als volgt uit:

Originele versie:

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

Dit is eigenlijk wat de Python-interpreter doet met de bovenstaande code:

 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 

Om beter te begrijpen wat er achter de schermen gebeurt, kan de for lus als volgt worden herschreven:

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

Is het logischer of verwarrend? :)

Ik moet erop wijzen dat dit een vereenvoudiging is voor illustratieve doeleinden. :)

441
24 окт. Antwoord aan Jason Baker op 24 oktober 2008-10-24 01:28 '08 om 1:28 2008-10-24 01:28

Het sleutelwoord voor yield komt neer op twee simpele feiten:

  1. Als de compiler overal binnen een functie het yield detecteert, wordt deze functie niet >return . In plaats daarvan wordt onmiddellijk een lui wachtlijstobject geretourneerd , een generator genaamd.
  2. De generator wordt herhaald. Wat is herhaalbaar? Dit is zoiets als een lijstsetbereik of dict-view met een ingebed protocol voor het bezoeken van elk item in een specifieke volgorde.

In een notendop: een generator is een luie, geleidelijk toenemende lijst , en de yield stellen u in staat om de notatiefunctie te gebruiken om de lijstwaarden te programmeren die de generator geleidelijk moet uitvoeren.

 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]] 

een voorbeeld

Laten we een functie makeRange definiëren die vergelijkbaar is met het range Python. De makeRange(n) GENERATOR:

 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> 

Om de generator te dwingen onmiddellijk openstaande waarden te retourneren, kunt u deze doorgeven aan list() (net als elke iteratieve):

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

Een voorbeeld vergelijken met "gewoon een lijst retourneren"

Het bovenstaande voorbeeld kan worden gezien als eenvoudigweg het maken van een lijst waaraan u toevoegt en retourneert:

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

Er is echter één significant verschil; zie het laatste gedeelte.


Hoe kunt u generatoren gebruiken?

Herhaald is het laatste deel van het begrijpen van de lijst, en alle generators zijn iteratief, dus ze worden vaak als volgt gebruikt:

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

Om de generators beter te begrijpen, kun je spelen met de itertools module (zorg ervoor dat je chain.from_iterable en niet chain met een chain . Van geen chain.from_iterable garantie). U kunt bijvoorbeeld zelfs generatoren gebruiken om oneindig >itertools.count() . U kunt uw eigen def enumerate(iterable): zip(count(), iterable) of, als alternatief, doe dit met behulp van het yield sleutelwoord in een while-lus.

Merk op dat generatoren voor veel andere doeleinden kunnen worden gebruikt, zoals het implementeren van coroutines, niet-deterministische programmering of andere elegante dingen. De 'luie lijsten'-weergave, die ik hier vertegenwoordig, is echter het meest gebruikte gebruikersgebied dat u zult vinden.


Achter de schermen

Dit is hoe het Python Iteration Protocol werkt. Dat is wat er gebeurt als u een list(makeRange(5)) . Dit is wat ik eerder beschreef als een 'luie, extra lijst'.

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

De next() ingebouwde functie roept eenvoudig .next() -objecten .next() , dat deel uitmaakt van het "iteratieprotocol" en voorkomt in alle iterators. Je kunt de next() functie (en andere delen van het iteratieprotocol) handmatig gebruiken om ongewone dingen te implementeren, meestal ten koste van de leesbaarheid, dus probeer dit niet te doen ...


kleine dingen

Gewoonlijk geven de meeste mensen niet om de volgende verschillen en zullen ze waarschijnlijk hier willen stoppen met lezen.

In Python is iteratief elk object dat "het concept van een for-lus begrijpt", bijvoorbeeld een lijst [1,2,3] , en een iterator een specifiek exemplaar van de gevraagde lus is, bijvoorbeeld [1,2,3].__iter__() . De generator is exact hetzelfde als elke iterator, behalve de manier waarop deze is geschreven (met de syntaxis van de functie).

Wanneer u een iterator in de lijst aanvraagt, wordt er een nieuwe iterator gemaakt. Wanneer u echter een iterator van een iterator aanvraagt ​​(wat u zelden doet), krijgt u gewoon een kopie ervan.

Dus in het onwaarschijnlijke geval dat u niet in staat bent om zoiets te doen ...

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

... onthoud dan dat de generator een iterator is; dat wil zeggen eenmalig gebruik. Als u het opnieuw wilt gebruiken, moet myRange(...) myRange(...) bellen myRange(...) . Als u het resultaat twee keer wilt gebruiken, converteert u het resultaat naar een lijst en slaat u het op in de variabele x = list(myRange(5)) . Те, кому абсолютно необходимо клонировать генератор (например, кто выполняет ужасно хакерское метапрограммирование), могут использовать itertools.tee если это абсолютно необходимо, так как предложение стандартов Python PEP для итератора было отложено.

378
ответ дан 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.