Šioje pamokoje sužinosite, kaip lengvai sukurti iteracijas naudojant „Python“ generatorius, kuo ji skiriasi nuo iteratorių ir įprastų funkcijų, ir kodėl turėtumėte ją naudoti.
Vaizdo įrašas: „Python“ generatoriai
Generatoriai „Python“
Kuriant iteratorių „Python“ yra daug darbo. Turime įdiegti klasę __iter__()
ir __next__()
metodą, sekti vidines būsenas ir kelti, StopIteration
kai nėra grąžintinų verčių.
Tai yra ir ilga, ir priešinga. Tokiose situacijose į pagalbą ateina generatorius.
„Python“ generatoriai yra paprastas iteratorių kūrimo būdas. Visą aukščiau paminėtą darbą automatiškai atlieka „Python“ generatoriai.
Paprasčiau tariant, generatorius yra funkcija, kuri grąžina objektą (iteratorių), kurį galime kartoti (po vieną vertę).
Sukurkite „Python“ generatorius
Sukurti „Python“ generatorių yra gana paprasta. Tai taip pat lengva, kaip apibrėžti normalią funkciją, bet su yield
sakiniu, o ne return
teiginiu.
Jei funkcijoje yra bent vienas yield
sakinys (joje gali būti ir kitų yield
ar return
sakinių), ji tampa generatoriaus funkcija. Ir tai, yield
ir return
funkcija grąžins tam tikrą vertę.
Skirtumas yra tas, kad nors return
sakinys visiškai nutraukia funkciją, yield
teiginys pristabdo funkciją, išsaugodamas visas būsenas, o vėliau tęsia iš eilės paskambindamas.
Skirtumai tarp generatoriaus funkcijos ir normalios funkcijos
Štai kaip generatoriaus funkcija skiriasi nuo įprastos funkcijos.
- Generatoriaus funkcijoje yra vienas ar keli
yield
teiginiai. - Kai iškviečiamas, jis grąžina objektą (iteratorių), bet ne iškart pradeda vykdyti.
- Metodai patinka
__iter__()
ir__next__()
yra įgyvendinami automatiškai. Taigi galime kartoti elementus naudodaminext()
. - Funkcijai pasibaigus, funkcija pristabdoma ir valdymas perduodamas skambinančiajam.
- Vietiniai kintamieji ir jų būsenos įsimenami tarp vienas po kito einančių skambučių.
- Galiausiai, kai funkcija pasibaigia,
StopIteration
automatiškai pakeliama skambinant toliau.
Čia yra pavyzdys, iliustruojantis visus aukščiau nurodytus dalykus. Mes turime generatoriaus funkciją, pavadintą my_gen()
keliais yield
teiginiais.
# 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
Toliau pateikiamas interaktyvus vertėjo darbas. Paleiskite juos „Python“ apvalkale, kad pamatytumėte išvestį.
>>> # 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
Vienas įdomus dalykas, kurį reikia atkreipti dėmesį į pirmiau pateiktą pavyzdį, yra tai, kad kintamojo n vertė atsimenama tarp kiekvieno skambučio.
Skirtingai nuo įprastų funkcijų, vietiniai kintamieji nesunaikinami, kai funkcija duoda. Be to, generatoriaus objektą galima kartoti tik vieną kartą.
Norėdami paleisti procesą iš naujo, turime sukurti kitą generatoriaus objektą naudodami kažką panašaus a = my_gen()
.
Paskutinis dalykas, kurį reikia atkreipti dėmesį į tai, kad mes galime tiesiogiai naudoti generatorius kilpoms.
Taip yra todėl, kad for
kilpa ima iteratorių ir iteruoja per ją naudodama next()
funkciją. Jis automatiškai baigiasi StopIteration
pakeltas. Patikrinkite čia, kad sužinotumėte, kaip „for loop“ iš tikrųjų įgyvendinama „Python“.
# 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)
Kai paleisite programą, išvestis bus:
Tai spausdinama pirmiausia 1 Tai spausdinama antra 2 Tai spausdinama pagaliau 3
„Python“ generatoriai su kilpa
Aukščiau pateiktas pavyzdys yra mažiau naudingas ir mes jį studijavome tik norėdami suprasti, kas vyksta fone.
Paprastai generatoriaus funkcijos įgyvendinamos kilpa, turinti tinkamą pabaigos sąlygą.
Paimkime generatoriaus, kuris pakeičia eilutę, pavyzdį.
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)
Rezultatas
olleh
Šiame pavyzdyje mes naudojome range()
funkciją, norėdami gauti indeksą atvirkštine tvarka naudodami „for“ kilpą.
Pastaba : Ši generatoriaus funkcija veikia ne tik su eilutėmis, bet ir su kitomis iterable rūšimis, tokiomis kaip sąrašas, paketas ir kt.
„Python“ generatoriaus išraiška
Paprastus generatorius galima lengvai sukurti skrendant naudojant generatoriaus išraiškas. Tai palengvina generatorių statybą.
Panašiai kaip „lambda“ funkcijos, kuriančios anonimines funkcijas, generatoriaus išraiškos sukuria anonimines generatoriaus funkcijas.
Generatoriaus išraiškos sintaksė yra panaši į sąrašo supratimo „Python“ sintaksę. Tačiau laužtiniai skliaustai pakeičiami apvaliais skliaustais.
Pagrindinis skirtumas tarp sąrašo supratimo ir generatoriaus išraiškos yra tas, kad supratimas apie sąrašą sukuria visą sąrašą, o generatorius - po vieną elementą.
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
Generatoriai yra puikios terpės, atspindinčios begalinį duomenų srautą. Begalinių srautų negalima laikyti atmintyje, o kadangi generatoriai vienu metu gamina tik vieną elementą, jie gali atstovauti begalinį duomenų srautą.
Ši generatoriaus funkcija gali generuoti visus lyginius skaičius (bent jau teoriškai).
def all_even(): n = 0 while True: yield n n += 2
4. Vamzdynų generatoriai
Keli generatoriai gali būti naudojami atliekant operacijų seriją. Tai geriausiai galima iliustruoti naudojant pavyzdį.
Tarkime, kad mes turime generatorių, kuris gamina „Fibonacci“ serijos skaičius. Ir mes turime dar vieną generatorių, skirtą skaičiams kvadratuoti.
Jei norime sužinoti „Fibonacci“ serijos skaičių kvadratų sumą, tai galime padaryti tokiu būdu, sujungdami generatoriaus funkcijų išvestį.
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))))
Rezultatas
4895
Šis vamzdynas yra efektyvus ir lengvai skaitomas (ir taip, daug vėsiau!).