Documente Academic
Documente Profesional
Documente Cultură
Lucrarea 3 Python
Lucrarea 3 Python
? funcții
Cum să utilizați funcțiile argument și cele anonime
Cum să utilizați iteratorii, generatorii și expresiile generatoare
Cuprins
Lucrarea 3. Programare funcțională și funcții în Python ..................................................................1
3.1 Moduri de definire a funcțiilor ..........................................................................................2
3.1.1 Moduri de definire a funcţiilor .....................................................................................2
3.1.2 Moduri de apelare a funcţiilor .....................................................................................2
3.2 Variabilele de tip funcţie ...................................................................................................5
3.3 Dicţionarele de funcţii .......................................................................................................7
3.4 Funcții-argument ...............................................................................................................8
3.5 Funcții anonime (lambda) .............................................................................................. 12
3.5.1 Returnarea de funcţii lambda ................................................................................... 12
3.5.2 Funcţii argument de tip lambda ................................................................................ 13
3.5.3 Dicţionare de funcţii lambda ..................................................................................... 14
3.5.4 Transformarea unei funcţii lambda într-o funcţie normală: ..................................... 14
3.6 Iteratorii ......................................................................................................................... 15
3.7 Generatorii ..................................................................................................................... 17
3.8 Expresii generatoare ...................................................................................................... 25
3.9 Bibliografie ..................................................................................................................... 27
Moduri de definire a funcțiilor 2
TOATE funcţiile returnează un rezultat. Dacă instrucţiunea return lipseşte, atunci funcţia
returnează valoarea None.
1. Apelul clasic:
f(v1,v2,v3)
Acesta e apelul clasic al funcțiilor, în care argumentele (v1,v2 etc.) se transferă spre parametri
(x,y,z …) pe bază de poziție.
La linia de comandă:
>>> import functii
>>> functii.functieArgOptionale(100)
5000
- funcţia a primit x=100, iar pentru y şi z a folosit valorile implicite
>>> functii.functieArgOptionale(100,2)
2000
- funcţia a primit 2 argumente, a folosit valoare implicită pentru z
>>> functii.functieArgOptionale(100,2,3)
600
- funcţia a primit toate argumentele, a ignorat valoarea implicită
>>> functii.functieArgOptionale(z=3,x=5)
75
- funcţia a primit argumente în ordine diferită de cea a parametrilor, lucru permis dacă apelul
precizează şi numele parametrilor; pentru y s-a folosit valoarea implicită
>>> lista=[9,99,999]
>>> dictionar={'z':5,'x':5,'y':5}
>>> functii.functieArgOptionale(*lista)
890109
- funcţia şi-a primit argumentele ca o listă, pe care a spart-o în cele 3 argumente aşteptate
>>> functii.functieArgOptionale(**dictionar)
125
- funcţia şi-a primit argumentele ca un dicţionar, pe care l-a spart în argumentele aşteptate; e
important de observat că dicţionarul trebuie să aibă chei identice cu numele parametrilor din funcţie, iar
ordinea lor poate să difere, identificarea făcându-se prin nume
>>> functii.functieArgVariabile1(7,6,4,2)
(7, 6, 4, 2)
>>> functii.functieArgVariabile1(7,6,4,'a','b','g',[1,2])
(7, 6, 4, 'a', 'b', 'g', [1, 2])
- funcţia poate primi oricâte argumente şi le grupează într-un tuplu, datorită receptării
argumentelor cu semnul *
>>> functii.functieArgVariabile2(x=7,y=6,z=4,w=2)
{'y': 6, 'x': 7, 'z': 4, 'w': 2}
>>> functii.functieArgVariabile2(x=7,y=6)
{'y': 6, 'x': 7}
- funcţia poate primi oricâte argumente cu nume şi le grupează într-un dicţionar, datorită
receptării argumentelor cu semnul **
A se observa modul diferit în care sunt interpretate asteriscurile, în funcţie de locul în care apar:
Moduri de definire a funcțiilor 4
def patrat1(x):
return x*x
În linia de comandă
Variabilele de tip funcţie fac posibile funcţiile care returnează funcţii. Adăugăm la functii.py:
def patrat2(x):
p=x*x
def mesaj(y):
print "patratul lui "+str(x)+" este "+str(p)
return y+p
return mesaj
Explicaţii:
funcţia mesaj() aparţine funcţiei patrat2() (e indentată spre interior);
funcţia patrat2() returnează funcţia mesaj(), NU o valoare; la rândul ei, funcţia mesaj() execută o
afişare cu print şi returnează o valoare;
funcţia mesaj() lucrează cu 2 argumente: x, primit de la funcţia-parinte (patrat2) şi y, ce va trebui
primit direct la apelarea funcţiei mesaj().
>>> reload(functii)
<module 'functii' from 'C:\Python27\Exemple\functii.py'>
>>> b=functii.patrat2(4)
>>> b
<function mesaj at 0x032E8630>
- de data aceasta parantezele sunt prezente şi totuşi b DEVINE funcţie, preluând corpul de la
mesaj(); aceasta din cauză că patrat2() returnează o funcţie la apelarea directă, cu paranteze şi
argument
>>> b(10)
patratul lui 4 este 16
26
- b a devenit funcţie, deci poate fi apelată! deoarece a preluat corpul funcţiei mesaj(), se execută
atât print (cu argumentul x sosit de la funcţia-părinte) cât şi return y+p (cu argumentul y sosit de la b)
Variabilele de tip funcţie 6
>>> c=functii.patrat2(20)(5)
patratul lui 20 este 400
>>> c
405
- acest exemplu reflectă mai bine posibilitatea de a transmite mai multe seturi de argumente
spre o funcţie-în-funcţie; prima linie execută printul şi returnează y+p atribuindu-l lui c
Obs: un apel de genul functii.patrat2(20,5) ar fi eşuat căci ar fi trimis ambele argumente la funcţia-
părinte (în x)
Pentru clarificări, urmăriți următoarea schemă (argumentele roşii merg la parametrul roşu, cele
albastre la parametrul albastru)
>>> dictionar={'a':functii.functia1,'b':functii.functia2,'c':functii.functia3}
>>> dictionar['a']()
's-a apelat functia1'
>>> dictionar['b']()
's-a apelat functia2'
>>> dictionar['c']()
's-a apelat functia3'
- de data aceasta avem un dicţionar de funcţii – lipsesc parantezele din dicţionar, dar sunt
folosite la apelarea elementelor dicţionarului, pentru a executa funcţiile
În concluzie, un dicţionar de funcţii este un tip particular de structură Case. Orice apel de forma:
dictionar[cheie]()
va apela funcţia ataşată cheii, funcţionând ca un Case de forma:
if cheie=='a':
functie1()
elif cheie=='b':
functie2()
elif cheie=='c':
functie3()
Funcții-argument 8
3.4 Funcții-argument
Salvăm în modulul functii.py (din Python27\Exemple) definițiile:
def dublare(x):
return x+x
def codpar(x):
return ord(str(x))%2==0
def suma(x,y):
return x+y
def produs(x,y):
return x*y
(prima funcție returnează dublul argumentului; dacă argumentul va fi numeric va avea loc
adunare, dacă va fi string, va avea loc concatenare)
(a doua funcție returnează True dacă codul ASCII al argumentului este par; argumentul este și
convertit în string pentru a evita erorile în caz că funcția primește numere)
(a treia funcție returnează suma argumentelor, sau concatenarea dacă acestea sunt stringuri)
(a patra funcție returnează produsul argumentelor, va da eroare pe stringuri)
În linia de comandă:
>>> import functii
(sau reload(functii),dacă a rămas importat de la exerciţiile anterioare)
>>> lista=[3,9,2]
>>> tuplu=(3,9,2)
>>> sir='392'
>>> map(functii.dublare,lista)
[6, 18, 4]
>>> map(functii.dublare,tuplu)
[6, 18, 4]
>>> map(functii.dublare,sir)
['33', '99', '22']
- map() transformă fiecare element din secvență, trecându-le prin funcția argument; în cazul de
față transformarea e dublarea elementului;map returnează o listă indiferent de caz
>>> filter(functii.codpar,lista)
[2]
>>> filter(functii.codpar,tuplu)
(2,)
>>> filter(functii.codpar,sir)
'2'
- filter selectează din secvență acele elemente pentru care funcția argument returnează True; în
cazul de față, elementele ale căror coduri ASCII sunt pare; filter returnează tuplu sau string dacă
secvența parcursă e de aceste tipuri, listă în orice alt caz
>>> reduce(functii.suma,lista)
14
>>> reduce(functii.suma,tuplu)
14
>>> reduce(functii.suma,sir)
'392'
- reduce() reduce întreaga secvență la o valoare unică, obținută prin aplicarea funcției-argument
pe toate elementele; în acest caz calculează suma, respectiv concatenarea, tuturor elementelor (în
general nu are sens să se creeze o funcție suma, deoarece există deja funcția sum() în Python)
În cazul particular al lui reduce(), funcția-argument are nevoie de 2 argumente (x,y). La început,
acestea vor fi primele 2 elemente din secvență, apoi vor fi valoarea returnată de funcția-argument și
următorul element din secvență. Acest fapt reiese dacă definim o funcție-argument care nu face decât
să-și returneze argumentele primite:
Completări:
map() poate primi mai multe secvențe, caz în care funcția-argument trebuie să aștepte un număr
de argumente egal cu numărul de secvențe;
reduce() poate primi un al treilea argument, considerată valoare inițială (astfel încât primul apel
al funcției-argument să nu se execute pe primele 2 elemente, ci pe valoarea inițială și primul
element).
Adăugăm la modulul functii.py:
def f1(x,y,z):
return (x,y,z)
Observaţie: Având în vedere calitatea de a transforma toate elementele unei liste, funcţia map() poate fi
folosită în mecanismul DSU, despre care s-a discutat la liste generate (secţiunea de sortare prin DSU).
În mod normal sortarea este crescătoare sau lexicografică (după primul caracter care diferă între
valorile sortate). Sunt cazuri în care dorim să ordonăm elementele după alte criterii (ex: sortarea unei
liste de obiecte după un atribut al acelor obiecte, sortarea alfabetică a unor stringuri după alt caracter
Funcții-argument 10
decât primul, sortarea unor stringuri după lungime, sortarea unor tuple după alt element decât primul,
sortarea unor numere după suma cifrelor etc.).
S-a arătat deja, la capitolul de generare a listelor, mecanismul DSU prin care se pot realiza astfel
de operaţii. O variantă mai facilă se bazează pe exploatarea funcţiilor-argument.
Pentru a realiza sortări după "criterii alternative", trebuie definită o funcţie care primeşte ca
argument fiecare element din listă şi returnează valoarea criteriulului de sortare. Pentru aceasta
adăugăm la modulul functii.py (în Python27\Exemple), definiţiile:
def ultimul(secventa):
return secventa[-1]
def aldoilea(secventa):
return secventa[1]
- am creat o funcţie care returnează ultimul element şi una care returnează al doilea element
dintr-o secvenţă de date – listă, tuplu, string – va funcţiona la toate deoarece toate acceptă accesare
prin indici; pentru a returna lungimea unei secvenţe există deja funcţia len().
Observație: Similar se pot crea liste de obiecte ce se pot sorta după atributele dorite ale obiectelor.
Funcţiile-criteriu vor avea forma
def functie(obiect)
return obiect.atribut
O proprietate importantă a funcţiei sorted() este că, la apariţia a două valori egale (pentru
criteriul ales), se păstrează ordinea originală între cele două. Aceasta permite sortarea după mai mult de
un criteriu:
În concluzie, ordonările multicriteriu se fac aplicând criteriile INVERS, de la cel mai puţin
important spre cel mai important. Se observă că mai întâi s-a făcut ordonarea după nume, abia apoi
după note. La ordonarea după note ne-am bazat pe faptul că elementele cu aceeaşi notă rămân
sortate conform ordinii precedente (care devine criteriul secundar), deci după nume. Această metodă
contravine oarecum abordării intuitive de la sortarea tabelelor – acolo se face întâi sortarea după
criteriul principal şi abia dacă se repetă valori se trece la aranjarea lor după criteriul secundar.
O altă cale de a aplica criterii multiple este unificarea tuturor criteriilor într-unul unic, returnat de
funcţia criterială indicată prin key. Unificarea tuturor criteriilor presupune concatenarea lor într-un
string al cărui ordonare normală să asigure ordinea dorită.
Funcții anonime (lambda) 12
def patrat2(x):
p=x*x
def mesaj(y):
print "patratul lui "+str(x)+" este "+str(p)
return y+p
return mesaj
Funcţia din interior nu trebuie neapărat să primească nume, acest nume nefiind apelat de
nicăieri altundeva. În locul ei se poate defini o funcţie anonimă:
def patrat2(x):
p=x*x
print "patratul lui "+str(x)+" este "+str(p)
return lambda y: y+p
>>> reload(functii)
<module 'functii' from 'C:\Python27\Exemple\functii.py'>
>>> b=functii.patrat2(4)
patratul lui 4 este 16
>>> b(10)
26
13 Lucrarea 3. Programare funcțională și funcții în Python
>>> c=functii.patrat2(20)(5)
patratul lui 20 este 400
>>> c
405
def dublare(x):
return x+x
def codpar(x):
return ord(str(x))%2==0
def suma(x,y):
return x+y
def produs(x,y):
return x*y
Aceste definiţii pot fi evitate ca mai jos, prin funcţii anonime trecute direct în argument:
>>> lista=[3,9,2]
>>> tuplu=(3,9,2)
>>> sir='392'
>>> map(lambda x:x+x,lista)
[6, 18, 4]
>>> map(lambda x:x+x,tuplu)
[6, 18, 4]
>>> map(lambda x:x+x,sir)
['33', '99', '22']
- desigur, având în vedere că am reutilizat funcţia în mai multe locuri, ar fi preferabilă stocarea
sa în modul pentru a fi reapelată de câte ori dorim; funcţiile lambda devin preferabile când sunt
executate într-un singur loc, altfel trebuie rescrise de fiecare dată
>>> filter(lambda x:ord(str(x))%2==0,lista)
[2]
>>> reduce(lambda x,y:x+y,lista)
14
- funcţia argument din reduce foloseşte 2 argumente!
Similar se pot folosi funcţii lambda la sortarea după criterii diferite de ordinea lexicografică. În
exemplele din capitolul privind sortarea după funcţii, am definit în modulul functii.py:
def ultimul(secventa):
return secventa[-1]
def aldoilea(secventa):
return secventa[1]
Funcții anonime (lambda) 14
>>> lista=['abc','xx','zzaa']
>>> sorted(lista,key=lambda x:x[-1])
['zzaa', 'abc', 'xx']
- sortare după ultimul character
>>> sorted(lista,key=lambda x:x[1])
['abc', 'xx', 'zzaa']
- sortare după al doilea character
Exploatarea funcţiilor lambda mai aduce un avantaj în cazul sortării: posibilitatea de a include în
criteriile de sortare informaţii din alte surse vizibile în momentul execuţiei:
>>> nume=['Ion','Maria','Andrei']
>>> note={'Ion':10,'Maria':2,'Andrei':5}
>>>sorted(nume,key=lambda x:note[x])
['Maria', 'Andrei', 'Ion']
Funcţia criterială foloseşte şi informaţia din dicţionarul note! Practic s-a sortat lista nume, în
funcţie de notele indivizilor, citite din dicţionarul note. O funcţie obişnuită nu ar fi putut face acest lucru,
deoarece nu ar avea acces direct la variabila note (ci numai la argumentul primit), în timp ce funcţia
lambda are. Desigur, tehnica poate fi folosită şi cu funcţii obişnuite, dacă realizăm eforturi suplimentare
de a asigura vizibilitatea variabilei note în modulul în care salvăm funcţia criterială (ar presupune să
stocăm şi variabila note într-un modul care să fie la rândul său importat de modulul funcţiilor).
Având în vedere că cele 3 funcţii nu au mai mult de o instrucţiune return, dicţionarul poate fi
creat fără a mai defini funcţiile în modul:
>>> dictionar={'a':lambda:'s-a apelat functia 1','b':lambda:'s-a apelat functia 2,'c':lambda:'s-a apelat functia3'}
>>> dictionar['a']()
's-a apelat functia 1'
Se observă că atunci când funcţiile lambda nu au argument, ele sunt formate doar din cuvântul
lambda şi valoarea de returnat, separate prin caracterul :.
3.6 Iteratorii
Iteratorii sunt structuri de date temporare ce pot fi parcurse O SINGURĂ dată, după care se
distrug. Parcurgerea poate fi făcută:
fie printr-un ciclu FOR,
fie pas cu pas prin apelarea funcției next() ce încarcă următorul element al iteratorului.
Un exemplu de iterator întâlnit deja sunt obiectele-fișier, care fac posibilă extragerea rândurilor
dintr-un fișier cu:
lista=[rand for rand in fisier]
sau
for rand in fisier:
comenzi aplicate pe fiecare rând
Iteratorii fișiere sunt extinși cu metoda seek() pentru a putea reparcurge același fișier de mai
multe ori fără a-l accesa în mod repetat de pe disc. Majoritatea iteratorilor NU au o astfel de metodă de
revenire, oferind doar posibilitatea parcurgerii înainte, deci unice. Alte exemple de iteratori pe care i-am
întâlnit în exerciţiile precedente au fost:
valoarea returnată de os.walk la parcurgerea arborilor de foldere (de aceea afişarea s-a făcut cu
*x for x in os.walk()+ şi nu direct cu os.walk());
valoarea returnată de codecs.iterencode şi codecs.iterdecode (de aceea apelam şi o funcţie list
sau dict pentru a pune rezultatul conversiei într-o structură reutilizabilă)
Evident, nu e obligatoriu ca iteratorii să fie folosiți dar trebuie cunoscuți deoarece unele funcții
returnează iteratori pe care programatorul decide dacă să-i convertească în liste, tuple etc. (dacă
obţineţi structuri de date care la a doua tentativă de parcurgere par a se fi golit, cel mai probabil e vorba
de un iterator!). Dacă se prevede că datele iteratorului vor trebui parcurse/accesate de mai multe ori,
atunci se transferă datele într-o structură de date persistentă precum lista:
lista=[x for x in iterator]
sau
lista=list(iterator)
Iteratorii 16
Iteratori se pot obține din majoritatea structurilor de date cu funcția iter(). Din dicționare,
această funcție extrage doar cheile. Se va folosi iteritems() pt a face un iterator din perechile
cheie:valoare sau itervalues() pentru a face un iterator din valori.
Iteratorii suportă în general majoritatea operațiilor valabile pentru liste și pot fi folosiți ca
argumente acolo unde se solicită o secvență de date (listă, tuplu etc.)
>>> lista=[1,2,3]
>>> iterator=iter(lista)
- s-a creat un iterator din listă
>>> x,y,z=iterator
>>> x
1
>>> y
2
>>> z
3
- s-au extras datele din iterator
>>> iterator.next()
- eroarea StopIterration semnifică faptul că unica parcurgere permisă pt iterator s-a finalizat;
dacă se dorește o reparcurgere, iteratorul trebuie creat din nou! reamintim că pentru iteratorii
construiți de noi la nivel de clasă, putem să le adăugăm metode în plus care să permită revenirea la un
element precedent, așa cum există funcția seek() la iteratorii obținuți din fișiere
17 Lucrarea 3. Programare funcțională și funcții în Python
3.7 Generatorii
Generatorii sunt:
funcții a căror execuție poate fi întreruptă și reluată (păstrându-se variabilele locale!); la fiecare
punct de întrerupere, generatorul poate returna o valoare!
mai mult, la fiecare întrerupere poate primi noi date, în plus faţă de argumentele apelului inițial!
toate valorile returnate de funcție (la punctele de întrerupere) formează un ITERATOR!
Una din principalele calități ale iteratorilor este că pot fi infiniți, de aceea generatorii sunt
adesea funcții ce returnează structuri de date infinite sau cu lungimea necunoscută inițial. Pentru a
produce un iterator infinit, funcția generator trebuie să conțină un ciclu infinit care să conțină punctul
de întrerupere (ce returnează valorile). Decizia de oprire a generării este luată de programator, prin
modul în care alege să parcurgă iteratorul returnat de generator.
2
>>> secventa.next()
3
>>> secventa.next()
4
>>> secventa.next()
- de data aceasta iteratorul a fost accesat element cu element, fiecare nou element distrugând
elementul precedent din memorie
def functieGenNum(n):
for contor in range(n):
return contor
Observație: Indiferent de argument, funcția nu depășește niciodată valoarea zero! Asta deoarece funcția
își OPREȘTE execuția la PRIMUL return (return 0) și nu o ÎNTRERUPE (cum face yield). La A DOUA apelare
a funcției, contorul, ciclul FOR și execuția funcției sunt resetate, returnând mereu zero.
19 Lucrarea 3. Programare funcțională și funcții în Python
Dacă dorim ca funcția obișnuită să returneze o secvență de valori, ea trebuie modificată astfel:
def functieGenNum(n):
return [i for i in range(n)]
- mai întâi se calculează lista, apoi e returnată integral
Acum s-a obținut o funcție obișnuită care generează liste de lungimea dată prin argument. După
cum s-a arătat deja, această listă e calculată integral la apelul funcției functieGenNum și e stocată
integral în memorie, în variabila secventa. Pe când în cazul generatorului, acesta își calcula și returna
valorile una câte una, atunci când era nevoie de ele (la apelarea lui next() sau la fiecare pas al unui ciclu
FOR), stocând în memorie DOAR elementul curent, cu caracter temporar (până la generarea următorului
element).
În continuare prezentăm un generator fără ciclu, cu punctele de întrerupere plasate unul după
altul:
def genereazaDiverse(x):
yield x
tuplu=(x,2*x)
Fiecare yield e un punct de
yield tuplu întrerupere și returnează o valoare.
sir=str(x)
yield sir
dictionar={sir:x,x:sir}
yield dictionar Fiecare yield așteaptă
x=x+1 apelul lui next pt. a continua
yield x execuția.
(adăugat la modulul generatori.py)
>>> reload(generatori)
<module 'generatori' from 'C:\Python27\Exemple\generatori.py'>
>>> s=generatori.genereazaDiverse(10)
>>> s.next()
10
- prima întrerupere cu yield
>>> s.next()
(10, 20)
- al doilea yield
>>> s.next()
'10'
- al treilea yield
>>> s.next()
{'10': 10, 10: '10'}
- al patrulea yield
Generatorii 20
>>> s.next()
11
- al cincilea yield
>>> s.next()
- eroare, nu mai sunt yielduri, iteratorul e finalizat)
Se poate deduce din acest exemplu că numărul de elemente din iteratorul produs e egal cu
numărul de yielduri executate. În consecință, pentru a obține un iterator infinit, e suficient să executăm
un număr infinit de yielduri. Soluția evidentă este includerea lui yield într-un ciclu infinit:
Se adaugă la modulul generatori.py:
def genereazaInfinit(x):
while True:
yield x
x=x+1
În linia de comandă:
>>> reload(generatori)
>>> secv=generatori.genereazaInfinit(10)
>>> valorigenerate=[secv.next() for i in range(20)]
>>> valorigenerate
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
- deși generatorul poate, datorită numărului infinit de yielduri, să producă un iterator infinit, numărul de
elemente produse a fost limitat la 20 cu ajutorul listei generate. Aici trebuie acordată atenție pentru a
nu lansa generatorul într-un proces infinit: *x for x in secv+ ar genera o listă infinită ce ar invada
memoria!
>>> valorigenerate2=[secv.next() for i in range(20)]
>>> valorigenerate2
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
- iteratorul fiind infinit, generarea de date poate continua!
>>> secv.next()
50
>>> secv.next()
51
- apelarea lui next() merge mai departe cu generarea! de aici încolo, orice combinație de folosire
a funcției next() sau a unor cicluri FOR va continua să genereze valori, eroarea StopIterration NU va
apare niciodată
def genereazaDiverse(x):
yield x
tuplu=(x,2*x)
x=(yield tuplu)
sir=str(x)
yield sir
dictionar={sir:x,x:sir}
yield dictionar
x=x+1
yield x
Linia boldată sugerează că în programul principal al 3-lea next va trebui înlocuit cu send, pentru
a injecta o valoare nouă în x.
>>> reload(generatori)
>>> it=generatori.genereazaDiverse(10)
>>> it.next()
10
- se execută generatorul PÂNĂ la yield x
>>> it.next()
- se execută generatorul PÂNĂ la yield tuplu; aici apare x=yield, deci x începe să aștepte o valoare!
(10, 20)
>>> it.send(3)
'3'
- se injectează valoarea 3 în x, apoi continuă generatorul până la yield sir
>>> it.next()
{'3': 3, 3: '3'}
- se execută generatorul până la yield dictionar
>>> it.next()
4
- se execută generatorul până la yield x
>>> it.next()
- eroare, generatorul s-a terminat, deci și iteratorul
Generatorii 22
Se observă cum send s-a sincronizat cu x=yield (regula: dacă al n-lea yield e atribuit unei
variabile, atunci al n+1-lea next trebuie înlocuit cu send!!!). Dacă sincronizarea nu are loc (și se
continuă cu next) variabila x primește valoarea None:
>>> it=generatori.genereazaDiverse(10)
>>> it.next()
10
- se execută generatorul PÂNĂ la yield x
>>> it.next()
- se execută generatorul PÂNĂ la yield tuplu; aici apare x=yield, deci x așteaptă o valoare!
(10, 20)
>>> it.next()
'None'
- nu s-a injectat nimic, apoi continuă generatorul până la yield sir
>>> it.next()
{'None': None, None: 'None'}
- se execută generatorul până la yield dicționar
>>> it.next()
- eroare, x=x+1 nu poate avea loc pentru x=None!!!
Pentru a trata situațiile în care programul principal nu injectează nimic, în generator trebuie să
existe un test IF asupra variabilei în așteptare. Dacă nu s-a injectat nimic, variabila trebuie să-și
recupereze valoarea precedentă (dacă avea una – nu e obligatoriu ca variabila în așteptare să fi existat
înainte, se poate crea una nouă). Cazul precedent se rezolvă astfel:
În liniile boldate, valoarea lui x înaintea convertirii sale în variabilă în așteptare e memorată ca
rezervă. După x=(yield), se testează dacă valoarea primită de la program este None. În caz afirmativ, x își
recuperează valoarea precedentă din rezervă. De data aceasta, accesarea valorilor din generator nu mai
este afectată de faptul că nu se răspunde variabilei în așteptare.
>>> reload(generatori)
<module 'generatori' from 'C:\Python27\Exemple\generatori.py'>
>>> it=generatori.genereazaDiverse(10)
>>> it.next()
10
>>> it.next()
(10, 20)
>>> it.next()
'10'
23 Lucrarea 3. Programare funcțională și funcții în Python
>>> it.next()
{'10': 10, 10: '10'}
>>> it.next()
11
def genereazaInfinit(x):
while True:
rezerva=x
x=(yield x)
if x==None:
x=rezerva
x=x+1
Se poate observa mecanismul de testare a valorii așteptate (cu recuperarea valorii precedente
dacă e cazul). Deoarece linia x=(yield) e inclusă în ciclul WHILE, înseamnă că generatorul așteaptă date la
FIECARE apelare (la fiecare yield). Deci, funcția send() va putea fi folosită ORICÂND în loc de next().
>>> reload(generatori)
<module 'generatori' from 'C:\Python27\Exemple\generatori.py'>
>>> it=generatori.genereazaInfinit(10)
>>> it.next()
10
>>> it.next()
11
- s-au generat 2 valori
>>> it.send(2)
3
- se injectează o valoare nouă, făcând ca șirul numerelor generate să se reseteze la 3
>>> it.next()
4
>>> it.next()
5
>>> it.next()
6
>>> it.send(-1)
0
- din nou se injectează o valoare nouă, resetând șirul numerelor generate la 0
>>> it.next()
1
>>> it.next()
2
>>> it.next()
3
>>> valorigenerate=[it.next() for i in range(10)]
>>> valorigenerate
[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
- se generează valori printr-un ciclu FOR
>>> it.send(2)
3
- se resetează generatorul la valoarea 3
>>> valorigenerate2=[it.next() for i in range(10)]
>>> valorigenerate2
[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
- se RE-generează aceleași valori; iată o metodă de a parcurge același ITERATOR mai mult de O
DATĂ!
Generatorii 24
Exemple de generatori uzual folosiţi în cicluri FOR sunt funcţiile reversed (returnează o structură
de date ordonată în ordine inversă) şi enumerate (ataşează la fiecare element al unei structuri de date
ordonate poziţia elementului):
>>> sir='xyz'
>>> [x for x in reversed(sir)]
['z', 'y', 'x']
- afişează caracterele în ordine inversă
>>> [x for x in reversed(range(10))]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
- afişează range(10) în ordine inversă, tehnică utilă în ciclurile FOR cu contor descrescător
>>> iterator=reversed(range(10))
>>> iterator
<listreverseiterator object at 0x033D9830>
- se observă că reversed returnează un iterator, nu o listă cum am fi tentaţi să credem
>>> iterator.next()
9
>>> iterator.next()
8
>>> iterator.next()
7
>>> [x for x in iterator]
[6, 5, 4, 3, 2, 1, 0]
>>> iterator.next()
- eroare! iterator finalizat; de obicei nu are sens să atribuim rezultatul returnat de reversed unei
variabile; reversed se foloseşte direct în ciclurile FOR pentru a modifica sensul de parcurgere a unei
structuri de date
>>> dictionar={'Ion':4,'Ana':9}
>>> [x for x in reversed(dictionar)]
- eroare, dicţionarul nu e o structură ordonată
>>> sir='xyz'
>>> [x for x in enumerate(sir)]
[(0, 'x'), (1, 'y'), (2, 'z')]
- enumerate asociază, prin tuple, fiecărui element poziţia sa
>>> [x for x in enumerate(sir)]
[(0, 'x'), (1, 'y'), (2, 'z')]
>>> iterator=enumerate(sir)
>>> [x for x in iterator]
[(0, 'x'), (1, 'y'), (2, 'z')]
>>> [x for x in iterator]
[]
- de ce în liniile de mai sus două parcurgeri ale variabilei iterator NU sunt posibile, dar două
parcurgeri ale rezultatului funcţiei enumerate() au fost posibile? pentru că fiecare apel al unei funcţii
generator RECREEAZĂ iteratorul!
>>> [x for x in enumerate(reversed(sir))]
[(0, 'z'), (1, 'y'), (2, 'x')]
25 Lucrarea 3. Programare funcțională și funcții în Python
0
2
4
6
8
>>> [2*x for x in iterator]
[]
>>> iterator.next()
- se observă că iteratorul s-a distrus, a doua parcurgere generează listă vidă, iar încercarea de a
apela funcția next() dă eroarea StopIteration
De obicei expresiile generatoare nu sunt atribuite unei variabile (nu are sens, având în vedere că
nu se pot parcurge decât o dată) ci oferă terenul de lucru pentru un ciclu FOR (deci pot fi considerate
liste generate de unică folosință):
- lista generată va calcula TOATE elementele listei și le va stoca în memorie, deci lista obținută
TREBUIE să fie finită; pentru a fi reutilizată, se va atribui unei variabile;
- expresia generatoare își va calcula elementele doar pe măsură ce e nevoie de ele, la fiecare pas
al ciclului FOR și le va distruge imediat ce sunt procesate pentru a face loc noului element; nu va putea fi
reutilizată, chiar dacă e atribuită unei variabile; expresiile generatoare sunt folosite pentru a genera
secvențe foarte mari, sau potențial infinite, în care condiția de oprire se exprimă printr-un IF (similar cu
funcțiile generator.
3.9 Bibliografie
D. M. Beazley, Python Essential Reference, Pearson Education, 2009
J. M. Zelle, Python programming: an introduction to computer science, Ed. Wilsonville: Franklin, Beedle,
2004
M.Lutz, Programming Python,O'Reilly, 2011.