Selles õpetuses saate teada, kuidas Pythoni generaatorite abil hõlpsasti iteratsioone luua, kuidas see erineb iteraatoritest ja tavalistest funktsioonidest ning miks peaksite seda kasutama.
Video: Pythoni generaatorid
Generaatorid Pythonis
Pythonis on palju tööd iteraatori ehitamisel. Peame rakendama klassi __iter__()
ja __next__()
meetodi, jälgima sisemisi olekuid ja tõstma, StopIteration
kui pole väärtusi, mida tagastada.
See on nii pikk kui ka vastupidine. Sellistes olukordades tuleb appi generaator.
Pythoni generaatorid on lihtne kordajate loomise viis. Kogu ülalnimetatud tööga tegelevad Pythoni generaatorid automaatselt.
Lihtsamalt öeldes on generaator funktsioon, mis tagastab objekti (iteraator), mille abil saame itereerida (üks väärtus korraga).
Generaatorite loomine Pythonis
Pythonis on generaatori loomine üsna lihtne. See on sama lihtne kui tavalise funktsiooni määratlemine, kuid yield
avalduse asemel return
lausega.
Kui funktsioon sisaldab vähemalt ühte yield
lauset (see võib sisaldada muud yield
või return
lauset), saab sellest generaatorfunktsioon. Mõlemad yield
ja return
tagastavad funktsioonilt mingi väärtuse.
Erinevus seisneb selles, et kui return
lause lõpetab funktsiooni täielikult, yield
peatab lause funktsiooni kõigi olekute salvestamise ja jätkab sealt edasi järjestikuste kõnedega.
Generaatori funktsiooni ja normaalfunktsiooni erinevused
Nii erineb generaatori funktsioon tavalisest funktsioonist.
- Generaatori funktsioon sisaldab ühte või mitut
yield
lauset. - Kui see kutsutakse, tagastab see objekti (iteraator), kuid ei alusta käivitamist kohe.
- Meetodid meeldivad
__iter__()
ja__next__()
rakendatakse automaatselt. Nii et saame üksuste abil itereeridanext()
. - Kui funktsioon annab järele, peatatakse funktsioon ja juhtimine antakse üle helistajale.
- Kohalikud muutujad ja nende olekud jäävad järjestikuste kõnede vahel meelde.
- Lõpuks, kui funktsioon lõpetatakse,
StopIteration
tõstetakse see edasiste kõnede korral automaatselt.
Siin on näide kõigi ülaltoodud punktide illustreerimiseks. Meil on generaatori funktsioon, mille nimi my_gen()
on mitu yield
lauset.
# 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
Interaktiivne jooksu tõlk on toodud allpool. Väljundi nägemiseks käivitage need Pythoni kestas.
>>> # 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
Üks huvitav asi, mida ülaltoodud näites tähele panna, on see, et muutuja n väärtus jääb iga kõne vahele meelde.
Erinevalt tavalistest funktsioonidest ei hävitata lokaalseid muutujaid funktsiooni andmisel. Lisaks saab generaatori objekti itereerida ainult üks kord.
Protsessi taaskäivitamiseks peame looma teise generaatori objekti, kasutades midagi sellist a = my_gen()
.
Viimane asi, mida tuleb märkida, on see, et generaatorit saab kasutada otse silmuste jaoks.
Selle põhjuseks on asjaolu, et for
silmus võtab iteraatori ja itereerib selle next()
funktsiooni abil. See lõpeb automaatselt, kui see StopIteration
on üles tõstetud. Siit saate teada, kuidas for loopi Pythonis tegelikult rakendatakse.
# 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)
Programmi käivitamisel on väljund järgmine:
See prinditakse kõigepealt 1 See trükitakse teine 2 See trükitakse lõpuks 3
Silmusega Pythoni generaatorid
Ülaltoodud näide on vähem kasutatav ja uurisime seda lihtsalt selleks, et saada aimu taustal toimuvast.
Tavaliselt viiakse generaatori funktsioonid ellu sobiva lõpptingimusega silmusega.
Võtame näite generaatorist, mis muudab stringi ümber.
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)
Väljund
olleh
Selles näites oleme range()
funktsiooni kasutanud indeksi saamiseks vastupidises järjekorras for loopi abil.
Märkus . See generaatori funktsioon ei tööta mitte ainult stringidega, vaid ka muud tüüpi iterable'idega, nagu loend, dupleks jne.
Pythoni generaatori avaldis
Lihtsaid generaatoreid saab hõlpsalt luua generaatorilausete abil. See muudab generaatorite ehitamise lihtsaks.
Sarnaselt lambda funktsioonidele, mis loovad anonüümseid funktsioone, loovad generaatori avaldised anonüümsed generaatori funktsioonid.
Generaatori avaldise süntaks sarnaneb Pythoni loendi mõistmise omaga. Kuid nurksulgudes asendatakse ümarad sulgud.
Peamine erinevus loendi mõistmise ja generaatori avaldise vahel seisneb selles, et loendi mõistmine loob kogu loendi, samal ajal kui generaatori avaldis toodab ühe üksuse korraga.
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
Generaatorid on suurepärased meediumid, et esindada lõpmatut andmevoogu. Lõpmatuid vooge ei saa mällu salvestada ja kuna generaatorid toodavad korraga ainult ühte üksust, võivad nad esindada lõpmatut andmevoogu.
Järgmine generaatori funktsioon võib genereerida kõik paarisarvud (vähemalt teoreetiliselt).
def all_even(): n = 0 while True: yield n n += 2
4. Torujuhtmete generaatorid
Mitme generaatori abil saab rida toiminguid juhtida. Seda saab kõige paremini illustreerida näite abil.
Oletame, et meil on generaator, mis toodab Fibonacci seeria numbreid. Ja meil on veel üks generaator numbrite ruutude tegemiseks.
Kui soovime välja selgitada Fibonacci seeria arvude ruutude summa, saame seda teha järgmisel viisil, ühendades generaatorfunktsioonide väljundid kokku.
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))))
Väljund
4895
See torustik on tõhus ja hõlpsasti loetav (ja jah, palju lahedam!).