Sunteți pe pagina 1din 27

Lucrarea 3.

Programare funcțională și funcții în Python


În această lucrare veţi învăţa:

 Cum să definiți funcțiile în Python


 Cum să utilizați variabilele de tip funcție și dicționarele de

? funcții


Cum să utilizați funcțiile argument și cele anonime
Cum să utilizați iteratorii, generatorii și expresiile generatoare

Funcții, variabile de tip funcție, dicționare de funcții, funcții-argument,


Cuvinte cheie: funcții anonime, iteratori, generatori, expresii 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

3.1 Moduri de definire a funcțiilor


3.1.1 Moduri de definire a funcţiilor

Forma generală a unei funcții:


def f(x,y,z):
instrucțiuni
zreturn rezultat

Dacă unele argumente nu sunt obligatorii, li se poate asocia o valoare implicită:


def f(x,y=1,z=None)
(x va fi argument obligatoriu, y şi z vor fi opţionali iar dacă lipsesc vor primi valorile indicate)

Funcţiile Python pot avea număr variabil de parametri:


def f(*parametri):
Funcţia va primi oricâte argumente şi le va grupa într-un tuplu ce va putea fi parcurs în interiorul
funcţiei.
def f(**parametri):
Funcţia va primi oricâte argumente-pereche(de tip nume=valoare) şi le va grupa într-un
dicţionar ce va putea fi parcurs în interiorul funcţiei.

TOATE funcţiile returnează un rezultat. Dacă instrucţiunea return lipseşte, atunci funcţia
returnează valoarea None.

3.1.2 Moduri de apelare a funcţiilor

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.

2.Python permite și apel pe bază de nume:


f(x=v1,z=v3,y=v2)
… caz în care poziția parametrilor nu mai e obligatoriu să respecte poziția argumentelor din
definiția funcției.

3.Apelul prin colecții de argumente:


argumente=[v1,v2,v3] (sau alt tip de secvență)
f(*argumente)
- secvenţa de argumente este spartă în elemente, fiecare element devenind un argument, în
ordinea apariției (deci e echivalent cu f(v1,v2,v3,…))

4.Apelul prin dicționare de argumente:


dictionarargumente={'x':'v1','y':v2,'z':'v3',…}
f(**dictionarargumente)
3 Lucrarea 3. Programare funcțională și funcții în Python

- dicționarul este spart în elemente, fiecare devenind un argument-pereche de tipul x=v1,y=v2


etc. (vezi tipul 2 de apel), deci e echivalent cu f(x=v1,y=v2,z=v3,…) fără a conta ordinea argumentelor.

Pentru exemple, creăm modulul functii.py în Python27\Exemple, cu conţinutul:


def functieArgVariabile1(*x):
return x
def functieArgVariabile2(**x):
return x
def functieArgOptionale(x,y=5,z=10):
return x*y*z
- am definit 3 funcţii: prima acceptă un număr variabil de argumente şi le transformă în tuplu; a
doua primeşte un număr variabil de argumente-pereche şi le transformă în dicţionar; a treia acceptă 1-3
argumente şi face produsul lor

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

 dacă apar în apelul funcţiei, argumentul e spart în elementele componente;


 dacă apar în definiţia funcţiei, aceasta poate primi oricâte argumente şi le uneşte într-un tuplu
sau dicţionar; în interiorul funcţiei argumentul (unic) trebuie să fie tratat ca tuplu sau dicţionar.

În continuare discutăm o serie de particularităţi privind programarea funcţională:


 variabilele de tip funcţie, a căror valoare este corpul unei funcţii şi nu valoarea returnată de o
funcţie;
 dicţionarele de funcţii;
 funcțiile-argument, un tip aparte de variabile-funcţie, necesare atunci când o funcţie primeşte
ca argument o altă funcţie, de obicei pentru a o aplica asupra unei structuri de date şi a produce
o structură nouă;
 funcțiile anonime, de "unică folosinţă", care se definesc chiar în locul în care sunt apelate; ele
pot deveni funcţii normale dacă sunt atribuite unei variabile de tip funcție;
 iteratorii – structuri secvențiale de date de "unică folosinţă", pot fi parcurse o singură dată, apoi
sunt distruse;
 generatorii – funcții a căror execuţie poate fi întreruptă şi reluată oridecâte ori; aceste funcţii
returnează iteratori;
 expresiile generatoare – nu sunt neapărat elemente de programare funcţională, ci o facilitate
sintactică oferită de Python pentru crearea iteratorilor fără a defini funcții-generator.
5 Lucrarea 3. Programare funcțională și funcții în Python

3.2 Variabilele de tip funcţie


Salvăm în modulul functii.py (din Python27\Exemple) definiția:

def patrat1(x):
return x*x

În linia de comandă

>>> import functii


>>> a=functii.patrat1(10)
>>> a
100
- a e o variabilă normală ce a preluat rezultatul returnat de funcţie
>>> a=functii.patrat1
- lipsesc parantezele!
>>> a
<function patrat1 at 0x032C7830>
- în lipsa parantezelor, a DEVINE funcţie
>>> a(20)
400
- de aici încolo, a a devenit funcţie, preluând corpul şi modul de funcţionare de la patrat1()!!

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)

>>> b=functii.patrat2(4) def patrat2(x):


>>> b p=x*x
<function mesaj at 0x032E8630> def mesaj(y):
>>> b(10) print "patratul lui "+str(x)+" este
"+str(p)
patratul lui 4 este 16
return y+p
26
return mesaj
>>> c=functii.patrat2(20)(5)
patratul lui 20 este 400
>>> c
405
Două utilizări frecvente au variabilele de tip funcţie:
 dicţionarele de funcţii;
 funcţiile argument.
7 Lucrarea 3. Programare funcțională și funcții în Python

3.3 Dicţionarele de funcţii


Dicționarele de funcții pot fi folosite în loc de structuri Case. Adăugăm la modulul functii.py 3
funcţii:
def functia1():
return 's-a apelat functia1'
def functia2():
return 's-a apelat functia2'
def functia3():
return 's-a apelat functia3'

Apoi, la linia de comandă:


>>> reload(functii)
>>> 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'
- un dicţionar normal, de valori, valorile sale sunt valorile returnate de funcţii

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

Se observă că primul argument preluat de map/filter/reduce este o variabilă de tip funcție


(funcția-argument). Aceasta va fi executată asupra fiecărui element din al doilea argument (care trebuie
să fie o secvență – listă, tuplu, șir etc.). Altfel spus, fiecare element din secvența-argument devine
argument pentru funcția-argument.
9 Lucrarea 3. Programare funcțională și funcții î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:

Adăugăm la modulul funcții.py:


def a(x,y):
return [x,y]

Apoi în linia de comandă:


>>> reload(functii)
<module 'functii' from 'C:\Python27\Exemple\functii.py'>
>>> reduce(functii.a,[1,2,3,4,5,6])
[[[[[1, 2], 3], 4], 5], 6]
- *1,2+ sunt primele 2 elemente din secvență, **1,2+,3+ sunt prima valoare returnată de funcția-
argument și următorul element din secvență, etc.

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)

Apoi, în linia de comandă:


>>> import functii
(sau reload(functii) dacă e importat deja)
>>> lista=[1,2,3,4]
>>> tuplu=(10,20,30,40)
>>> dictionar={'x':100,'y':200,'z':300}
>>> map(functii.f1,lista,tuplu,dictionar.values())
[(1, 10, 200), (2, 20, 100), (3, 30, 300), (4, 40, None)]
- s-a luat primul element din FIECARE secvență și împreună s-au transformat în tuplu prin
funcția-argument f1, apoi al doilea element din FIECARE secvență, etc. (obs: rezultatul seamănă cu cel al
funcției zip, dar zip nu va returna ultimul tuplu datorită valorii None – zip se oprește odată cu cea mai
scurtă dintre secvențe, în acest caz dicționarul)
>>> reduce(functii.suma,lista,100)
110
- s-a calculat suma elementelor din listă, aplicată PESTE valoarea inițială 100; primul apel al
funcției-argument s-a aplicat pe perechea (100,1) în loc de (1,2) cum au fost cazurile precedente

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

Revenim la linia de comandă


>>> import functii
(sau reload(functii) dacă era importat deja)
>>> lista=['abc','xx','zzaa']
>>> sorted(lista,key=functii.ultimul)
['zzaa', 'abc', 'xx']
- sortare după ultimul character
>>> sorted(lista,key=functii.aldoilea)
['abc', 'xx', 'zzaa']
- sortare după al doilea character
>>> sorted(lista,key=len)
['xx', 'abc', 'zzaa']
- sortare după lungimea stringului
>>> listatuple=[(1,2),(4,3,1,0),(5,2,1)]
>>> sorted(listatuple,key=functii.ultimul)
[(4, 3, 1, 0), (5, 2, 1), (1, 2)]
>>> sorted(listatuple,key=functii.aldoilea)
[(1, 2), (5, 2, 1), (4, 3, 1, 0)]
>>> sorted(listatuple,key=len)
[(1, 2), (5, 2, 1), (4, 3, 1, 0)]
- aceleaşi criterii aplicate pe o listă de tuple

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:

Presupunem că dorim să obţinem o listă de studenţi şi note după următoarele criterii:


 lista să fie ordonată descrescător după note (criteriu principal);
 toţi studenţii care au aceeaşi notă să fie ordonaţi crescător după nume (criteriu secundar)

11 Lucrarea 3. Programare funcțională și funcții în Python

>>> note={'Ana':9, 'Maria':5, 'Ion':10, 'Delia':10, 'Andrei':5}


>>> listanote=note.items()
- s-a convertit dictionarul în listă de tuple, pt. a face posibile sortările
>>> listanote
[('Delia', 10), ('Ion', 10), ('Andrei', 5), ('Ana', 9), ('Maria', 5)]
- aceasta e ordinea arbitrară în care au fost accesate datele din dicţionar – poate diferi de la un
calculator la altul!
>>> listanotedupanume=sorted(listanote)
>>> listanotedupanume
[('Ana', 9), ('Andrei', 5), ('Delia', 10), ('Ion', 10), ('Maria', 5)]
- s-a obţinut lista sortată după nume
>>> listanotedupanote=sorted(listanotedupanume,key=functii.aldoilea,reverse=True)
>>> listanotedupanote
[('Delia', 10), ('Ion', 10), ('Ana', 9), ('Andrei', 5), ('Maria', 5)]
- s-a obţinut lista sortată după note(descrescător)+nume; a se observa că a doua sortarea s-a
aplicat asupra rezultatului PRIMEI sortări şi nu asupra listei originale!

Î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

3.5 Funcții anonime (lambda)


Funcțiile anonime se declară cu cuvîntul cheie lambda. Acestea sunt:
 funcţii fără nume;
 funcţii cu o singură linie (asigură doar returnarea valorii unei expresii);
 funcţii care nu necesită apelarea lor din mai multe locuri (de aceea nu au nevoie de nume);
 funcţii care sunt definite chiar în locul în care sunt apelate (fiind apelate dintr-un singur loc
aceasta nu e o problemă);
 funcţii "de unică folosinţă" (sunt create, executate şi distruse după execuţie); ele pot deveni
funcţii normale dacă sunt atribuite unor variabile de tip funcţie.
Desigur, rolul principal al unei funcţii e să fie reutilizată, deci rostul funcţiilor anonime e
discutabil. Ele sunt însă utile (mai degrabă dpdv sintactic) pentru:
 a implementa mai eficient funcţii ce sunt apelate într-un singur loc;
 a defini funcţii-argument, funcţii returnate de funcţii sau dicţionare de funcţii chiar în locul în
care trebuie apelate, fără a le mai salva separat în prealabil într-un modul.

3.5.1 Returnarea de funcţii lambda


În secţiunea privind variabilele de tip funcţie, am creat în modulul functii.py o funcţie ce returna
altă funcţie:

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

Se observă prescurtarea semnificativă a codului sursă:


s-a folosit lambda în loc de numele funcţiei;
definiţia lambda a putut fi trecută direct în return;
definiţia lambda NU mai trebuie să conţină un return, expresia conținută e calculată şi returnată;
elementul din faţa celor două puncte e argumentul;
 o limitare importantă este faptul că funcţiile anonime nu pot conţine mai multe linii de cod; ele
conţin după caracterul : o singură expresie a cărei valoare va fi returnată; de aceea instrucțiunea
print a trebuit scoasă în faţa funcţiei lambda, făcând acum parte din funcţia-părinte.
Funcţionarea e similară cu exemplul prezentat la variabilele de tip funcţie, cu mici diferenţe date
de mutarea lui print în funcţia părinte:

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

3.5.2 Funcţii argument de tip lambda


În exemplele precedente am văzut două situaţii cu funcţii-argument:
 funcţia criterială din parametrul key (de la sortare);
 funcţiile folosite drept argument în map, filter sau reduce.
Până acum, toate aceste funcţii au fost stocate separat, într-un modul, apoi modulul a fost
importat pentru a le putea apela. Putem evita lucrul cu modulul dacă definim funcţiile-argument direct
în argumentul care le apelează. Trebuie însă să ţinem cont de limitarea conform căreia funcţiile lambda
pot avea o singură linie de cod, cea care asigură returnarea.
Reamintim că în capitolul legat de funcţii-argument am definit în modulul functii.py
următoarele:

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

Acestea pot fi evitate dacă realizăm sortarea astfel:

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

3.5.3 Dicţionare de funcţii lambda


În secţiunea dedicată dicţionarelor de funcţii am definit 3 funcţii:
def functia1():
return 's-a apelat functia1'
def functia2():
return 's-a apelat functia2'
def functia3():
return 's-a apelat functia3'

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.5.4 Transformarea unei funcţii lambda într-o funcţie normală:


>>> a=lambda x:x+x
>>> a
<function <lambda> at 0x032E8630>
>>> a(10)
20
O funcţie lambda se poate atribui unei variabile care, astfel, DEVINE astfel funcţie, conform
mecanismului anterior discutat.
15 Lucrarea 3. Programare funcțională și funcții în Python

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

Ce rost au iteratorii dacă pot fi parcurși o singură dată?


1.Majoritatea structurilor secvențiale (liste, tuple, stringuri) au la bază astfel de iteratori, extinși
cu funcții care fac posibile și alte operații decât trecerea la elementul următor (deci iteratorul e noțiunea
fundamentală pe care se bazează structurile iterative);
2.În unele cazuri nu are rost să se rezerve spațiu în memorie pentru o structură secvențială care
e parcursă o singură dată: orice ciclu de tipul for i=0 to n (for i in range(n+1) în Python) de fapt parcurge
un iterator (cu numerele de la 0 la n), după care îl distruge. Ar fi inutil să se creeze o variabilă ce
stochează numerele de la 0 la n. În mod frecvent iteratorii sunt creați ca teren de lucru pentru un singur
ciclu For, în scopul utilizării mai eficiente a memoriei.
3.Programatorii au posibilitatea de a-și crea proprii iteratori, sub forma unei clase care are (în
afară de constructor) doar două metode: next() și __iter__(). Prima metodă trebuie să implementeze
modul în care se dorește să se obțină următorul element, a doua trebuie să conțină return self (să
returneze iteratorul).
4. Iteratorii, spre deosebire de liste, tuple etc. pot fi infiniți fără a afecta funcționarea
programelor. De fapt, un iterator stochează în memorie DOAR elementul curent și funcția next() prin
care își calculează (în timpul parcurgerii) următorul element, proces care poate fi rulat la infinit fără să
invadeze memoria internă. Este deci o soluţie mult mai eficientă pentru a stoca structuri de date "de
unică folosinţă".

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.

Construim modulul generatori.py salvat în C:\Python27\Exemple, cu conținutul:


def genereazaNumere(n):
for contor in range(n):
yield contor

Spre deosebire de funcțiile obișnuite:


 generatorul conține un ciclu (aici nu e infinit, ci limitat de argument);
 ciclul FOR conține un punct de întrerupere indicat prin yield;
 tot yield indică și valoarea returnată în momentul întreruperii (această valoare va fi adăugată la
iterator);
 execuția generatorului e reluată în momentul în care programul principal accesează cea mai
recentă valoare returnată cu yield;
 return POATE să apară într-un generator, dar nu pentru a returna valoare, ci pentru a OPRI
definitiv execuția;
 în această manieră, de câte ori execuția ajunge la yield, ciclul FOR se întrerupe, returnează
valoarea, așteaptă ca aceasta să fie accesată, după care se reia;
 toate valorile returnate alcătuiesc un iterator, deci accesare se face fie prin next(), fie printr-o
parcurgere cu FOR din programul principal (care apelează next() implicit la fiecare pas).
Cum se poate exploata generatorul de mai sus?

>>> import generatori


>>> secventa=generatori.genereazaNumere(10)
>>> secventa
<generator object genereazaNumere at 0x0309EE90>
- se observă că funcția nu a returnat o valoare sau o listă direct afișabilă
>>> [x for x in secventa]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [x for x in secventa]
[]
- se observă că secvența nu poate fi parcursă de 2 ori, a doua oară returnează valoare vidă
>>> secventa=generatori.genereazaNumere(5)
- pentru reparcurgere s-a reapelat generatorul, de data asta cu un alt argument; asta face posibil
ca generatorul să returneze iteratori de lungimi diferite și convenabile la fiecare apel!
>>> secventa.next()
0
>>> secventa.next()
1
>>> secventa.next()
Generatorii 18

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

Observație: prin analogie cu funcțiile obișnuite, există tentația de a se considera că generatorul


a fost apelat în linia secventa=generatori.genereazaNumere(5). Fals!
Mecanismul real e următorul:
 prin această atribuire, s-a stabilit că variabila secventa VA prelua pe rând valorile returnate de
generator, ATUNCI când va fi parcursă de un ciclu FOR (de exemplu, în cadrul listei generate cu *x
for x …+;
 apelul efectiv al funcției-generator și returnarea PRIMEI valori are loc abia la execuția primului
next() (care în cazul parcurgerii cu FOR e implicit la fiecare pas);
 la PRIMUL apel al generatorului, contorul ia valoarea 0, apoi yield îl returnează și întrerupe
temporar ciclul (din generator) până la următorul next() (sau următorul pas al unui ciclu FOR din
programul principal);
 valoarea returnată e stocată în variabila secventa;
 la AL DOILEA next() reia execuția generatorului de unde a rămas (de la yield) continuă cu mărirea
contorului și returnează A DOUA VALOARE, întrerupând din nou ciclul;
 valoarea returnată O ÎNLOCUIEȘTE pe cea existentă în variabila secventa;
 în această manieră fiecare next() reia execuția generatorului, iar fiecare yield o întrerupe
returnând valoarea la care s-a ajuns.
 Practic generatorul e o funcție capabilă să-și întrerupă execuția de oricâte ori (returnând o
valoare la fiecare întrerupere), lucru imposibil cu o funcție obișnuită! Dacă întreruperile sunt incluse într-
un ciclu INFINIT, generatorul poate returna un șir teoretic infinit de valori. Practic, va returna atâtea
valori câte apeluri next() au loc asupra sa (iar dacă e accesat prin FOR, va returna atâtea valori câți pași
are ciclul FOR)!

Ce se întâmpla dacă în loc de generator foloseam o funcție normală?


Adăugăm la modulul generatori o funcție cu același cod dar cu return în loc de yield:

def functieGenNum(n):
for contor in range(n):
return contor

Apoi, în linia de comandă reîncărcăm modulul:


>>> reload(generatori)
<module 'generatori' from 'C:\Python27\Exemple\generatori.py'>
>>> secventa=generatori.functieGenNum(10)
>>> secventa
0
>>> secventa=generatori.functieGenNum(5)
>>> secventa
0

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

Apoi, în linia de comandă:


>>> reload(generatori)
<module 'generatori' from 'C:\Python27\Exemple\generatori.py'>
>>> secventa=generatori.functieGenNum(10)
>>> secventa
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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)

Valorile returnate formează


În linia de comandă: un iterator cu 5 elemente.

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

O posibilitate recent introdusă în Python e injectarea de valori în timpul unei întreruperi. La


oricare din instrucțiunile yield se poate injecta o valoare nouă în locul celei pe care a returnat-o yield. În
figura următoare, săgețile sugerează ordinea în care are loc execuția:
 în programul principal se avansează până la primul next() care transferă execuția în generator;
 generatorul se execută până la primul yield;
 controlul revine programului principal, împreună cu valoarea returnată de yield;
 la un moment dat, în generator se întâlnește expresia v=(yield b); prin aceasta, pe lângă
întrerupere și trimiterea VALORII b spre programul principal, are loc și crearea variabilei v, ce
așteaptă să primească valoare DIN PROGRAMUL PRINCIPAL;
o programul principal TREBUIE să apeleze continuarea generatorului cu send(x) în loc de
next(); valoarea x va fi astfel injectată în variabila v din generator (prin săgeata
îngroșată);
o de la acest moment generatorul poate folosi variabila v, cu valoare injectată (săgeata
punctată).
21 Lucrarea 3. Programare funcțională și funcții în Python

Program principal Generator:


iterator=gen() def gen():
iterator.next() …………….
…………………… yield a
iterator.next() ……………
…………………… ……………
iterator.send(x) v=(yield
b)
…………...

Observație: send() trebuie să se SINCRONIZEZE cu v=(yield B). Apariția …………….


lui yield ÎNTR-O ATRIBUIRE creează
imediat o variabilă (v) care așteaptă o valoare; dacă programul principal NU o oferă prin send (ci
yield valoarea
apelează un next), nu se injectează nimic și variabila în așteptare primește c None.

Pentru exemplificare, modificăm funcția genereazaDiverse din modulul generatori:

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 generator se include testul IF:


def genereazaDiverse(x):
yield x
tuplu=(x,2*x)
rezerva=x
x=(yield tuplu)
if x==None:
x=rezerva
sir=str(x)
yield sir
dictionar={sir:x,x:sir}
yield dictionar
x=x+1
yield x

Î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

Reluăm situația pentru un ciclu infinit:


Se modifică în modul, în generatori.py, generatorul:

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

3.8 Expresii generatoare


Python adaugă la aceste tehnici preluate din programarea funcțională noțiunea de expresii
generatoarecare combină generatorii(în sensul că returnează iteratori) cu listele generate (în sensul că
folosesc o sintaxă similară). Spre deosebire de listele generate, expresiile generatoare se exprimă între
paranteze rotunde și valoarea lor fie se atribuie unui iterator (primul exemplu), fie se parcurge direct
într-un ciclu FOR (al doilea exemplu):

>>> iterator=(x for x in range(5))


>>> for i in iterator:
2*i

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ță):

>>> for i in ((x,2*x) for x in range(5)):


i
(0, 0)
(1, 2)
(2, 4)
(3, 6)
(4, 8)
>>> [i for i in ((x,2*x) for x in range(5))]
[(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)]
>>>
- s-au boldat expresiile generatoare al căror rezultat n-a mai fost stocat într-o variabilă; adesea
se practică această sintaxă deoarece nu are sens să se stocheze iteratorul într-o variabilă al cărei
conținut se distruge imediat
- se observă că aceeași expresie generatoare e folosită de 2 ori; totuși, nu e vorba de un iterator
PARCURS de 2 ori, ci CREAT de 2 ori; ca şi la funcţiile-generator, fiecare apariție a unei expresii
generatoare are ca efect CREAREA iteratorului

Diferența între a parcurge o secvență de date ca un iterator (rezultat al unei expresii


generatoare):
for i in ((x,2*x) for x in range(5))
sau ca o listă (rezultat al unei liste generate):
for i in [(x,2*x) for x in range(5)]
constă în:
Expresii generatoare 26

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

Practic, o expresie generatoare funcţionează ca o funcţie generator, doar că în loc să se mai


definească o funcţie se foloseşte sintaxa concisă a listelor generate. Aşa cum funcţiile lambda ne scutesc
uneori de a defini explicit o funcţie, expresiile generatoare ne scutesc de a define explicit un generator.
27 Lucrarea 3. Programare funcțională și funcții în Python

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.

S-ar putea să vă placă și