Što čini ključna riječ prinosa?

Koja je upotreba ključne riječi yield u Pythonu? Što radi?

Na primjer, pokušavam razumjeti ovaj kôd 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 

A ovo je brojčanik

 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 

Što se događa kada metoda _get_child_candidates ? Je li se popis vratio? Jedna stavka? Je li opet pozvan? Kada će se naknadni pozivi zaustaviti?


1. Kôd je preuzet od Jochena Schulza (jrschulz), koji je stvorio izvrsnu Python knjižnicu za metričke prostore. Ovo je poveznica na cijeli izvor: Modul mspace .

8911
dao Alex. 24 окт. S. 24 oct. 2008-10-24 01:21 '08 u 1:21 am 2008-10-24 01:21
@ 46 odgovora
  • 1
  • 2

Da biste razumjeli što je yield , morate razumjeti što su generatori. I prije nego što generatori dođu iteratori.

iterables

Kada stvorite popis, možete pročitati njegove stavke jednu po jednu. Čitanje njegovih elemenata jedan po jedan naziva se iteracija:

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

mylist je ponovljiv. Kada koristite razumijevanje popisa, stvarate popis i stoga ponovljiv:

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

Sve što možete koristiti " for... in... " je iterativno; lists , strings , datoteke ...

Ove iteracije su prikladne jer možete čitati onoliko koliko želite, ali sve vrijednosti čuvate u memoriji, a to nije uvijek ono što želite kada imate puno vrijednosti.

generatori

Generatori su iteratori, vrsta ponavljanja koju možete ponoviti samo jednom . Generatori ne pohranjuju sve vrijednosti u memoriju, generiraju vrijednosti u hodu :

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

To je isto, osim što ste upotrijebili () umjesto [] . ALI, ne možete to učiniti for я in mygenerator drugi put, budući da se generatori mogu koristiti samo jednom: izračunavaju 0, zatim ga zaboravljaju i izračunavaju 1, te na kraju računaju 4, jedan za drugim.

popustiti

yield je ključna riječ koja se koristi kao return , osim što će funkcija vratiti generator.

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

Ovo je beskoristan primjer ovdje, ali je korisno kada znate da će vaša funkcija vratiti ogroman skup vrijednosti koje samo trebate pročitati jednom.

Da biste se nosili s yield , morate shvatiti da se kod poziva funkcije, kôd napisan u tijelu funkcije ne pokreće. Funkcija vraća samo objekt generatora, malo je komplicirana :-)

Tada se vaš kod nastavlja od mjesta gdje je stao svaki put, for korištenja generatora.

Sada najteži dio:

Kada prvi put nazovete for pozovete objekt generatora stvoren iz vaše funkcije, on će pokrenuti kôd u vašoj funkciji od samog početka dok ne dosegne yield , a zatim vraća prvu vrijednost petlje. Zatim će svaki sljedeći poziv ponovno pokrenuti petlju koju ste ponovno napisali funkciji i vratiti sljedeću vrijednost sve dok se vrijednost ne vrati.

Generator se smatra praznim nakon pokretanja funkcije, ali više ne ulazi u yield . To može biti zbog činjenice da je ciklus završio, ili zbog činjenice da više ne zadovoljavate "if/else" .


Vaš je kôd objašnjen

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 

pozivatelja:

 # 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 

Ovaj kôd sadrži nekoliko pametnih dijelova:

  • Ciklus se ponavlja u popisu, ali se popis proširuje tijekom iteracije petlje :-) Ovo je kratak način da prođete kroz sve ove ugniježđene podatke, čak i ako je malo opasan, jer možete dobiti beskonačnu petlju. U tom slučaju, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) iscrpljuju sve vrijednosti generatora, ali while nastavljaju stvarati nove objekte generatora koji će generirati vrijednosti koje nisu prethodne, budući da se ne primjenjuje na isti čvor ,

  • Metoda extend() je metoda objekta popisa koji čeka na iteraciju i dodaje svoje vrijednosti na popis.

Obično mu dajemo popis:

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

Ali u vašem kodu dobiva generator, što je dobro, jer:

  1. Ne morate dvaput čitati vrijednosti.
  2. Možda imate mnogo djece i ne želite da ih se sve čuva u sjećanju.

I to radi jer Python ne brine ako je argument metode popis ili ne. Python čeka na iteraciju, pa će raditi s nizovima, popisima, tuplesima i generatorima! To se naziva patka i jedan je od razloga zašto je Python tako cool. Ali ovo je još jedna priča za drugo pitanje ...

Ovdje se možete zaustaviti ili malo pročitati da biste vidjeli naprednu upotrebu generatora:

Kontrola isisavanja generatora

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

Napomena. Za Python 3 koristite print(corner_street_atm.__next__()) ili print(next(corner_street_atm))

To može biti korisno za razne stvari, kao što je kontroliranje pristupa resursu.

Itertools, tvoj najbolji prijatelj

Modul itertools sadrži posebne funkcije za upravljanje iteracijama. Jeste li ikada željeli kopirati generator? Lanac od dva generatora? Grupirajte vrijednosti u ugniježđenom popisu s jednim retkom? Map/Zip bez izrade drugog popisa?

Zatim samo import itertools .

Primjer? Pogledajmo moguće postupke dolaska za konjske utrke:

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

Razumijevanje unutarnjih iteracijskih mehanizama

Iteracija je proces koji podrazumijeva iteracije (implementiranje __iter__() metode) i iteratora (implementirajući __next__() ). Ponavljanje su svi objekti iz kojih možete dobiti iterator. Iteratori su objekti koji omogućuju ponavljanje ponavljanja.

O ovome se u ovom članku govori više o tome kako raditi u petlji .

13.022
24 окт. Odgovor daje e-satis 24 okt. 2008-10-24 01:48 '08 u 1:48 2008-10-24 01:48

Označite yield Grocking

Kada vidite funkciju s yield , koristite ovaj jednostavan trik da biste razumjeli što će se dogoditi:

  1. Umetnite result = [] na početku funkcije.
  2. Zamijenite svaki yield expr s result.append(expr) .
  3. Umetnite return result retka return result na dno funkcije.
  4. Yay - nema više izjava o yield ! Pročitajte i saznajte šifru.
  5. Usporedite funkciju s izvornom definicijom.

Ova tehnika može vam dati ideju o logici funkcije, ali ono što se zapravo događa s yield bitno se razlikuje od onoga što se događa u pristupu temeljenom na listama. U mnogim slučajevima pristup prinosa bit će mnogo učinkovitiji i brži. U drugim slučajevima ovaj će se trik zaglaviti u beskrajnoj petlji, čak i ako izvorna funkcija radi sasvim dobro. Čitajte dalje da biste saznali više ...

Nemojte brkati vaše iteratore, iteratore i generatore.

Prvo, iterator protokol - kada pišete

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

Python izvodi sljedeća dva koraka:

  1. Dobiva iterator za mylist :

    Pozivanje iter(mylist) → vraća objekt sa next() metodom (ili __next__() u Pythonu 3).

    [Ovo je korak o kojem većina ljudi zaborava razgovarati]

  2. Koristi iterator za elemente petlje:

    Nastavite s pozivanjem next() metode na iteratoru koji se vraća iz koraka 1. Povratna vrijednost next() dodjeljuje x i izvršava se tijelo petlje. Ako je iznimka StopIteration pozvana iznutra next() , to znači da više nema vrijednosti u iteratoru i ciklus se završava.

Istina je da Python izvodi gore navedena dva koraka u bilo koje vrijeme kada želi iterirati sadržaj nekog objekta - tako da može biti for loop, ali može biti i kod poput otherlist.extend(mylist) (gdje je otherlist Python popis )

border=0

Ovdje je mylist iterativan budući da implementira iterator protokol. U korisnički definiranoj klasi, možete implementirati __iter__() metodu kako bi vaše instance klase bile iterativne. Ova metoda bi trebala vratiti iterator. Iterator je objekt sa next() metodom. Možete implementirati i __iter__() i next() u istu klasu i imati __iter__() vraćajući self . To će raditi za jednostavne slučajeve, ali ne i kada želite da dva iteratora zaokruže isti objekt u isto vrijeme.

Tako u protokolu iterator mnogi objekti implementiraju ovaj protokol:

  1. Ugrađeni popisi, rječnici, tuples, setovi, datoteke.
  2. Prilagođene klase koje implementiraju __iter__() .
  3. Generatori.

Imajte na umu da for petlja ne zna s kojim predmetom se bavi - samo slijedi iterator protokol i rado će dobiti element po elementu kada zove next() . Ugrađeni popisi vraćaju svoje stavke jedan po jedan, rječnici vraćaju ključeve jedan po jedan, datoteke vraćaju nizove jedan po jedan, itd. A generatori se vraćaju ... pa, kada dođe do yield :

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

Umjesto yield o yield , ako su postojala tri return f123() operatora u f123() samo prva, i f123() funkcija. Ali f123() nije uobičajena funkcija. Kada f123() , ne vraća vrijednost u izvodima izvoda! Vraća objekt generatora. Osim toga, funkcija zapravo ne izlazi - ulazi u stanje čekanja. Kada for petlja pokuša skrenuti objekt generatora, funkcija se vraća iz svog zaustavljenog stanja na sljedeći redak nakon prethodno vraćenog yield rezultata, izvršava sljedeći redak koda, u ovom slučaju yield , i vraća ga kao sljedeću stavku. To se događa dok se funkcija ne oslobodi, au ovom trenutku generator StopIteration i StopIteration ciklus.

Dakle, objekt generatora je sličan adapteru - na jednom kraju on prikazuje protokol iteratora, osiguravajući __iter__() i next() za održavanje petlje u dobrom stanju. Na drugom kraju, međutim, pokreće funkciju koja je dovoljna za dobivanje sljedeće vrijednosti i vraća je u stanje pripravnosti.

Zašto koristiti generatore?

Obično možete pisati kod koji ne koristi generatore, ali implementira istu logiku. Jedna je mogućnost koristiti privremeni popis trikova koji sam ranije spomenuo. To neće raditi u svim slučajevima, na primjer, ako imate beskonačne petlje, ili to može dovesti do neučinkovitog korištenja memorije kada imate stvarno dugačak popis. Drugi pristup je implementirati novu iterativnu klasu SomethingIter koja sprema stanje u elementima instance i izvršava sljedeći logički korak u njoj pomoću next() metode (ili __next__() u Pythonu 3). Ovisno o logici, kod unutar next() metode može izgledati vrlo komplicirano i sklon pogreškama. Ovdje generatori osiguravaju čisto i jednostavno rješenje.

1744
26 окт. odgovor dao user28409 26. listopada 2008-10-26 00:22 '08 u 0:22 2008-10-26 00:22

Razmislite o tome ovako:

Iterator je samo zamišljen izraz za objekt koji ima sljedeću () metodu. Znači, funkcija prinosa na kraju izgleda ovako:

Izvorna verzija:

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

To je u osnovi ono što Python interpreter radi s gore navedenim kodom:

 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 

Kako bi bolje razumjeli što se događa iza kulisa, for petlja se može prepisati na sljedeći način:

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

Ima li više smisla ili vas samo zbunjuje? :)

Moram naglasiti da je ovo pojednostavljenje u ilustrativne svrhe. :)

441
24 окт. Odgovorite Jasonu Bakeru 24. listopada 2008-10-24 01:28 '08 u 1:28 2008-10-24 01:28

Ključna riječ yield svodi se na dvije jednostavne činjenice:

  1. Ako kompajler otkrije ključnu riječ yield bilo gdje unutar funkcije, ta se funkcija više ne vraća preko return . Umjesto toga, odmah vraća objekt lijen liste čekanja, nazvan generator.
  2. Generator se ponavlja. Što je ponovljivo? To je nešto poput range set list ili dict-view s ugrađenim protokolom za posjećivanje svake stavke u određenom redoslijedu.

Ukratko: generator je lijen, postupno rastući popis , a yield o yield omogućuju vam da koristite funkciju notacije za programiranje vrijednosti popisa koje bi generator trebao postupno izlaziti.

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

primjer

Definiramo funkciju makeRange koja je slična Python range . Poziv 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> 

Da bi se generator odmah vratio na čekanje, možete ga proslijediti na list() (kao i svaki drugi):

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

Uspoređujući primjer s "samo vraćanjem popisa"

Gornji se primjer može vidjeti kao jednostavno stvaranje popisa na koji dodajete i vraćate:

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

Međutim, postoji jedna značajna razlika; Pogledajte posljednji odjeljak.


Kako možete koristiti generatore

Iterirani je posljednji dio razumijevanja popisa, a svi generatori su iterativni, pa se često koriste ovako:

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

Da biste bolje razumjeli generatore, možete se poigrati s modulom itertools (svakako koristite chain.from_iterable a ne chain s jamstvom za chain.from_iterable ). Na primjer, čak možete koristiti generatore za implementaciju beskonačno dugih itertools.count() popisa, kao što je itertools.count() . Možete implementirati vlastitu def enumerate(iterable): zip(count(), iterable) ili, alternativno, to učinite pomoću ključne riječi yield u while petlji.

Imajte na umu da se generatori mogu koristiti za mnoge druge svrhe, kao što su implementacija koroutina, ne-determinističko programiranje ili druge elegantne stvari. Međutim, pogled "lazy lists", koji ovdje predstavljam, je najčešće korišteno područje koje ćete naći.


Iza kulisa

Tako funkcionira Python protokol iteracije. To se događa kada napravite list(makeRange(5)) . To je ono što sam ranije opisao kao "lijeni, dodatni popis".

 >>> 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() ugrađena funkcija jednostavno poziva .next() objekte .next() , koja je dio "iteracijskog protokola" i pojavljuje se na svim iteratorima. Možete ručno koristiti next() funkciju (i druge dijelove iteracijskog protokola) za implementaciju neobičnih stvari, obično na štetu čitljivosti, pa pokušajte to ne učiniti ...


sitnice

Obično većina ljudi ne mari za sljedeće razlike i vjerojatno će ovdje prestati čitati.

U Pythonu je iterativan bilo koji objekt koji "razumije koncept petlje", na primjer, popis [1,2,3] , a iterator je specifičan primjer tražene petlje, na primjer [1,2,3].__iter__() , Generator je potpuno isti kao bilo koji iterator, osim načina na koji je napisan (sintaksom funkcije).

Kada zatražite iterator s popisa, on stvara novi iterator. Međutim, kada zatražite iterator od iteratora (koji rijetko radite), on vam jednostavno daje svoju kopiju.

Tako da u malo vjerojatnom slučaju da ne možete učiniti nešto slično ...

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

... onda zapamtite da je generator iterator; tj. jednokratnu upotrebu. Ako ga želite ponovno koristiti, trebali biste myRange(...) nazvati myRange(...) . Ako trebate koristiti rezultat dva puta, pretvorite rezultat u popis i spremite ga u varijablu x = list(myRange(5)) . Oni koji apsolutno trebaju klonirati generator (na primjer, koji izvodi užasno itertools.tee hakera) mogu koristiti itertools.tee ako je to apsolutno neophodno, jer je ponuda Python PEP standarda za iterator odgođena.

378
19 июня '11 в 9:33 2011-06-19 09:33 odgovor je dan ninjagecko 19. lipnja '11 u 9:33 2011-06-19 09:33

Što čini ključna riječ yield u pythonu?

Shema odgovora / Sažetak

  • Funkcija yield na poziv vraća generator .
  • Generatori su iteratori jer implementiraju iterator protokol , tako da ih možete ponoviti.
  • Informacije se također mogu slati generatoru, što ga čini konceptualno koroutinom .
  • U Pythonu 3 možete delegirati iz jednog generatora u drugi u oba smjera koristeći yield from .
  • (Aplikacija kritizira nekoliko odgovora, uključujući i gornji, i raspravlja o korištenju return u generatoru.)

generatori:

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.