Python ienesīgums, ģeneratori un ģeneratoru izteiksmes

Šajā apmācībā jūs uzzināsiet, kā viegli izveidot atkārtojumus, izmantojot Python ģeneratorus, kā tas atšķiras no iteratoriem un parastajām funkcijām, un kāpēc tas jāizmanto.

Video: Python ģeneratori

Ģeneratori Python

Ir daudz darba, lai izveidotu iteratoru Python. Mums ir jāievieš klase ar metodi __iter__()un __next__()metode, jāseko iekšējiem stāvokļiem un jāpaaugstina, StopIterationkad nav vērtību, kuras jāatdod.

Tas ir gan ilgstoši, gan pretrunīgi. Šādās situācijās ģenerālis nāk talkā.

Python ģeneratori ir vienkāršs veids, kā izveidot iteratorus. Visus iepriekš minētos darbus automātiski veic Python ģeneratori.

Vienkārši sakot, ģenerators ir funkcija, kas atgriež objektu (iteratoru), kuru mēs varam atkārtot (pa vienai vērtībai).

Izveidojiet ģeneratorus Python

Izveidot ģeneratoru Python ir diezgan vienkārši. Tas ir tikpat viegli, kā definēt normālu funkciju, bet ar yieldpaziņojumu, nevis returnpaziņojumu.

Ja funkcija satur vismaz vienu yieldpriekšrakstu (tajā var būt citi yieldvai returnpriekšraksti), tā kļūst par ģeneratora funkciju. Abi yieldun returnatgriezīs kādu vērtību no funkcijas.

Atšķirība ir tāda, ka, kamēr returnpaziņojums pilnībā izbeidz funkciju, yieldpaziņojums pauzē funkciju, saglabājot visus stāvokļus, un vēlāk turpinās no turienes pēc kārtas.

Atšķirības starp ģeneratora funkciju un normālu funkciju

Lūk, kā ģeneratora funkcija atšķiras no parastās funkcijas.

  • Ģeneratora funkcija satur vienu vai vairākus yieldpaziņojumus.
  • Kad tiek izsaukts, tas atgriež objektu (iteratoru), bet izpildi nesāk nekavējoties.
  • Metodes patīk __iter__()un __next__()tiek ieviestas automātiski. Tāpēc mēs varam atkārtot vienumus, izmantojot next().
  • Tiklīdz funkcija ir derīga, funkcija tiek apturēta un vadība tiek pārsūtīta zvanītājam.
  • Vietējie mainīgie un to stāvokļi tiek atcerēti starp secīgiem zvaniem.
  • Visbeidzot, kad funkcija tiek izbeigta, tā StopIterationtiek automātiski paaugstināta nākamajos zvanos.

Šeit ir piemērs, lai ilustrētu visus iepriekš minētos punktus. Mums ir ģeneratora funkcija, kas nosaukta my_gen()ar vairākiem yieldapgalvojumiem.

 # 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

Interaktīvs palaist tulks ir dots zemāk. Palaidiet tos Python čaulā, lai redzētu izvadi.

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

Viena interesanta lieta, kas jāņem vērā iepriekš minētajā piemērā, ir tā, ka mainīgā n vērtība tiek atcerēta starp katru zvanu.

Atšķirībā no parastajām funkcijām, vietējie mainīgie netiek iznīcināti, kad funkcija iegūst. Turklāt ģeneratora objektu var atkārtot tikai vienu reizi.

Lai restartētu procesu, mums jāizveido vēl viens ģeneratora objekts, izmantojot kaut ko līdzīgu a = my_gen().

Visbeidzot jāatzīmē, ka mēs varam tieši izmantot ģeneratorus cilpām.

Tas ir tāpēc, ka forcilpa aizņem iteratoru un iterē pār to, izmantojot next()funkciju. Tas automātiski beidzas, kad StopIterationtiek pacelts. Pārbaudiet šeit, lai uzzinātu, kā forth cilne faktiski tiek ieviesta 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)

Palaidot programmu, izeja būs:

 Tas tiek izdrukāts vispirms 1 Šis tiek drukāts otrais 2 Tas tiek izdrukāts beidzot 3

Python ģeneratori ar cilpu

Iepriekš minētais piemērs ir mazāk lietojams, un mēs to izpētījām tikai, lai iegūtu priekšstatu par fonā notiekošo.

Parasti ģeneratora funkcijas tiek realizētas ar cilpu, kurai ir piemērots beigu nosacījums.

Ņemsim piemēru par ģeneratoru, kas apgriež virkni.

 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)

Rezultāts

 olleh

Šajā piemērā mēs izmantojām range()funkciju, lai iegūtu indeksu apgrieztā secībā, izmantojot for ciklu.

Piezīme : Šī ģeneratora funkcija darbojas ne tikai ar virknēm, bet arī ar citiem iterable veidiem, piemēram, sarakstu, kopu utt.

Python ģeneratora izteiksme

Vienkāršus ģeneratorus var viegli izveidot uzreiz, izmantojot ģeneratoru izteiksmes. Tas atvieglo ģeneratoru celtniecību.

Līdzīgi lambda funkcijām, kas rada anonīmas funkcijas, ģeneratora izteiksmes rada anonīmas ģeneratora funkcijas.

Ģeneratora izteiksmes sintakse ir līdzīga saraksta izpratnei Python. Bet kvadrātiekavas tiek aizstātas ar apaļām iekavām.

Galvenā atšķirība starp saraksta izpratni un ģeneratora izteiksmi ir tāda, ka saraksta izpratne rada visu sarakstu, savukārt ģeneratora izteiksme vienlaikus rada vienu vienumu.

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

Ģeneratori ir izcili nesēji, lai attēlotu bezgalīgu datu plūsmu. Bezgalīgas straumes nevar saglabāt atmiņā, un, tā kā ģeneratori vienlaikus ražo tikai vienu vienumu, tie var attēlot bezgalīgu datu plūsmu.

Šī ģeneratora funkcija var ģenerēt visus pāra skaitļus (vismaz teorētiski).

 def all_even(): n = 0 while True: yield n n += 2

4. Cauruļvadu ģeneratori

Vairāku ģeneratoru var izmantot virknei darbību. To vislabāk var ilustrēt, izmantojot piemēru.

Pieņemsim, ka mums ir ģenerators, kas ražo skaitļus Fibonači sērijās. Un mums ir vēl viens ģenerators skaitļu kvadrātā.

Ja mēs vēlamies uzzināt Fibonači sērijas skaitļu kvadrātu summu, mēs to varam izdarīt šādi, apvienojot ģeneratora funkciju izvadi.

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

Rezultāts

 4895

Šis cauruļvads ir efektīvs un viegli nolasāms (un jā, daudz foršāk!).

Interesanti raksti...