Tässä opetusohjelmassa opit luomaan iteraatioita helposti Python-generaattoreilla, kuinka se eroaa iteraattoreista ja normaaleista toiminnoista, ja miksi sinun pitäisi käyttää sitä.
Video: Python-generaattorit
Generaattorit Pythonissa
Iteraattorin rakentamisessa Pythonissa on paljon työtä. Meidän on toteutettava luokka __iter__()
ja __next__()
menetelmä, seurattava sisäisiä tiloja ja korotettava, StopIteration
kun palautettavia arvoja ei ole.
Tämä on sekä pitkä että vasta-intuitiivinen. Generaattori tulee apuun tällaisissa tilanteissa.
Python-generaattorit ovat yksinkertainen tapa luoda iteraattoreita. Pythonin generaattorit käsittelevät kaiken edellä mainitun työn automaattisesti.
Yksinkertaisesti sanottuna generaattori on funktio, joka palauttaa objektin (iteraattorin), jonka voimme iteroida (yksi arvo kerrallaan).
Luo generaattoreita Pythonissa
Generaattorin luominen Pythoniin on melko helppoa. Se on yhtä helppoa kuin normaalin funktion määritteleminen, mutta yield
lauseella lauseen sijaan return
.
Jos funktio sisältää ainakin yhden yield
lauseen (se voi sisältää muita yield
tai return
lauseita), siitä tulee generaattorifunktio. Molemmat yield
ja return
palauttavat jonkin arvon funktiosta.
Erona on, että vaikka return
käsky lopettaa toiminnon kokonaan, yield
lause keskeyttää toiminnon, joka tallentaa kaikki tilansa, ja jatkuu myöhemmin sieltä peräkkäisissä puheluissa.
Generaattorin ja normaalitoiminnon erot
Näin generaattoritoiminto eroaa normaalista toiminnosta.
- Generaattori-toiminto sisältää yhden tai useamman
yield
lauseen. - Kun sitä kutsutaan, se palauttaa objektin (iteraattorin), mutta ei aloita suoritusta heti.
- Menetelmät pitävät
__iter__()
ja__next__()
toteutetaan automaattisesti. Joten voimme toistaa kohteiden läpi käyttämällänext()
. - Kun toiminto tuottaa, toiminto keskeytetään ja ohjaus siirretään soittajalle.
- Paikalliset muuttujat ja niiden tilat muistetaan peräkkäisten puhelujen välillä.
- Lopuksi, kun toiminto päättyy,
StopIteration
se nousee automaattisesti uusissa puheluissa.
Tässä on esimerkki kaikkien edellä mainittujen kohtien havainnollistamiseksi. Meillä on generaattoritoiminto, joka on nimetty my_gen()
useilla yield
lauseilla.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
Tulkin vuorovaikutteinen ajo on annettu alla. Suorita nämä Python-kuoressa nähdäksesi tuotoksen.
>>> # It returns an object but does not start execution immediately. >>> a = my_gen() >>> # We can iterate through the items using next(). >>> next(a) This is printed first 1 >>> # Once the function yields, the function is paused and the control is transferred to the caller. >>> # Local variables and theirs states are remembered between successive calls. >>> next(a) This is printed second 2 >>> next(a) This is printed at last 3 >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls. >>> next(a) Traceback (most recent call last):… StopIteration >>> next(a) Traceback (most recent call last):… StopIteration
Yksi mielenkiintoinen asia huomata yllä olevassa esimerkissä on, että muuttujan n arvo muistetaan jokaisen puhelun välillä.
Toisin kuin normaalit funktiot, paikallisia muuttujia ei tuhota, kun funktio tuottaa. Lisäksi generaattoriobjekti voidaan toistaa vain kerran.
Prosessin uudelleenkäynnistämiseksi meidän on luotava toinen generaattoriobjekti käyttämällä jotain sellaista a = my_gen()
.
Viimeinen asia on huomata, että voimme käyttää generaattoreita silmukoihin suoraan.
Tämä johtuu siitä, että for
silmukka ottaa iteraattorin ja iteroi sen yli next()
toiminnon avulla. Se päättyy automaattisesti, kun StopIteration
se nostetaan. Tarkista tästä, kuinka for -silmukka todella toteutetaan Pythonissa.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop for item in my_gen(): print(item)
Kun suoritat ohjelmaa, tulos on:
Tämä tulostetaan ensin 1 Tämä tulostetaan toinen 2 Tämä tulostetaan viimeisenä 3
Python-generaattorit, joissa on silmukka
Yllä olevasta esimerkistä on vähemmän hyötyä, ja tutkimme sitä vain saadaksemme kuvan taustalla tapahtuvasta.
Normaalisti generaattoritoiminnot toteutetaan silmukalla, jolla on sopiva loppuehto.
Otetaan esimerkki generaattorista, joka kääntää merkkijonon.
def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str(i) # For loop to reverse the string for char in rev_str("hello"): print(char)
Tuotos
olleh
Tässä esimerkissä olemme käyttäneet range()
funktiota hakemaan indeksi päinvastaisessa järjestyksessä for-silmukan avulla.
Huomaa : Tämä generaattoritoiminto ei toimi vain merkkijonojen, vaan myös muun tyyppisten iterable-tiedostojen, kuten luettelon, dupleksin jne. Kanssa.
Python-generaattorin lauseke
Yksinkertaisia generaattoreita voidaan luoda helposti lennossa generaattorilausekkeilla. Se tekee generaattorien rakentamisesta helppoa.
Samoin kuin lambda-funktiot, jotka luovat nimettömiä toimintoja, generaattorin lausekkeet luovat nimettömiä generaattorin toimintoja.
Generaattorin lausekkeen syntakse on samanlainen kuin Pythonin luettelon ymmärtäminen. Mutta hakasulkeet korvataan pyöreillä sulkeilla.
Suurin ero luettelon ymmärtämisen ja generaattorin lausekkeen välillä on se, että luettelon ymmärtäminen tuottaa koko luettelon, kun taas generaattorin lauseke tuottaa yhden kohteen kerrallaan.
They have lazy execution ( producing items only when asked for ). For this reason, a generator expression is much more memory efficient than an equivalent list comprehension.
# Initialize the list my_list = (1, 3, 6, 10) # square each term using list comprehension list_ = (x**2 for x in my_list) # same thing can be done using a generator expression # generator expressions are surrounded by parenthesis () generator = (x**2 for x in my_list) print(list_) print(generator)
Output
(1, 9, 36, 100)
We can see above that the generator expression did not produce the required result immediately. Instead, it returned a generator object, which produces items only on demand.
Here is how we can start getting items from the generator:
# Initialize the list my_list = (1, 3, 6, 10) a = (x**2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) next(a)
When we run the above program, we get the following output:
1 9 36 100 Traceback (most recent call last): File "", line 15, in StopIteration
Generator expressions can be used as function arguments. When used in such a way, the round parentheses can be dropped.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Use of Python Generators
There are several reasons that make generators a powerful implementation.
1. Easy to Implement
Generators can be implemented in a clear and concise way as compared to their iterator class counterpart. Following is an example to implement a sequence of power of 2 using an iterator class.
class PowTwo: def __init__(self, max=0): self.n = 0 self.max = max def __iter__(self): return self def __next__(self): if self.n> self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
The above program was lengthy and confusing. Now, let's do the same using a generator function.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Since generators keep track of details automatically, the implementation was concise and much cleaner.
2. Memory Efficient
A normal function to return a sequence will create the entire sequence in memory before returning the result. This is an overkill, if the number of items in the sequence is very large.
Generator implementation of such sequences is memory friendly and is preferred since it only produces one item at a time.
3. Represent Infinite Stream
Generaattorit ovat erinomaisia välineitä edustamaan ääretöntä datavirtaa. Äärettömiä virtoja ei voida tallentaa muistiin, ja koska generaattorit tuottavat vain yhden kohteen kerrallaan, ne voivat edustaa ääretöntä datavirtaa.
Seuraava generaattoritoiminto voi tuottaa kaikki parilliset luvut (ainakin teoriassa).
def all_even(): n = 0 while True: yield n n += 2
4. Putkilinjan generaattorit
Useita generaattoreita voidaan käyttää sarjaan toimintoja. Tämä voidaan parhaiten havainnollistaa esimerkin avulla.
Oletetaan, että meillä on generaattori, joka tuottaa Fibonacci-sarjan numerot. Ja meillä on toinen generaattori numeroiden neliöimiseksi.
Jos haluamme selvittää Fibonacci-sarjan numeroiden neliösumman, voimme tehdä sen seuraavalla tavalla yhdistämällä generaattoritoimintojen lähtö yhteen.
def fibonacci_numbers(nums): x, y = 0, 1 for _ in range(nums): x, y = y, x+y yield x def square(nums): for num in nums: yield num**2 print(sum(square(fibonacci_numbers(10))))
Tuotos
4895
Tämä putkilinja on tehokas ja helppo lukea (ja kyllä, paljon viileämpi!).