Sunteți pe pagina 1din 421

Eugen Diaconescu

Editura Universității din Pitești


Adresa: Târgul din Vale nr.1, Pitești, Argeș, România - 110040
Telefon: +40.348.453.106 / +40.745.696.684
e-mail: sorin.fianu@upit.ro

Editura Universității din Pitești este acreditată de


CONSILIUL NAȚIONAL AL CERCETĂRII ȘTIINȚIFICE DIN ÎNVĂȚĂMÂNTUL
SUPERIOR

Referenți:
Dr. ing. Florentina Magda Enescu
Dr. ing. Rizea Vasile

DIACONESCU, EUGEN
Limbajul și ecosistemul de programare Python
Editura Universității din Pitești, 2022
e-ISBN: 978-606-560-754-5
Eugen Diaconescu

Limbajul și Ecosistemul de Programare


Python

Ghid – Manual – Îndrumar

Editura Universității din Pitesti – 2022


e-ISBN:978-606-560-754-5
1

CUPRINS
Introducere, 10
I. Ce este Python ?
II. Avantajele și dezavantajele limbajului Python
III. Utilizarea Python în proiectarea aplicațiilor
IV Instalarea Python
V Medii de dezvoltare pentru proiectele Python
V.1 Conceptul de mediu de dezvoltare
V.2 IDLE Python
V.3 Spyder
V.4 PyCharm
V.5 Visual Studio Code
V.6 Eclipse + PyDev
V.7 Pyzo
V.8 Sublime Text
V.9 Thony
V.10 Alte medii de dezvoltare IDE
VI Proiectul Jupyter
VII Interpretoare și compilatoare Python

1. Elemente introductive în limbajul Python, 28


1.1 Cuvintele cheie și identificatorii
1.1.1 Cuvintele cheie (keywords)
1.1.2 Identificatorii
1.2 Declarația, indentarea și comentariile
1.2.1 Declarația Python
1.2.2 Indentarea în Python
1.2.3 Comentariile
1.3 Variabilele, Constantele și Literalele
1.3.1 Variabilele Python
1.3.2 Constante
1.3.3 Reguli și convenții de denumire a variabilelor și constantelor
1.3.4 Literale
1.4 Tipuri de date în Python
1.4.1 Numerele în Python
1.4.2 Tipul boolean
1.4.3 Liste
1.4.4 Tuplurile în Python
1.4.5 Șirurile în Python
1.4.6 Mulțimea (set) în Python
1.4.7 Dicționare
1.4.8 Conversia între două tipuri de date
1.5 Conversia de tip și forțarea tipului (casting)
1.5.1 Conversia de tip
1.6. Instrucțiunile de Intrare/Ieșire și import
1.6.1 Funcția de ieșire print()
1.6.2 Funcția de intrare
2

1.6.3 Importul modulelor


1.7 Operatorii în Python
1.7.1 Operatorii aritmetici
1.7.2 Operatorii de comparație
1.7.3 Operatorii logici
1.7.4 Operatorii pe biți (bitwise)
1.7.5 Operatorii de atribuire
1.7.6 Operatorii speciali
1.7.7 Operatorii de apartenență
1.8 Spațiul numelor (namespace) și domeniul de vizibilitate (scope)
1.8.1 Numele în Python
1.8.2 Spațiul numelor (namespace)
1.8.3 Domeniul de vizibilitate al variabilelor (scope)
1.9 Mutabil, imutabil și alias
1.10 Întrebări, exerciții și probleme

2. Controlul fluxului de prelucrare, 59


2.1 Concepte
2.2 Construcția condițională
2.3 Instrucțiunea if … else în Python
2.4 Instrucțiunea match-case
2.5 Bucla for
2.6 Bucla while
2.7 Instrucțiunile break și continue
2.7.1 Instrucțiunea break
2.7.2 Instrucțiunea continue
2.8 Instrucțiunea pass
2.9 Întrebări, exerciții și probleme

3. Funcții, module și pachete, 78


3.1 Funcții
3.1.1 Definiția funcției în Python
3.1.1 Definiția funcției în Python
3.1.3 Instrucțiunea return
3.1.4 Domeniul de vizibilitate și durata de viață a variabilelor în cadrul
funcțiilor
3.1.5 Funcții cu argumente în Python
3.1.5.1 Funcții cu număr fix de argumente
3.1.5.2 Funcții cu număr variabil de argumente
3.1.6 Recursivitatea
3.1.7 Funcțiile lambda
3.1.8 Variabile globale
3.1.9 Variabile locale
3.1.10 Variabile globale și locale
3.1.11 Variabile nelocale
3.1.12 Utilizarea cuvântului cheie global
3.1.13 Variabilele globale în cazul mai multor module Python
3.1.14 Globalitatea în funcțiile încuibate
3.1.15 Utilizarea *args și **kwargs
3

3.2 Modulele
3.2.1 Importarea modulelor în Python
3.2.1.1 Instrucțiunea import
3.2.1.2 Instrucțiunea from … import
3.2.1.3 Importarea tuturor numelor
3.2.2 Calea de căutare (path) a modulelor
3.2.3 Reîncărcarea unui modul
3.2.4 Funcția built-in dir()și atributul '__name__'
3.3 Pachetele
3.3.1 Definiția pachetului
3.3.2 Importul modulelor din pachete
3.4 Întrebări, exerciții și probleme

4. Tipuri numerice de date în Python, 102


4.1 Tipuri numerice
4.2 Conversia de tip
4.3 Numere fracționare și zecimale
4.4 Fracțiile în Python
4.5 Module matematice
4.6 Întrebări, exerciții și probleme

5. Liste, 110
5.1 Precizare
5.2 Definiția și crearea listelor
5.3 Accesarea elementelor unei liste
5.3.1 Indexarea unei liste
5.3.2 Indexarea negativă
5.4 Felierea (slicing)
5.5 Schimbarea sau adăugarea elementelor unei liste
5.6 Ștergerea sau eliminarea elementelor dintr-o listă
5.7 Tabel cu metodele obiectului listă
5.8 Lista comprehensivă
5.9 Alte operații cu liste în Python
5.9.1 Testul de apartenență la listă
5.9.2 Parcurgerea unei liste
5.10 Tablourile 1D în Python. Modulul array
5.10.1 Generalități
5.10.2 Operațiile de bază cu un tablou (array) unidimensional (1D)
5.11 Liste de liste (tablourile bidimensionale 2D) în Python
5.11.1 Operațiile de bază cu tablourile 2D
5.11.2 Netezirea listelor de liste
5.12 Întrebări, exerciții și probleme

6. Tupluri, 125
6.1 Definiția și crearea tuplurilor
6.2 Accesarea elementelor unui tuplu
6.2.1 Indexarea pozitivă
6.2.2. Indexarea negativă
6.2.3 Slicingul (felierea) tuplurilor
4

6.3 Schimbarea elementelor unui tuplu


6.4 Ștergerea unui tuplu
6.5 Alte metode aplicabile obiectului tuplu
6.6 Avantajele tuplurilor comparativ cu listele, în cazuri specifice
6.7 Întrebări, exerciții și probleme

7. Șiruri, 133
7.1 Conceptul de șir
7.2 Crearea șirurilor
7.3 Accesarea caracterelor unui șir
7.4 Modificarea sau ștergerea unui șir
7.5 Operații cu șiruri
7.5.1 Concatenarea a două sau mai multe șiruri
7.5.2 Iterarea unui șir
7.5.3 Testarea apartenenței la șir
7.5.4 Funcții pre-construite pentru lucrul cu șiruri
7.6 Formatarea șirurilor
7.6.1 Secvența Escape
7.6.2 Șiruri brute (raw)
7.6.3 Formatarea șirurilor cu metoda format()
7.6.4 Formatare clasică (specifică limbajului C standard)
7.6.5 Formatarea cu prefix f (metoda f-strings)
7.7 Metodele uzuale ale șirurilor
7.8 Întrebări, exerciții și probleme

8. Tipul de date set, 143


8.1 Definiția și crearea seturilor
8.2 Modificarea unui set
8.3 Eliminarea de elemente dintr-un set
8.4 Operații logice cu seturi (mulțimi)
8.5 Alte metode pentru operații cu seturi
8.6 Alte operații cu seturi
8.7 Funcții built-in pentru tipul de date set
8.8 Înghețarea seturilor
8.9 Întrebări, exerciții și probleme

9. Dicționare, 152
9.1 Definiția și crearea dicționarelor
9.2 Accesarea elementelor unui dicționar
9.3 Modificarea și adăugarea elementelor unui dicționar
9.4 Eliminarea elementelor din dicționar
9.5 Metodele obiectului dicționar
9.6 Comprehensiunea dicționarelor
9.7 Alte operații cu dicționarele
9.7.1 Testul de apartenență la dicționar
9.7.2 Parcurgerea unui dicționar
9.7.3 Funcții built-in pentru dicționare
9.8 Întrebări, exerciții și probleme
5

10. Operații I/O. Fișiere, 160


10.1 Noțiunea de fișier
10.2 Deschiderea fișierelor
10.3 Închiderea fișierelor
10.4 Scrierea fișierelor
10. 5 Citirea fișierelor
10.5.1 Utilizarea metodei read(size)
10.5.2 Citirea fișierului utilizând o comandă repetitivă
10.5.3 Utilizarea metodei readline()
10.6 Metode pentru fișiere
10.7 Operații cu directoare și fișiere
10.7.1 Conceptul de director (folder)
10.7.2 Schimbarea directorului
10.7.3 Obținerea listei directoarelor și fișierelor
10.7.4 Crearea unui nou director
10.7.5 Redenumirea unui director sau fișier
10.7.6 Ștergerea unui director sau fișier
10.7.7 Redirectarea fișierelor stdin, stdout și stderr
10.8 Întrebări, exerciții și probleme

11. Programarea Orientată pe Obiecte – prezentare generală, 173


11.1 Generalități
11.2 Clasele
11.3 Obiectele
11.4 Metodele
11.5 Programarea orientată pe obiecte
11.6 Utilitatea POO
11.7 Caracteristicile structurale ale modelului obiectelor utilizate în POO
11.7.1 Abstractizarea
11.7.2 Moştenirea
11.7.3 Încapsularea
11.7.4 Polimorfismul
11.8 Întrebări, exerciții și probleme

12. Clase și obiecte în Python, 184


12.1 Definirea și crearea claselor și obiectelor
12.2 Ștergerea obiectelor și atributelor
12.3 Încapsularea datelor
12.4 Moștenirea
12.5 Suprascrierea metodelor
12.6 Moștenirea multiplă
12.7 Moștenirea multinivel
12.8 Ordinea apelării metodelor (MRO)
12.9 Supraîncărcarea operatorilor
12.9.1 Conceptul de supraîncărcare a unui operator
12.9.2 Supraîncărcarea utilizând funcții speciale
12.9.3 Supraîncărcarea operatorului + și a altor operatori
12.10 Întrebări, exerciții și probleme
6

13. Iteratori, 204


13.1 Definiția și utilizarea iteratorilor
13.2 Cum funcționează bucla for cu iteratorii
13.3 Construcția iteratorilor de tip utilizator
13.4 Iteratori infiniți
13.5 Modulul itertools
13.6 Întrebări, exerciții și probleme

14. Generatoare, 212


14.1 Definiția și crearea generatoarelor
14.2 Generatoare cu bucle for
14.3 Expresii generatoare
14.4 Utilizarea generatoarelor
14.5 Întrebări, exerciții și probleme

15. Închideri, 221


15.1 Concept
15.2 Definirea unei închideri
15.3 Utilitatea unei închideri
15.4 Întrebări, exerciții și probleme

16. Funcții decoratoare, 226


16.1 Concept și definiție
16.2 Construcția decoratorilor
16.3 Decorarea funcțiilor care au listă de parametri
16.4 Înlănțuirea decoratorilor
16.5 Întrebări, exerciții și probleme

17. Erori și excepții built-in, 233


17.1 Tipuri de erori
17.1.1 Erorile de sintaxă și de sistem
17.1.2 Excepțiile (erorile logice)
17.2 Excepțiile de tip built-in
17.3 Tratarea excepțiilor cu try, except și finally
17.3.1 Excepțiile în Python
17.3.2 Capturarea excepțiilor
17.3.3 Capturarea excepțiilor specifice
17.3.4 Ridicarea excepțiilor în Python
17.3.5 Instrucțiunea try cu clauza else
17.3.6 Instrucțiunea try ... finally
17.4 Excepții definite de utilizator
17.4.1 Crearea excepțiilor de tip utilizator
17.4.2 Excepția AssertionError
17.5 Suprascrierea (supraîncărcarea) claselor excepție
17.6 Întrebări, exerciții și probleme
7

18. Expresii regulate RegEx, 245


18.1 Definiție și utilizare
18.2 Specificarea șabloanelor/pattern-urilor utilizând RegEx
18.3 Funcții pentru lucrul cu expresii regulate
18.4 Proprietățile obiectului găsit prin potrivire
18.5 Utilizarea prefixului r sau R înaintea expresiei regulate
18.6 Întrebări, exerciții și probleme

19. Ecosistemul Python, prezentare generală, 256


19.1 Calcul numeric și științific
19.1.2 NumPy
19.1.3 Vizualizarea datelor
19.1.4 Pandas
19.1.5 SciPy (sub-ecosistem)
19.2. Biblioteci Machine Learning & Deep Learning
19.3 Dezvoltare Web (Web Development)
19.4 Dezvoltarea interfețelor grafice (GUI Development)
19.5 Biblioteci penru procesarea imaginilor (Image Processing Libraries)
19.6 Dezvoltare software (Software Development)
19.7 Administrarea sistemelor (System Administration)
19.8 Biblioteci pentru dezvoltarea jocurilor (Game Development Libraries)
19.9 Biblioteci pentru automatizarea testărilor (Automation Testing Libraries)
19.10 Web Scrapping Libraries

20. Interfețe grafice utilizator (GUI), 263


20.1 Introducere în GUI
20.1.1 Generalități
20.1.2 Tkinter
20.1.3 QT, PyQT, PyQt5, PySide, QT Designer
20.1.4. Kivy
20.1.5 WxPython
20.1.6 PyGUI
20.1.7 PyGTK
20.1.8 Jpython
20.2 Tkinter
20.2.1 Prezentare generală tkinter
20.2.2 Tipurile de widgets specifice tkinter
20.2.3 Atributele standard ale widgeturilor
20.3 Întrebări, exerciții și probleme

21. Numpy. Elemente de bază, 285


21.1 Introducere
21.2 Comparație între caracteristicile tablourilor în Python și Numpy
21.3 Crearea și accesarea tablourilor în Numpy
21.3.1 Crearea tablourilor
21.3.2 Accesarea ordonată a tablourilor în Numpy
21.4 Prelucrarea formei tablourilor în Numpy
21.4.1 Schimbarea formei tablourilor
8

21.4.2 Alipirea și divizarea tablourilor în Numpy


21.5. Copierea tablourilor în Numpy
21.5.1 Atribuirea simplă a tablourilor
21.5.2 Copierea prin crearea unei “vederi” (view, shalow)
21.5.3. Copierea efectivă (reală) a tablourilor în Numpy
21.6. Broadcasting-ul (difuziunea, sau extinderea automată)
21.7. Printarea tablourilor în Numpy
21.8. Operațiile de bază cu tablouri în Numpy
21.9. Funcții universale în Numpy
21.10 Întrebări, exerciții și probleme

22. Matplotlib, 309


22.1 Generalități
22.2 Lucrul cu mai multe figuri și mai multe axe
22.3 Trasarea și proprietățile grilelor
22.4 Crearea legendelor
22.5 Reprezentarea polară
22.6 Grafic cu bare
22.7 Grafic de tip pie (pie chart)
22.8 Plasarea textului pe grafic
22.9 Desenarea de linii pe figură
22.10 Specificarea dimensiunii și salvarea imaginii unui grafic
22.11 Afișarea graficului în format “stem”
22.12 Afișarea cu axe logaritmice, sau neliniare
22.13 Afișarea histogramelor
22.14 Reprezentări 3D
22.15 Întrebări, exerciții și probleme

23. Pandas, 338


23.1 Concepte și definiții
23.2 Crearea unei serii de date
23.3 Crearea unui DataFrame
23.4 Atributele și metodele obiectelor Series și DataFrame
23.5 Indexarea și selectarea datelor în Pandas
23.6 Operații cu Serii și DataFrame-uri în Pandas
23.7 Generarea intervalelor temporale “dată-timp ”
23.8 Vizualizarea datelor în Pandas cu Matplotlib
23.9 Întrebări, exerciții și probleme

24. SciPy, 359


24.1 Introducere
24.2 Aplicații Scipy
24.2.1 Funcții trigonometrice și exponențiale
24.2.2 Calcul integral
24.2.3 Algebră liniară
24.2.4 Funcții de interpolare
24.2.5 Funcții de optimizare
24.2.6 Transformata Fourier
9

24.2.7 Procesarea semnalelor


24.2.8 Modulul Spatial Data Structures and Algorithms
24.3 Întrebări, exerciții și probleme

25. Procesarea imaginilor în Python, 377


25.1 Unelte pentru proocesarea imaginilor în Python
25.2 Modelul imaginii utilizat în procesarea digitală
25.2.1 Tipuri de imagini digitale
25.2.2 Convenții de reprezentare a culorilor în imaginile digitale
25.2.3 Formate de imagine
25.3 Citirea și salvarea unei imagini
25.4 Operații cu imagini în Pillow
25.4.1 Crearea unei imagini
25.4.2 Afișarea dimensiunii (lățime, înălțime) și formatul unei imagini
25.4.3 Afișarea modului de culoare (spațiul de culoare) al unei imagini
25.4.4 Rotirea unei imagini
25.4.5 Răsturnarea unei imagini
25.4.6 Redimensionarea unei imagini
25.4.7 Compunerea și fuziunea imaginilor
25.4.8 Decuparea unei imagini
25.5 Întrebări, exerciții și probleme

26. Administrarea ecosistemului Python, 398


26.1 Instalarea Python utilizând distribuția Anaconda
26.2 Configurarea Python
26.3. Utilizarea pachetelor
26.3.1 Pachete Python
26.3.2 Unelte de instalare a pachetelor Python
26.3.4 Chestiuni legate de organizarea spațiului de lucru
26.4 Administrarea dependențelor aplicațiilor
26.4.1 Conceptul de mediu virtual
23.4.2 Crearea unui mediu virtual
26.5 Întrebări, exerciții și probleme

Glosar, 408

Bibliografie, 414
Prefață
Dacă se face o simplă căutare, pe Internet, a lucrărilor referitoare la limbajul Python, se
descoperă imediat o cantitate impresionantă de tutoriale, cursuri online, cărți, aplicații, sau
documentații, în majoritate în limba engleză. În acest context se pune întrebarea: de ce această
nouă lucrare?

Răspunsul este simplu: această lucrare se dorește a fi un mijloc de abordare, de utilitate cît mai
mare (ghid, manual și îndrumar practic), pentru cei care doresc să se inițieze sau să-și
îmbunătățească cunoștințele referitoare la limbajul Python și aplicațiile sale, în limba română.

Lucrarea încearcă să fie:

- Un ghid, pentru că asigură o privire de ansamblu asupra fenomenului Python (fără însă
să epuizeze toate aspectele).
- Un manual, pentru că încearcă să fie o lucrare de referință în studiul și utilizarea Python.
- Un îndrumar, deoarece oferă un număr mare de exemple și exerciții de utilizare a
limbajului Python și a aplicațiilor sale.

Ideal, pentru parcurgerea lucrării, ar fi utilă inițierea prealabilă în limbajul “C” (pentru
asigurarea unor cunoștințe generale despre limbaje) și/sau ”C++” (pentru asigurarea unor
cunoștințe generale despre tehnologia programării orientate spre obiecte). În acest ultim caz,
abordarea conținutului poate fi făcută “liniar”, capitol după capitol.

Totuși, lucrarea poate fi utilizată și pentru inițierea într-un limbaj de programare, fără
cunoașterea anterioară a limbajului C, sau a unui alt limbaj. În acest caz, capitolele se pot
parcurge oarecum “neliniar” și iterativ. Python este un limbaj orientat total pe obiecte, dar
abordarea conceptelor referitoare la obiecte nu se face din primele capitole ale lucrării. Din
acest motiv, se recomandă cititorului să nu insiste asupra unor concepte sau modele pe care nu
le înțelege în primele capitole, dar să revină la ele după studiul capitolelor 11 (generalități POO)
și 12 (clase și obiecte).

Cartea este împărțită în două:

Partea I se referă la învățarea limbajului Python (primele capitole până la al 19-lea, inclusiv,
conțin noțiunile fundamentale, atât cele comune diferitelor limbaje de programare, cât și cele
specifice: tipuri de date, șiruri, liste, tupluri, dicționare, etc.). Partea I este accesibilă și poate fi
studiată și de elevii de gimnaziu sau liceu.
Partea a II-a conține o prezentare limitată a unor aspecte ale “ecosistemului” Python (capitolele
20 – 26). Este utilă studenților sau specialiștilor care caută un mediu de programare fără costuri
(“open source”), echivalent cu MATLAB, sau mai puternic, îndeosebi pentru aplicații științifice,
dar nu numai. Sunt abordate în cadrul acestei părți chestiuni ca programarea unor interfețe
grafice interactive, reprezentarea și prelucrarea datelor numerice/științifice, elemente de
procesare a imaginilor, etc.

Adresa de email a autorului, dedicată lucrării, pentru primirea diverselor observații, sugestii,
aprecieri, sau discutarea anumitor aspecte ale cărții, este: eugen.n.diaconescu@gmail.com.

Eugen Diaconescu, septembrie 2022


Eugen Diaconescu Limbajul și ecosistemul de programare Python

INTRODUCERE
I. Ce este Python ?
Python este un limbaj de programare deja foarte cunoscut și apreciat, având capacități
superioare, specifice limbajelor de nivel înalt. La popularitatea sa au contribuit atât ușurința de
învățare a sintaxei, cât și portabilitatea sa.
Python a fost dezvoltat de Guido van Rossum la Stichting Mathematisch Centrum în Olanda.
Limbajul a fost scris ca un succesor al limbajului de programare denumit ‘ABC’, iar prima
versiune a fost disponibilă în 1991.
Numele “Python” a fost dat de Guido van Rossum după un show TV denumit “Monty Python’s
Flying Circus” care se difuza la acea vreme și care plăcuse autorului.
Python este un software ușor accesibil care poate fi descărcat și instalat gratuit pe orice sistem
de operare de la www.python.org.
Python împrumută caracteristici atât din limbajele tradiționale ca Java și C, dar și din limbajele
funcționale.
Este un limbaj interpretat, codul sursă este mai întâi convertit într-un bytecode, apoi executat
de mașina virtuală Python. Sunt disponibile și compilatoare pentru obținerea fișierelor
executabile din programele sursă.
Esența filozofiei limbajului este pe scurt prezentată în documentul "PEP 20 – The Zen of
Python". (Python Enhancement Proposals, Python Software Foundation, Tim Peters, 2004),
care include și următoarele aforisme (în engleză):
- Beautiful is better than ugly.
- Explicit is better than implicit.
- Simple is better than complex.
- Complex is better than complicated.
- Readability counts.
II. Avantajele și dezavantajele limbajului Python
Avantaje
Python este ușor, rapid-inteligibil, multi-orientat, vast, robust, adaptativ, scalabil, concentrat.
Este ușor de învățat și înțeles - deoarece sintaxa limbajului Python este mai simplă, ceea ce
ușurează învățarea și înțelegerea.
Este un limbaj multi-orientat - deoarece permite mai multe tipuri sau stiluri de programare:
structurată, orientată spre obiecte și funcțională.
Este vast – deoarece dispune de un număr uriaș de module (“ecosistemul” Python) care acoperă
multe aspecte ale activităților de proiectare și realizare de programe informatice. Aceste module
sunt ușor disponibile, ceea ce face din Python un limbaj extensibil.
Sustenabil – deoarece are susținerea unei comunități open source foarte puternice. Acest lucru
ajută la fixarea rapidă a bug-urilor, ceea ce face ca Python să fie și robust și adaptiv.
Este scalabil - deoarece asigură o structură îmbunătățită pentru a permite și realizarea de
programe și aplicații mari, nu numai scripturi dezvoltate în shell.
11 Introducere

Este eficient și concentrat – se spune că programele scrise în Python sunt cu 20-90% mai scurte
decât în alte limbaje clasice.
O explicație a succesului uriaș al Pythonului s-ar datora, după unele studii și cercetări, ușurinței
utilizării pe care ar fi generat-o identarea obligatorie și eliminarea acoladelor tradiționale
pentru delimitarea blocurilor, atât de obișnuite și necesare în limbajele C/C++ și Java.
Python a fost primul limbaj de programare în care s-a conștientizat pe deplin ideea că indentarea
este un factor foarte important în ușurarea efortului de a scrie programe.
Indentarea a fost de mult timp o tehnică utilizată în scrierea programelor, mai în toate limbajele
de programare, având scopul de a asigura o anumită claritate în reprezentarea algoritmului prin
instrucțiuni și a ușura înțelegerea acestuia la momentul citirii sau recitirii unui program.
Indentarea este de fapt o organizare în planul vederii a structurii ierarhice și arborescente a
unităților care compun programul, cu efecte vizuale pe direcțiile orizontală și verticală. În
limbaje ca C/C++/Java, existența structurii ierarhic-arborescente este garantată prin utilizarea
acoladelor, indentarea fiind doar recomandată și opțională. Ca urmare al utilizării în plus a unui
număr mare de simboluri, perechi de acoladă, se produce efectul nedorit de obosire a vederii și
diminuarea atenției și chiar generarea unui număr mai mare de erori datorită necesității de a
contabiliza mental perechile de acoladă. Mai mult, apare un efect pervers al neobligativității
indentării constând în tentația oferită programatorului de a nu utiliza deloc indentarea, cel puțin
pe unele porțiuni, sau de a o utiliza neuniform în cadrul programului, mai ales dacă acesta este
supus unor frecvente modificări, de către mai mulți programatori. Neuniformitatea este însă
obositoare, iar alternarea aleatorie a unor reguli de indentare poate conduce la unele dificultăți
în concentrarea programatorului la munca sa.
Mai mult de atât, se verifică practic că este mult mai ușoară structurarea vizuală a unităților
programului, comparativ cu aceea prin simboluri de tip acoladă. Efectul este cumva asemănător
aceluia sugerat de un vechi proverb chinezesc: “O imagine valorează mai mult ca o mie de
cuvinte.” De fapt, procesarea de creier a unei imagini se face mult mai rapid comparativ cu
analiza logică a unei hărți de simboluri.
Sentimentul de ordine și aspectul de lucru organizat pe care îl produce indentarea, mai ales
indivizilor care prin natura lor sunt oarecum dezordonați, dă o adevărată dependență față de
limbajul Python.
Python este considerat în prezent ca fiind un limbaj utilizat pe larg de comunitatea
științifică, îndeosebi de accea implicată activ în cercetările referitoare la inteligența
artificială, fiind considerat un înlocuitor pentru Matlab. Combinația Python+Numpy
(Numerical Python) +Matplotlib (Plotting Library) +SciPy (Scientific Python) este utilizată în
prezent pe scară largă ca o soluție alternativă la Matlab, fiind suficient de modernă și
cuprinzătoare. În plus, caracterul open source este un important avantaj pentru ca Python și
ecosistemul său să fie preferat sistemului Matlab.
Sintetic, Python poate fi apreciat ca:
1. Simplu și ușor de invătat.
2. Are un ciclu rapid de dezvoltare a proiectelor (easy and fast prototyping).
3. Mod interactiv de lucru.
4. Portabil (PortablePython poate fi instalat pe un stick usb).
5. Dispune de biblioteci pentru programarea GUI.
6. General extensibil.
12 Introducere

Dezavantaje
Principalul dezavantaj este viteza mai mică de execuție, deoarece nu este un limbaj integral
compilat, ci interpretat. De fapt, este considerat de către unii specialiști ca fiind “interpretat și
compilat”, luîndu-se în considerare și faza de obținere a unui bytecode înainte de execuție.
Totuși, viteza de lucru poate fi îmbunătățită prin compilatoare (sau “cross-compilere”).

III. Utilizarea Python în proiectarea aplicațiilor


Limbajul Python tinde să devină universal.
Din 2003, Python s-a clasat constant în topul celor mai populare zece limbaje de programare
din Indexul comunității de programare TIOBE, unde, din octombrie 2021, este cel mai popular
limbaj (în fața Java și C). A fost selectat Limbajul de programare al anului (pentru „cea mai
mare creștere a evaluărilor într-un an”) în 2007, 2010, 2018 și 2020.
Organizațiile mari care folosesc Python includ Wikipedia, Google, Yahoo!, CERN, NASA,
Facebook, Amazon, Instagram, Spotify, etc.
În septembrie 2021, Python Package Index (PyPI), “depozitul” oficial pentru componentele
Python produse de terți, conținea peste 329,000 pachete aplicabile în diverse domenii, ca de
exemplu: automatizare, baze de date, interfețe grafice utilizator GUI, multimedia, rețele de
calculatoare, calcul științific, administrarea sistemelor de calcul, inteligență artificială,
documentare, procesarea imaginii, framework-uri web, procesarea textelor, rețele neuronale,
analiza datelor, etc.
Python este utilizat la proiectarea sistemelor embeded; MicroPython și CircuitPython sunt
variante Python 3 optimizate pentru microcontrolere, inclusiv kiturile de roboți jucărie Lego
Mindstorms.
În domeniul educațional, proiectul Raspberry Pi single-board computer a adoptat Python ca
principalul limbaj de programare pentru utilizatori.

IV Instalarea Python
Versiunile limbajului Python au apărut conform următoarei cronologii:
1991, februarie - Python 0.9.0
1994, ianuarie – Python 1.0
2000, octombrie – Python 2.0
2008, decembrie – Python 3.0
2016, decembrie – Python 3.6
2018, mai – Python 3.7
2019, octombrie – Python 3.8
2020, octombrie – Python 3.9
2020 – Python 2.0 nu a mai fost susținut (end-of-life).
2021, octombrie – Python 3.10
13 Introducere

Se va prezenta pe scurt instalarea Pythonului pentru sistemele de operare Windows și


Linux/Ubuntu, mai des întâlnite în România.
Pytho poate fi instalat individual sau prin intermediul unei distribuții, cea mai cunoscută fiind
Anaconda. În acest ultim caz, odată cu Python mai sunt instalate și alte aplicații care sunt fie
componente ale ecosistemului Python, fie sunt în strânsă legătură cu acesta.
Platforma Windows
Observație. Versiunile Python 3.9+ nu mai pot fi utilizate pe sistemul de operare Windows 7
sau pe cele anterioare acestuia.
Pentru instalarea Python, trebuie urmați pașii indicați la www.python.org, figura 1.
14 Introducere
15 Introducere

Fig. I.1 Etapele de urmat pentru instalarea Python


Platforma Ubuntu (Linux)

Python3 este în prezent preinstalat pe versiunile curente de Linux (Ubuntu). Se apelează simplu
în terminal, în linia de comandă, “python3”, după care apare promterul binecunoscut “>>>”.
Dacă se utilizează o versiune mai veche de Linux/Ubuntu, se accesează www.python.org și se
descarcă codul arhivat corespunzător versiunii de Python dorită. Se continuă instalarea,
presupunând un utilizator non-user, cu privilegii sudo, etc.

V Medii de dezvoltare pentru proiectele Python


Pe tot cuprinsul cărții au fost utilizate doar mediile de de dezvoltare IDLE Python (cu tkinter
integrat) și Spyder (pentru programele care necesită suport grafic la ieșire pentru matplotlib).
În practică sunt disponibile multe alte medii, unele fiind foarte apreciate (pyCharm, etc.). O
descriere sumară a acestora se face în continuare.

V.1 Conceptul de mediu de dezvoltare

Un mediu de dezvoltare integrat (Integrated Development Environment - IDE) este o aplicație


software care oferă facilități importante programatorilor de calculator pentru dezvoltarea de
software. Un IDE constă în mod normal din cel puțin un editor de cod sursă, instrumente de
automatizare a construcțiilor și un depanator. Unele IDE conțin compilatorul, interpretorul, sau
ambele; altele, nu – fiind necesar să fie încărcate după dorință. Limita dintre un IDE și alte părți
ale unui mediu mai larg de dezvoltare software nu este bine definită; uneori sunt integrate un
sistem de control al versiunilor sau diverse instrumente pentru a simplifica construcția unei
interfețe grafice cu utilizatorul (GUI). Multe IDE moderne au, de asemenea, un browser de
clase, un browser de obiecte și o diagramă ierarhică de clase pentru utilizare în dezvoltarea de
software orientat obiect.
Un IDE se poate caracteriza prin următoarele informații referitoare la structura și
funcționalitatea pe care o posedă:
16 Introducere

- Numele (originea, semnificația), Producator/Creator, Licență: Comercial/Free,


Specific/Universal, Platformă sau Cross-platformă, Basic sintax highlighting (iluminare text
sintaxă de bază), Numerotare linii, Identare automată și inteligentă, Completare-auto-cod,
Cuvinte cheie prezentate ca auto-completare în timpul editării, Multilingv, Integrare surse,
Împerechere automată paranteze-acolade, Autocompletare (eventual cu auto-import), Creare
docstring, Vizualizare rapidă pachete-module-clase instalate, Redare grafică, Scris în ...
(limbajul în care a fost realizat), Prezență debugger (eventual și consolă de debug), Analizor de
cod, widget toolkit, GUI builder, Go to definition (salt la definiție), Browser de clase,
Refactorizare cod, Controlul versiunii, Profiler, Adresa documentare, existența unei Descrieri
sumare, etc.
Pentru Python, conform Wikipedia, sunt disponibile următoarele IDE:
- Anjuta, Eric, Geany, IDLE, Komodo IDE, Kdevelop, Microsoft Visual Studio, MonoDevelop,
Ninja-IDE, PIDA, PyCharm, Pydev/Liclipse (plug-in pentru Eclipse și Aptana), PyScripter,
PythonAnywhere, SlickEdit, SourceLair, Spyder, Thony, Understant, Visual Studio Code,
Wing.
La lista de mai sus se adaugă multe altele, de exemplu: GNU Emacs, Atom, Vim, etc.
Un loc aparte îl opcupă proiectul Jupiter.

V.2 IDLE Python


IDLE este un mediu minimal de dezvoltare și învățare integrat al limbajului Python, denumit
IDLE (Integrated Development and Learning Environment) și este o alternativă la linia de
comandă Python, fiind livrat în kitul de instalare..
IDLE este un shell, adică un interpretor interactiv, fiind la bază o construcție REPL – Read-
Eval-Print-Loop. Acest shell citește (read) o instrucțiune Python, evaluează (eval) rezultatele
execuției instrucțiunii și apoi le afișează (print) pe ecran. În continuare repetă ciclul (loop),
citind o nouă instrucțiune.
IDLE este și un editor de fișiere sursă Python de tip .py. Aceste fișiere se pot deschide, edita și
executa în multiple ferestre. Editorul de fișiere deține o serie de unelte, ca de exemplu identarea
automată și inteligentă cu evidențierea sintaxei, completarea codului și afișarea de sugestii (call
tips) și funcțiunea de context al codului. De asemenea, IDLE oferă și modul integrat cu pași
Interpreter Debug, adică deschiderea unei ferestre prin care se poate inspecta valoarea
variabilelor locale și globale.
O altă facilitate este definirea și utilizarea punctelor de întrerupere (breakpoint). Mai mult, se
asigură un instrument stack viewer prin care se urmărește vizibilitatea stivei de apeluri, traseul
erorilor și excepțiilor apărute la execuția codului.
IDLE oferă posibilitatea configurării și adaptării la particularitățile utilizatorilor în cinci
domenii: Fonturi/Taburi, Highlights (teme), utilizarea tastaturii, aspecte generale și extensii.
Date tehnice:
Producător: IDLE se instalează automat împreună cu interpretorul Python.
Comercial/free: free
Cross-platformă: Windows, Linux (Ubuntu, etc.), Mac-OS
Specific/Universal: proiectat special pentru utilizare cu Python.
Multilingv: Engleză
17 Introducere

Vizualizare rapidă pachete-module-clase instalate. Se utilizează opțiunile din meniu Path Browser și
Module Browser.
Identare inteligenta: opțiuni diverse în meniu pentru identare, tabulare și colorare.
Numerotare linii: opțiune ON/OFF
Opțiuni configurare: Da
Împerechere automată paranteze-acolade: Nu
Completare-automată-cod: Nu (la cerere prin Ctrl +Sp)
Redare grafică:Da (tkinter)
Integrare surse/proiecte: Nu
Integrare Version Control: Nu

Observație. Schimbarea fondului din întunecat (IDLE dark) în luminos (IDLE Classic) se face
din Options/Configure IDLE/Highlights.

Fig. I.2 Shellul și editorul IDLE Python


Utilizarea “prompterului extins” (multilinie)
Consola de programare IDLE oferă posibilitatea introducerii pe mai multe linii a instrucțiunilor
complexe de tip for, if, sau def. Promterul curent “>>>” poate fi urmat pentru continuarea
instrucțiunii complexe de prompterul secundar “. . .”, înlocuind parțial necesitatea utilizării unui
editor de surse, astfel:

>>>for i in range(n):
... print(i*'*')
... print((n-i)*'%')
%%%%%
*
%%%%
**
%%%
***
%%
****
%
18 Introducere

V.3 Spyder

Spyder este un IDE puternic pentru mediul științific (scris în Python), pentru programarea în
limbajul Python, de tip open source.
Integrează un număr mare de pachete importante pentru mediul științific, printre care: NumPy,
SciPy, Matplotlib, Pandas, Ipython, Sympy, etc. De asemenea, integrează: console interne
multiple, history log, on line help browser, etc., permițînd vizualizarea de pachete și
documentații în interiorul IDE, analizor, debugger, etc. Include plug-in-uri, de exemplu pentru
vizualizare și editare de Notebooks-Jupiter (cu conda sau pip) . Stabilirea directorului curent
de lucru poate fi selectat din meniul tools>preferences>current working directory, iar
opțiunea de interfață întunecată (dark) din tools>preferences>appearance>interface
theme.

Comparat cu PyCharm, are mai puține facilități.


Se poate instala individual sau prin intermediul distribuției Anaconda.

Fig. I.3 Interfața Spyder


Date tehnice:
Producător: Proiect al unei echipe de contributori (Spyder project contributors). Creatorul inițial:
Pierre Raybaut, 2009.
Comercial/free: Open source, licență MIT
Cross-platformă: Da (Windows, Linux, Unix, MacOS, etc.)
Specific/Universal: Specific Python
Multilingv: nu
Vizualizare rapidă pachete-module-clase instalate. Da. Modulele și pachetele instalate pot fi
vizualizate cu help>dependences.
Identare inteligenta: Da
Numerotare linii: Opțional
Opțiuni configurare: Da
Împerechere automată paranteze-acolade: Da
19 Introducere

Completare-auto-cod: Da
Redare grafică: Da, utilizează Qt, PyQT, sau PySide pentru GUI.
Integrare surse/proiecte: Da
Integrare Version Control: Nu
Plug-in-uri: DA
https://docs.spyder-ide.org/index.html
Observație. Sursa programului Python poate fi împărțită în celule care pot fi executate
individual, sau pas cu pas urmărind rezultatele, sau integral. Șirul delimitator pentru o celulă
este “#%%”, același ca în Jupyter Notebook.

V.4 PyCharm
PyCharm este considerat cel mai complet IDE pentru Python. Este oferit în trei variante:
Profesional Edition (plătit), Community Edition (Gratuit) și Educational Edition (Gratuit) (din
2010) de firma JetBrains.
În timp ce PyCharm CE și PyCharm EE se pot utiliza doar cu Python, PyCharm PE poate
dezvolta proiecte și pentru alte procesoare software, fie aparținând ecosistemului Python
(Django, Web2Py, Flask, Pyramid, etc.), fie diferite (JavaScript, PHP, etc.).
PyCharm este integrat în distribuția Anaconda.
Date tehnice:
Producător: Compania cehă JetBrains
Comercial/free: Apache Licence (PCE), proprietary licence (PPE)
Cross-platformă: Windows, macOS and Linux
Specific/Universal: specific Python, dar și alte limbaje ca JavaScript, PHP
Multilingv: Engleză
Vizualizare rapidă pachete-module-clase instalate.
Identare inteligenta: Da
Numerotare linii: Da
Opțiuni configurare: Da
Împerechere automată paranteze-acolade: Da
Completare-auto-cod: Da
Redare grafică:Da
Integrare surse/proiecte: Da
Integrare Version Control: Da
Observație. Schimbarea fondului din întunecat în luminos se face deschizând
Settings/Preferences cu Ctrl +Alt + S (File/Setings), selectând Appearance&Behavior și apoi
Background Image Button.
20 Introducere

Fig. I.4 Interfața PyCharm


În concluzie, PyCharm include aproape orice caracteristică posibilă la un IDE: navigare rapidă
printre proiecte, inspectarea codului/refactoring, verificare și corectare de erori, facilități
complexe de punere la punct (debugging), include un control al versiunii, etc.

V.5 Visual Studio Code


Visual Studio Code este un editor de cod gratuit (neproprietar) ușor de utilizat, dar puternic,
care rulează pe desktop și este disponibil pentru Windows, MacOS și Linux. Vine cu suport
încorporat pentru JavaScript, TypeScript și Node.js și are un ecosistem bogat de extensii pentru
alte limbaje (cum ar fi Python, C++, C #, Java, PHP, Go, etc.) și runtime (.NET). Suportă
documente de tip notebook.
Observație. Visual Studio Code se poate deschide și în browser (!) la adresa https://vscode.dev
(dar nu se pot utiliza terminal și debugger).

V.6 Eclipse + PyDev


PyDev este un Python IDE pentru Eclipse, care poate fi utilizat pentru dezvoltarea aplicațiilor
în Python, Jython și IronPython.
Eclipse este un IDE gratuit, ușor de deprins de noii utilizatori.
PyDev este foarte ușor de utilizat de programatorii de Java și în plus este gratuit.
Pydev a fost creat în anii 2003-2004 de Aleksander Totic și în prezent este menținut de Fabio
Zadrozny.
PyDev dispune de multe facilități, printre care: integrarea Django, completarea codului,
completarea codului cu autoimport, sugestii referitoare la tipul datelor, analiza codului, salt la
definiții, refactorizare, debbuger, debugger la distanță, browser pentru componente/tokeni,
21 Introducere

consolă interactivă, găsire referințe (Ctrl + Shift +G), etc. Sunt disponibile shortcuts-uri la
tastatură pentru toate acțiunile cu mouse-ul.
PyDev pentru Eclipse are o variantă denumită LICLIPSE, care acceptă Django, HTML, CSS și
JavaScript, dar necesită licență plătită.

V.7 Pyzo
Este un mediu de dezvoltare pentru Python gratuit și open source.
Este scris în Python și utilizează toolkitul GUI QT.
Este orientat catre simplitate și ușurință în dezvoltarea proiectelor.

Fig. I.5 Interfața Pyzo

V.8 Sublime Text


Sublime Text3 (ST3) este un mediu de dezvoltare scris în Python și C++, simplu, ușor de utilizat
și având o viteză mai ridicată decât alte IDE complexe pentru Python, consumând și mai puțină
memorie. Editorul său de texte dispune de calități deosebite. Se consideră că are un suport foarte
bun pentru Python și un grup mare de utilizatori fideli. Permite adăugarea de numeroase
facilități pentru dezvoltarea de proiecte complexe.
Observație. Deși se solicită cumpărarea unei licențe, Sublime Text poate fi testat pentru
evaluare cu mențiunea că durata evaluării nu se fixează. Este scris de Jon Skinner.
22 Introducere

Fig. I.6 Interfața Sublime Text

V.9 Thony
Thony este un IDE foarte prietenos pentru începătorii care studiază Python pentru învățare. În
acest scop este proiectat special, avind un debugger și alte caracteristici de mare ajutor. Este
gratuit, fiind dezvoltat de Universitatea din Tartu, Estonia.

Fig. I.7 Interfața Thony


23 Introducere

V.10 Alte medii de dezvoltare IDE


Komodo IDE
Komodo IDE este considerat cel mai bun IDE pentru dezvoltarea aplicațiilor Web și Mobile.
Este un IDE “poliglot” deoarece suportă peste 100 de limbaje (Python, Go, Perl, Ruby, NodeJS,
HTML, CSS, etc.), pe orice sistem de operare.
Este un sistem comercial dezvoltat de ActiveState și dispune de toate caracteristicile posibile
pentru un IDE.

Wing
Wing este o soluție performantă de IDE pentru Python cu licență plătită de la WingWare. Este
multiplatformă (Windows, Linux și MacOS X). Dispune de un utilitar de cod inteligent și unelte
foarte bune de punere la punct, etc.
Sunt oferite 3 versiuni, pentru 3 tipuri diferite de utilizatori:
Wing 101 pentru începători (licență gratuită), cu următoarele facilități:
Depanarea codului, inclusiv interactivă, raportarea excepțiilor și a urmăririi, vizualizare
variabile și stivă, etc.
Wing Personal (licență gratuită pentru uz educațional), cu facilități suplimentare:
Depanare multithreading și alte facilități complexe și avansate de depanare și punere la punct,
etc.
Wing Pro pentru programatorii profesioniști (licență comercială), are aproape toate facilitățile
posibile pentru un IDE, inclusiv utilizare pentru Django, Flask, Jupyter, matplotlib, web2py,
Plone, Zope, Docker, AWS, Vagrant, Raspberry Pi, Blender, Unreal Engine, Nuke, etc.

Cloud9
Cloud9 este un IDE cu aproape toate facilitățile posibile și care permite lucrul colaborativ în
Cloud (oriunde, oricând), în mai multe limbaje, inclusiv Python, C, C++, PHP, Ruby, Perl,
JavaScript cu Node.js, Go, etc.
Dispune de unul dintre cele mai avansate editoare cu completarea codului, etc. Poate dezvolta
proiecte în Django și Flask.
Din 2016 a fost achiziționat de Amazon și poate fi utilizat necomercial doar cu un cont AWS
(Amazon Web Services).
Eric
Eric este un IDE open source, deci gratuit, scris în Python, pentru Python și QT. Deși este un
proiect necomercial, are toate caracteristicile unui software profesional, comparabil cu multe
IDE-uri comerciale. Creatorul său se numește Detler Offenback.
24 Introducere

VI Proiectul Jupyter
Proiectul Jupyter a fost demarat în 2014 cu numele IPython Projects ca un mediu interactiv
open-source pentru calculul științific și inteligența artificială. Ideea era de pune la îndemâna
cercetătorilor un software open-source bazat pe standarde de asemenea de tip open și servicii
pentru interacționarea cu calculatorul, bazate pe web. In primul rând, el permite utilizatorului
să includă, pe lângă codul Python și text formatat (markdown), vizualizări statice și dinamice,
widgeturi JavaScripts, etc. Documentul rezultat poate fi exportat ca simplu script Python, PDF,
sau HTML.

Nucleul IPython acceptă și alte limbaje de programare, ca Julia și R. Chiar denumirea sa arată
acest lucru: Jupyter = JUlia, PYThon, R. El a evoluat, acceptând și alte limbaje de programare
utilizate în prezent, ajungând la peste 40.
Prima generație de software a fost Jupyter Notebook, dedicat dezvoltării de cod, inițial în
limbajul de programare Python. A doua generație este Jupyter Lab care își propune să includă
componente de tip notebook, cod sursă și date, în mai multe fluxuri de lucru.
Comunitatea Jupyter (jupyter.org) este foarte largă și susține numeroase proiecte, găzduite de
GitHub, de exemplu: jupyter, ipython, jupyterhub, jupyterlab, jupyter-widgets, jupyter-server,
jupyter-xeus, voila-dashboards, binder-examples, jupyter-resources, etc.
Observație. Documentele de lucru Jupyter se rulează în browser, pe calculatorul utilizatorului
(serverul Jupyter este instalat pe propriul calculator al utilizatorului, asigurându-se astfel
protecția datelor). Totuși, se poate efectua o sesiune de lucru și la distanță, pe calculatoarele
universităților sau ale firmelor, unde poate fi instalad serverul Jupyter de interes. În acest caz,
securitatea disponibilă este cea generală asigurată de softurile IT utilizate.
Observație. Un exemplu special de utilizarea Jupyter este rularea aplicațiilor Python pe colab,
supercalculatorul oferit de Google pentru utilizatorii independenți care dezvoltă aplicații intens
consumatoare de resurse, în general din domeniul inteligenței artificiale. Este util pentru rularea
aplicațiilor care solicită mari resurse de calcul de tip GPU.
Instalarea serverului Jupyter se face simplu în Windows sau Linux/Ubuntu cu utilitarele pip și
conda, de exemplu:
>pip install jupyter

Jupyter Notebook
Jupyter Notebook reprezintă prima generație a proiectului Jupyter.
Jupyter Notebook poate fi startat în Windows și din linia de comandă a sistemului de operare:
C:\cale director de lansare>jupiter notebook

Jupyter Notebook este o aplicație client-server. Pagina de lucru se deschide în browserul web
implicit de pe calculator la o adresă URL locală, de exemplu: http://127.0.0.1:8888
(http://localhost:8888).
Pagina de lucru este un dashboard, arătând notebook-urile existente în directorul de lucru (care
este chiar cel de lansare), figura 1.8. Extensia implicită este .ipynb (probabil prescurtarea de
la ipython notebook), dar sunt admise și alte extensii, de exemplu .py.
Caracteristici generale:
25 Introducere

- Editarea codului în browser cu identare și iluminare automată, etc.


- Executarea codului din browser, cu atașarea rezultatelor codului care le-a generat.
- Afișarea rezultatelor calculelor utilizând reprezentări media ca HTML, LaTEX, PNG, SVG,
etc.
- Editarea în browser a textului utilizând limbajul de marcare Markdown.
- Posibilitatea de a include notație matematică utilizând LaTEX și redarea acestuia cu MathJax.
Specific unei pagini Notebook este organizarea conținutului în celule. O celulă poate
cuprinde cod, titluri sau text în format markdown (pentru documentarea codului, etc.).
- Se poate executa codul dintr-o singură celulă individuală, sau mai multe simultan.
- Rezultatul executării este prezentat imediat după fiecare celulă.
- Celulele se pot insera sau șterge. De asemenea, se pot incorpora și afișa vizualizări cu
Matplotlib sau alte procesoare grafice.

Fig. I.8 Dashboardul Jupyter Notebook

O pagină nouă de lucru (notebook nou) se deschide selectând new, figura 1.9.

Fig. I.9 Crearea unei noi pagini de lucru în Jupyter Notebook

Selectând Python 3 se va deschide o altă fereastră, unde se poate introduce și rula noul cod
Python, în celule, figura 1.10.
26 Introducere

Fig. I.10 Pagina cu celule din Jupyter Notebook


Observație. Documentele Notebook fac parte din familia aplicațiilor REPL (Read-Eval-Print-
Loop). Un REPL este, așa cum de altfel s-a arătat mai sus la prezentarea IDLE, o aplicație
interactivă unde se pot scrie câteva linii de cod (snipet), care se pot executa și care permite apoi
vizualizarea imediată a rezultatului. Un notebook este un REPL organizat în celule distincte (în
orice ordine) care pot conține secvența de cod executabil, sau un text markdown, sau
vizualizarea ieșirii. În afară de textul explicativ formatat, alte elemente markdown sunt titlurile,
imaginile, ecuațiile matematice, etc.

Jupyter-Lab
Jupyter-Lab (apărut în 2018) este a doua generație de interfețe bazate pe web a proiectului
Jupyter. El va înlocui probabil Jupyter Notebook, dar în prezent suportă și documentele de tip
Jupyter Notebook în același mod ca și Jupyter Notebook.
Jupyter-Lab permite configurarea și aranjarea interfeței de către user corespunzător interesului
său, integrând mai multe fluxuri de lucru. De asemenea admite instalarea unor plug-in-uri
pentru extinderea facilităților.
Jupyter-Lab (figura 1.11) se poate lansa de asemenea din linia de comandă, astfel:
>jupyter-lab

Fig. I.11Dashboardul Jupyter Lab


27 Introducere

VII Interpretoare și compilatoare Python


Definiție. Compilatorul transformă un program sursă într-un cod obiect binar (cod mașină) pe
care calculatorul îl poate înțelege și executa. Operațiile compilatorului pot fi divizate în mai
multe faze (analiza lexicală, analiza sintactică, analiza semantică, generarea codului
intermediar, optimizarea codului obiect și generarea codului executabil). În cazul opririi înainte
de final, compilatorul oferă o listă cu toate erorile descoperite. După corectarea erorilor
semnalizate în programul sursă, procesul de compilare se poate relua. Acest mod de lucru se
mai numește și procesare “batch” (pe loturi). Rezultatul final este un program executabil cu o
viteză mare de execuție, de regulă.
Definiție. Un interpretor este un program care execută codul sursă linie cu linie și se oprește
când întâlnește o eroare determinată de instrucțiunea curent executată. Corectarea erorii se
poate face imediat, în timp ce programul se execută, nu înainte de aceasta. Ca urmare, procesele
de depanare și punere la punct a programului sunt mult simplificate. Viteza de rulare a
programului este însă mai mică decât în cazul programelor executabile “stand-alone”.
Observație. Programele Python sunt în general mai lente decât cele scrise în alte limbaje, de
exemplu în C/C++. Acest lucru se datorează printre altele și faptului că programele Python sunt
de regulă consumatoare de memorie mai multă decât cele scrise în alte limbaje, deși sunt mai
scurte ca programe sursă, Python fiind un limbaj mai concis.
Interpretoare Python
CPython – este implementarea originală, rămasă și în prezent ca standard, fiind ceea ce se
descarcă și se instalează de la www.python.org. CPython este scris în limbajul C. CPython
compilează codul Python într-un bytecode intermediar care este interpretat de o mașină virtuală
CPython, denumită VMP (Virtual Machine Python). Oferă un grad mare de compatibilitate
între diferite platforme, biblioteci (pachete Python) și module scrise în C.
PyPy – este un interpretor dezvoltat pe baza unui subset al Python denumit RPython. Oferă un
optim de viteză și performanță (apreciat ca fiind de 4,5 ori mai rapid ca CPython). Vine cu un
compilator JIT (Just In Time) care îl ajută să aibă un spor de viteză în condițiile unui consum
de memorie rezonabil.
Jython – face conversia codului sursă Python în bytecode Java care se poate rula pe mediile
JVT (Java Virtual Machine).
IronPython – este o implementare open – source integrată cu mediul .NET Framework.
Compilatoare Python
Compilatoarele produc cod-obiect direct executabil (“stand-alone”) din surse Python.
Pyinstaler – este cel mai cunoscut și utilizat compilator de programe sursă Python în
executabile directe, existând versiuni pentru toate cele mai cunoscute sisteme de operare:
Windows, Linux, Mac OS, Solaris, AIX, etc.
py2exe – produce executabile doar pentru Windows.
Nuitka – solicită existența unui compilator C++.
Cython – compilează producând un cod în C, apoi utilizează un compilator de C pentru a obține
un executabil.
Alte compilatoare: cx-Freeze, py2App (MacOS), Pynsist.
Eugen Diaconescu Limbajul și ecosistemul de programare Python

CAPITOLUL 1

Elemente introductive în limbajul Python


1.1 Cuvintele cheie și identificatorii
1.1.1 Cuvintele cheie (keywords)

Cuvintele cheie sunt cuvinte a căror utilizare este rezervată în Python. Cuvintele cheie definesc
sintaxa și structura limbajului Python.
Nu se poate utiliza un cuvânt cheie ca nume de variabilă, nume de funcție sau ca orice alt
identificator.
În Python, cuvintele cheie sunt case sensitive, ceea ce înseamnă că la scrierea lor se sesizează
diferența dintre caracterele majuscule și minuscule.
În total, sunt 35 cuvinte cheie în Python. În timp, numărul lor s-a modificat destul de puțin.
Toate cuvintele cheie cu excepția lui True, False și None sunt scrise în lowercase. De exemplu:
False, None, True, and, as, assert, async, etc.

Observație. Lista cuvintelor cheie poate să difere în funcție de versiunea Python instalată. Se
poate obține lista completă în versiunea current utilizată de Python prin comenzile:
>>> import keyword
>>> print(keyword.kwlist)
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break',
'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally',
'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal',
'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

1.1.2 Identificatorii

Definiție. Un identificator este un nume dat unei entități variabilă, funcție, clasă, etc., rolul său
fiind să diferențieze o entitate de alta.

Reguli pentru scrierea identificatorilor

Identificatorii pot fi o combinație de litere mici (a – z), mari (A – Z), digiți (0 – 9), sau
underscore ( _ ) . Exemple corecte: mCifre, linie_1 și valoare_noua_citita .
1. Un identificator poate avea orice lungime.
2. Un identificator nu poate începe cu o cifră. Numele 5numar este invalid, dar numar5 este
permis.
3. Cuvintele cheie nu pot fi utilizate ca identificatori.
Exemplul 1.1 #identificatorul nu poate fi cuvânt cheie
lambda = 7

Output
File "<interactive input>", line 1
29 Elemente introductive în limbajul Python

lambda = 7
^
SyntaxError: invalid syntax

4. Nu se pot utiliza simbolurile speciale: !, @, #, $, % etc., în compunerea identificatorului.


Exemplul 1.2 #identificatorii nu pot conține simboluri speciale
a% = 0 #genereaza eroare

Output
File "<interactive input>", line 1
a% = 0
^
SyntaxError: invalid syntax

1.2 Declarația, indentarea și comentariile


1.2.1 Declarația Python

Pentru programatorii români sunt câteva aspecte de terminologie care trebuie lămurite. De
ajutor poate fi o comparație cu limbajul C, mai vechi și adesea cunoscut înainte de studiul
limbajului Python prin sistemul de educație existent.
În limbajul C există clar o diferențiere, nu numai lingvistică, dar și ca întrebuințare, între
termenii instrucțiune, declarație și definiție.
În limbajul C, în primul rând prin “instrucțiune” sunt denumite cuvintele cheie de tipul for,
if, while, etc. Apoi, în limbajul C, se face distincție între definiție și declarație.

Declarația care se poate referi, de exemplu, la tipul unei variabile, este doar o informare a
compilatorului pentru ca acesta să știe câtă memorie trebuie alocată pentru variabila respectivă
(dar inițializarea încă nu este făcută).
Definiția unei variabile presupune atât alocarea de memorie cât și inițializarea acesteia..
Atât declarația, cât și definiția au în limbajul C scopul de a preciza de la început tipul variabilei,
care nu se mai poate modifica în timpul programului.
În Python lucrurile sunt diferite. Python este un limbaj din categoria “cu deducerea tipului”
(limbaj type-inferred) la execuție, pentru care nu este necesară definirea tipului variabilei
la declarare.
În Python, instrucțiunea pe care o poate executa interpretorul este denumită declarație
(statement). De exemplu, a = 1 este o declarație de atribuire, deci o definiție. Declarațiile if,
for, while, etc. sunt alte tipuri de declarații ce vor fi discutate mai târziu.

Totuși în limba română, pentru Python, nu este greșită și utilizarea termenului “instrucțiune”.
În această lucrare, termenii “instrucțiune” și “declarație” vor fi utilizați cu același sens.
Declarația multi-linie, caracterul de continuare
În Python, sfârșitul unei instrucțiuni este marcată printr-un character newline. Este însă posibil
să se extindă o declarație peste mai multe linii utilizând caracterul de continuare (\). De
exemplu:
a = 1 + 2 + 3 + \
4 + 5 + 6 + \
7 + 8 + 9
30 Elemente introductive în limbajul Python

Aceasta a fost o continuare explicită de linie. În Python, continuarea de linie este implicită în
interiorul parantezelor și acoladelor ( ), [ ], { }. De exemplu, se poate implementa implicit
multilinia de mai sus astfel:
a = (1 + 2 + 3 +
4 + 5 + 6 +
7 + 8 + 9)

La fel se procedează și cu parantezele pătrate [ ] și acoladele { }. De exemplu:


colors = ['rosu',
'galben',
'albastru']

De asemenea, se pot pune mai multe instrucțiuni pe o singură linie utilizând simbolul (;):
a = 1; b = 2; c = 3

1.2.2 Indentarea în Python

Cele mai multe din limbajele de programare, ca de exemplu C, C++, sau Java utilizează
acoladele {} pentru a defini un bloc de cod. Python, în schimb, utilizează indentarea.
Un bloc de cod (corpul unei funcții, bucle, etc.) începe cu prima linie indentată și se termină la
prima linie neindentată. Mărimea indentării poate fi oricare, dar trebuie păstrată pentru tot
blocul.
În general, ca mărime a indentării se utilizează patru spații, obținute folosind tasta tab. De
exemplu:
for i in range(1,11):
print(i)
if i == 5:
break
Utilizarea indentării face ca programul scris în Python să apară curat și clar. Rezultatul
înseamnă programe coerente, având un aspect similar.
Indentarea poate fi omisă în continuările de linie, dar opțiunea de indentare rămâne o idee bună,
deoarece face codul mai ușor de citit. De exemplu:
if True:
print('Start calcul')
a = 5

și
if True: print('Start calcul'); a = 5

ambele secvențe sunt corecte și fac același lucru, dar prima formă este mai clară.
În cazul unei indentări incorecte se afișează IndentationError.
Observație. O indentare greșită poate conduce la erori de programare uneori greu de identificat
și eliminat.
Observație. Regulile de bună practică în programare recomandă totuși evitarea utilizării tastei
TAB pentru identare, preferându-se folosirea spațiilor.
31 Elemente introductive în limbajul Python

1.2.3 Comentariile

Comentariile sunt foarte importante în timpul redactării unui program. Acestea descriu ce se
întâmplă în interiorul programului, astfel încât o persoană care citește codul sursă poate să-l
înțeleagă mai repede, fără dificultate.
Detaliile cheie ale unui program recent scris se pot uita peste ceva timp. În acest caz, este
întotdeauna eficient să se explice acele detalii prin intermediul comentariilor. Așadar, a găsi
timp pentru a explica aceste detalii sau conceptele programului sub formă de comentarii este
întotdeauna o idee bună.
În Python, se utilizează simbolul hash (#) la începutul scrierii unui comentariu. Comentariul se
extinde până la caracterul rând nou / newline.
Interpretorul Python ignoră comentariile.
#Acesta este un comentariu
print('Salut!')

Comentarii multi-linie
Unele comentarii se pot extinde pe mai multe linii.
Pentru realizarea comentariilor multilinie sunt disponibile două metode.
A. O metodă de a realiza acest lucru este utilizarea simbolului (#) la începutul fiecărei linii. De
exemplu:
#De aici incepe calculul
#perimetrului și ariei pentru următoarele
#figuri geometrice: triunghi, patrat și cerc.

B. O altă cale de a face același lucru este utilizarea ghilimelelor triple (simple sau duble,
triplate), adică ''' sau """.
Ghilimelele simple (apostroful) triplate sau ghilimele duble triplate sunt folosite în general
pentru șiruri multilinie, dar și pentru comentarii multilinie.
Observație. Comentariile multilinie nu generează cod obiect suplimentar; excepție este doar
cazul docstring-urilor.
"""Acesta
este un exemplu
de comentariu multilinie"""

Șirul de documentare “docstring”


Un docstring este o prescurtare pentru un șir de documentare.
Docstring-ul este un literal de tip șir care apare imediat după definirea funcției, metodei, clasei,
sau modulului.
Docstring-urile sunt delimitate de ghilimele triple, putând fi scrise pe unul sau mai multe
rânduri. De exemplu:
def incr(n):
""" Functia incrementeaza valoarea """
return n + 1

Docstring-urile sunt associate unui obiect prin atributul __doc__. Acest lucru permite accesul
la docstringul obiectului, astfel:
32 Elemente introductive în limbajul Python

def incr(n):
"""Functia incrementeaza valoarea """
return n + 1
print(incr.__doc__)

Output
Functia incrementeaza valoarea

1.3 Variabilele, Constantele și Literalele


1.3.1 Variabilele Python

Definiție. O variabilă este o locație de memorie având un nume, utilizată pentru memorarea
unei date în memorie și al cărei conținut poate fi schimbat mai târziu în program. De exemplu:
lungime = 21

S-a creat variabila cu numele lungime. S-a atribuit valoarea 21 acestei variabile. Această
valoare se poate schimba:
lungime = 21
lungime = 23.7

Observație: În Python, nu se pot atribui în mod real valori variabilelor. Ceea ce se întâmplă de
fapt este memorarea în variabilă a referinței la obiect (adresa obiectului).
Atribuirea de valori variabilelor în Python
Atribuirea de valori variabilelor se face prin operatorul de atribuire “=”.
Examplul 1.3 #Declararea și atribuirea de valoare unei variabile
titlu = "Programarea PLC-urilor"
print(titlu)

Output
Programarea PLC-urilor
În secvența program de mai sus, s-a atribuit valoarea Programarea PLC-urilor variabilei
titlu. Apoi s-a imprimat valoarea atribuită variabilei titlu, adică Programarea PLC-
urilor.

Observație: Deoparece Python este un limbaj din categoria “cu deducerea tipului” (limbaj type-
inferred), pentru care nu este necesară definirea tipului variabilei, Python știe automat că
Programarea PLC-urilor este un șir și declară (neexplicit) variabila titlu ca șir.

Examplul 1.4 #Schimbarea valorii unei variabile


nume_site = "robofun.ro"
print(nume_site)
# atribuirea unei noi valori variabilei nume_site
nume_site = "emag.ro"
print(nume_site)

Output
robofun.ro
emag.ro

Exemplul 1.5 # Atribuirea de valori diferite la variabile multiple


33 Elemente introductive în limbajul Python

a, b, c = 8, 4.4, "abcdefg"
print(a)
print(b)
print(c)

Output
8
4.4
Abcdefg

Exemplul 1.6 # Atribuirea aceleiași valori la toate variabilele, deodată


x = y = z = "perimetru"
print(x)
print(y)
print(z)

Output
perimetru
perimetru
perimetru

1.3.2 Constante

Definiție. O constantă este un tip de variabilă a cărei valoare nu poate fi schimbată.


Declararea și atribuirea valorii unei constante în Python
De regulă, în Python, constantele sunt declarate și incluse într-un modul distinct. Modulul este
un fișier conținând variabile, funcții, etc. care sunt importate în fișierul principal. În interiorul
modulului, constantele sunt scrise cu majuscule.
Exemplul 1.7 #crearea și stocarea constantelor
Crearea unui fișier constant.py:
PI = 3.14
GRAVITATIA = 9.8

Crearea unui fișier prog.py:


import constant
print(constant.PI)
print(constant.GRAVITATIA)
Output
3.14
9.8
Observație. In realitate, nu se utilizează constante în Python. Numirea și scrierea lor cu
majuscule este doar o convenție pentru a le separa de variabile; totuși acest lucru nu împiedică
modificarea lor.

1.3.3 Reguli și convenții de denumire a variabilelor și constantelor

1. Numele de constante și variabile trebuie să fie o combinație de litere mici (lowercase, a -


z), mari (uppercase, A - Z), cifre (0 - 9) sau liniuța de subliniere (underscore, _ ). De exemplu:
studiu_de_caz
SEMN_CIRCULATIE
34 Elemente introductive în limbajul Python

microMotor
PrimulProiect

2. Este recomandat să se creeze nume care au un sens. De exemplu, viteza are mai mult sens
comparativ cu v.
3. In cazul numelor de variabile formate din două cuvinte, se utilizează caracterul underscore
( _ ) pentru a le separa. De exemplu:
anul_nasterii
data_angajarii

4. Pentru constante se utilizează majusculele. De exemplu:


PI
G
TEMP
VITEZA_SUNET

5. Nu se pot utiliza simbolurile special ca !, @, #, $, %, etc. în numele constantei sau variabilei.


6. Nu se poate începe numele unei variabile cu o cifră.

1.3.4 Literale
Definiție. Literalul este o dată conținută într-o variabilă sau constantă. Python conține mai
multe tipuri de literale, după cum se prezintă în continuare.
Literale Numerice
Literalele numerice sunt immutable (nu se pot schimba). Literalele numerice sunt de trei tipuri
numerice diferite: Integer, Float și Complex.
Exemplul 1.8 #Utilizarea literalelor numerice
a = 0b1011 #literal binar
b = 123 #literal zecimal
c = 0o620 #literal octal
d = 0x12c #literal hexazecimal
#Literal float
float_1 = 22.7
float_2 = 2.5e3
#Literal complex
z = 5.58j
print(a, b, c, d)
print(float_1, float_2)
print(x, x.imag, x.real)
Output
11 123 400 300
22.7 2500.0
5.58j 5.58 0.0

În secvența program de mai sus:


- s-au atribuit literale întregi unor variabile diferite: a este un literal binar, b este un literal
zecimal, c este un literal octal și d este un literal hexadecimal.
- la imprimarea unei variabile, toate literalele sunt convertite în valori zecimale.
- 22.5 și 2.5e3 sunt literale în virgulă flotantă. 2.5e3 este exprimarea cu exponențială și este
echivalentă cu 2.5*102.
35 Elemente introductive în limbajul Python

- În cazul variabilelor complexe (de exemplu, atribuirea valorii 7+3.33j variabilei x), se va
utiliza literalul real (x.real) și literalul imaginary (x.imag) pentru a crea părțile reale și
imaginare ale numerelor complexe.
Literale șir
Definiție. Un literal de tip șir este o secvență de caractere cuprinsă între apostrofuri. Se pot
utiliza apostroful simplu ('), dublu (") (ghilimelele se pot considera apostrofuri duble), sau
triplu (''') pentru șiruri. Un literal de tip character este un singur character cuprins între
apostrofuri simple sau duble.
Exemplul 1.9 #Utilizarea literalelor șir
sir1 = 'acesta este un sir delimitat de apostrof'
sir2 = "acesta este un sir delimitat de ghilimele"
char1 = 'A'
char2 = "B"
sir_multilinie1 = '''acesta este
un sir
multilinie delimitat de apostrof triplat'''
sir_multilinie2 = """acesta este
un sir
multilinie delimitat de ghilimele triplate"""
unicode = u"\u00dcnic\u00f6de"
raw_sir = r"sir \n brut"

print(sir1)
print(sir2)
print(char1)
print(char2)
print(sir_multilinie1)
print(sir_multilinie2)
print(unicode)
print(raw_sir)

Output
acesta este un sir delimitat de apostrof
acesta este un sir delimitat de ghilimele
A
B
acesta este
un sir
multilinie delimitat de apostrof triplat
acesta este
un sir
multilinie delimitat de ghilimele triplate
Ünicöde

sir \n brut

Observație. (1) Șirul u"\u00dcnic\u00f6de" este un literal Unicode care acceptă și alte
caractere decât cele engleze. În acest caz, \u00dc reprezintă Ü și \u00f6 reprezintă ö.
(2)r"sir \n brut" este un literal șir brut (raw), în care caracterul special \n inclus în
șir este interpretat ca text.
Literale booleene
Definiție. Un literal Boolean poate avea una dintre cele două valori: True sau False.
36 Elemente introductive în limbajul Python

Exemplul 1.10 #Utilizarea literalelor booleene în Python


În programul de mai jos se utilizează literalul boolean True și False. În Python, True reprezintă
valoarea 1 și False valoarea 0.
x = (1 == True)
y = (1 == False)
a = True + 10
b = False + 10

print("x =", x)
print("y =", y)
print("a:", a)
print("b:", b)
Output
x = True
y = False
a: 11
b: 10
Valoarea lui x este True deoarece 1 este egal cu True, iar valoarea lui y este False deoarece
1 nu este egal cu False.
Observație. Se pot utiliza True sau False în expresii numerice ca valori. Valoarea lui a este
11 deoarece se adaugă True (=1) la 10. Similar, b este 10 deoarece se adună False (=0) la 10.
Literale speciale
Definiție. Literalul special, None, se utilizează pentru a preciza că un câmp nu a fost creat încă.
Exemplul 1.11 # Utilizarea literalelor special în Python
bere = "In stoc"
vin = None
def produse(x):
if x == bere:
print(bere)
else:
print(vin)
produse(bere)
produse(vin)
Output
In stoc
None
În programul de mai sus, se definește o funcție produse. Funcția produse, afișează In stoc
când argumentul este bere, apoi când argumentul este vin, afișează None.
Colecții de literale
Există patru colecții diferite de literale: List, Tuple, Dict, și Set.
Exemplul 1.12 #Utilizarea colecțiilor de literale în Python
fructe = ["mar", "par", "gutuie"] #lista
numere = (1, 2, 3) #tuplu
alfabet = {'a':'apa', 'b':'bilet', 'c':'cui'} #dictionar
vocale = {'a', 'e', 'i' , 'o', 'u'} #set
print(fructe)
print(numere)
print(alfabet)
print(vocale)
37 Elemente introductive în limbajul Python

Output
['mar', 'par', 'gutuie']
(1, 2, 3)
{'a': 'apa', 'b': 'bilet', 'c': 'cui'}
{'e', 'a', 'o', 'i', 'u'}
În programul de mai sus se creează o listă de fructe, un tuplu de numere, un dicționar dict având
valori cu chei destinate fiecărei valori și un set de vocale.

1.4 Tipuri de date în Python


În Python, oricărei valori îi corespunde un tip de dată.
Definiție. Deoarece orice (entitate) este un obiect în programarea Python, tipurile de date sunt
de fapt clase, iar variabilele sunt instanțe (obiecte) ale respectivelor clase.
Există diferite tipuri de date în Python, din care unele mai importante sunt prezentate în
continuare (figura 1.1).

Fig.1.1 Tipurile de date în Python


1.4.1 Numerele în Python
Numerele întregi, numerele în virgule flotantă și numerele complexe reprezintă tipuri de date.
Ele sunt definite drept clasele int, float și complex în Python.
Pentru a afla cărei clase îi aparține o valoare sau o variabilă se poate utiliza funcția type().
Similar, funcția isinstance() este utilizată pentru a verifica că un obiect aparține unei anumite
clase.
Exemplul 1.13
a = 5
print(a, "este de tip", type(a))
a = 2.0
print(a, "este de tip", type(a))
a = 1+2j
print(a, "este de tip complex ?", isinstance(1+2j,complex))

Output
5 este de tip <class 'int'>
2.0 este de tip <class 'float'>
(1+2j) este de tip complex ? True

Întregii pot fi de orice lungime, limitarea este dată doar de memoria disponibilă.
38 Elemente introductive în limbajul Python

Un număr în virgulă flotantă are precizia de până la 15 zecimale. Partea întreagă și partea
flotantă/zecimală sunt separate de punct. 1 este un întreg, 1.0 este un număr în virgulă flotantă.
Numerele complexe sunt scrise în forma x + yj, unde x este partea reală, iar y este partea
zecimală.
Exemplul 1.14 #lungimea nelimitată a întregilor și precizia de 15 zecimale
>>> a = 1234567890123456789
>>> a
1234567890123456789
>>> b = 0.1234567890123456789
>>> b
0.12345678901234568
>>> c = 1+2j
>>> c
(1+2j)
Se observă că variabila flotantă b apare trunchiată.
1.4.2 Tipul boolean
În Python, o variabilă booleană este declarată prin cuvintele cheie True și False.
Observație. Python atribuie tipul la momentul execuției și nu are un cuvânt cheie, de exemplu
boolean pentru declararea tipului (ca în alte limbaje, de exemplu Java) .
În Python, din perspectiva POO, tipul boolean este o subclasă a clasei întregilor. Variabila
booleană memorează la adresa unde a fost declarată, valoarea 1 pentru ‘True’ și 0 pentru False.
Comparațiile oricărei expresii cu True sau False se vor face prin operatorul de identitate is,
fie prin operatorul de egalitate ==.
int(True) == 1
int(False) == 0

Observație importantă. Atenție, True și 1 nu sunt aceleași obiecte !


id(True) == id(1) va genera False (pentru conversia lui 1 în True se va utiliza bool(1)).
De asemenea, cu variabilele booleene poate lucra orice operator (într-un context numeric, True
și False se comportă similar ca variabilele 0 și 1):
True + True + True = 3
Toate obiectele din Python au asociate valori True, cu excepția obiectelor: None, False, 0
(în orice fel de tip: 0, 0.0, 0 + 0j, etc.), și a variabilelor cu conținut vid de orice fel (set,
dicționare, etc.).
Funcția built-in bool() se utilizează pentru a converti orice valoare într-un obiect boolean.
1.4.3 Liste
Definiție. Lista este o secvență ordonată de elemente și este una dintre cele mai utilizate tipuri
de date în Python, fiind foarte flexibilă. Nu este necesar ca toate elementele aparținând unei
liste să fie de același tip.
Declararea listei se face simplu, elementele sale sunt separate de virgule și încluse între
paranteze drepte [ ].
a = [2021, 7.7, 'Arges']
39 Elemente introductive în limbajul Python

Se poate utiliza operatorul de secționare/slicing/feliere [ ] pentru a extrage un element sau o


secvență de elemente din listă, precizând un index sau un interval de indecși.
Observație. Indexul startează de la 0 în Python.

Exemplul 1.15 #extragere elemente din listă prin slicing

a = [5,10,15,20,25,30,35,40]
# a[2] = 15
print("a[2] = ", a[2])
# a[0:3] = [5, 10, 15]
print("a[0:3] = ", a[0:3])
# a[5:] = [30, 35, 40]
print("a[5:] = ", a[5:])

Output
a[2] = 15
a[0:3] = [5, 10, 15]
a[5:] = [30, 35, 40]
Listele sunt mutabile, adică pot fi modificate.
Exemplul 1.16 #listele sunt mutabile
a = [1, 2, 3]
a[2] = 4
print(a)

Output
[1, 2, 4]

1.4.4 Tuplurile în Python

Definiție. Tuplul este o secvență ordonată de elemente, la fel ca o listă. Singura diferență este
ca tuplul este imutabil. Un tuplu odată creat, nu mai poate fi modificat.
Tuplurile sunt utilizate pentru protejarea datelor la scriere și sunt de obicei mai rapide decât
listele deoarece sunt mai simple, lipsindu-le unele elemente care le-ar permite să fie modificate
dinamic.
Un tuplu este delimitat de paranteze rotunde (), iar elementele sale sunt separate prin virgule.
xt = (5,'program', 1+3j)
Se poate utiliza operatorul de secvențiere/feliere/slicing [] pentru a extrage elementele, dar
valorile acestora nu se pot schimba.
Exemplul 1.17 #extragere elemente din tuplu
xt = (5,'program', 1+3j)
print("xt[1] = ", xt[1])
print("xt[0:3] = ", xt[0:3])
# Generare eroare, tuplurile sunt imutabile
xt[0] = 10

Output
xt[1] = program
xt[0:3] = (5, 'program', (1+3j))

Traceback (most recent call last):


. . . . .
40 Elemente introductive în limbajul Python

xt[0] = 10
TypeError: 'tuple' object does not support item assignment

Ultima afișare de mai sus demonstrează imutabilitatea tuplurilor.

1.4.5 Șirurile în Python

În Python, șirul (string) este o secvență de caractere Unicode. Reprezentarea șirurilor se face
cu ajutorul ghilimelelor simple sau duble. Șirurile multi-linie sunt delimitate de ghilimele
simple (apostrofuri) triplate: ''', sau ghilimele duble triplate: """.
Exemplul 1.18 #șiruri
s = "Sir pe o linie"
print(s)
s = '''Sir
multilinie'''
print(s)

Output
Sir pe o linie
Sir
multiline
Operatorul de secvențiere/feliere/slicing [ ] se poate utiliza cu șiruri, ca și în cazul tuplelor.
Asemănător, șirurile sunt imutabile, astfel că apare eroare dacă se încearcă modificarea unor
porțiuni din ele.
Exemplul 1.19 #șirurile sunt imutabile
s = 'Buna ziua!'
# s[3] = 'a'
print("s[3] = ", s[3])
# s[5:9] = 'cccc'
print("s[5:9] = ", s[5:9])
# Generare eroare, sirurile sunt imutabile in Python
s[5] ='w'

Output
s[3] = a
s[5:9] = ziua
Traceback (most recent call last):
. . . . .
s[5] ='w'
TypeError: 'str' object does not support item assignment

1.4.6 Mulțimea (set) în Python


Definiție. Mulțimea (set) este o colecție neordonată de elemente unice (nu sunt permise
duplicatele), delimitată de acolade { }.
Exemplul 1.20 #mulțimi (set)
a = {5,2,3,1,4}
# imprimarea unei variabile set
print("a = ", a)
# afisarea tipului variabilei a
print(type(a))

Output
41 Elemente introductive în limbajul Python

a = {1, 2, 3, 4, 5}
<class 'set'>
Se pot realiza operații ca reuniunea, sau intersecția a două mulțimi. În interiorul unei mulțimi,
nu există (se elimină) elemente duplicate, elementele unei mulțimi sunt unice.
Exemplul 1.21 #eliminarea duplicatelor într-o mulțime
a = {1,2,2,3,3,3}
print(a)

Output
{1, 2, 3}

Observație. Deoarece un set este o colecție neordonată, nu are sens operația de indexare
aplicată unei mulțimi. În consecință, operatorul de secvențiere [] nu funcționează.
Exemplul 1.22 #indecșii nu operează în set
>>> a = {1,2,3}
>>> a[1]
Traceback (most recent call last):
File "<string>", line 301, in runcode
File "<interactive input>", line 1, in <module>
TypeError: 'set' object does not support indexing

1.4.7 Dicționare

Definiție. Dicționarul este o colecție neordonată de perechi cheie – valoare.


Se utilizează în general ca mijloc de gestionare în cazul unor cantități mari de date. Dicționarele
sunt optimizate în special pentru operația de regăsire a datelor. Pentru a regăsi o valoare, trebuie
cunoscută cheia cu care este asociată.
Un dicționar se definește utilizând acoladele {}, fiecare element al său fiind o pereche
exprimată în forma key:value. Cheia și valoarea pot fi de orice tip:
>>> d = {1:'valoare','cheie':2}
>>> type(d)
<class 'dict'>
Se utilizează cheia pentru aflarea valorii associate. Invers nu este însă posibil.
Exemplul 1.23 #regăsirea pe baza cheii
d = {1:'valoare','cheie':2}
print(type(d))
print("d[1] = ", d[1]);
print("d['cheie'] = ", d['cheie']);
# Generare eroare dacă se încearcă regăsirea după valoare
print("d[2] = ", d[2]);

Output
<class 'dict'>
d[1] = valoare
d['cheie'] = 2
Traceback (most recent call last):

print("d[2] = ", d[2]);
KeyError: 2
42 Elemente introductive în limbajul Python

1.4.8 Conversia între două tipuri de date


Se poate face conversia între două tipuri diferite de date prin utilizarea diferitelor funcții de
conversie ca: int(), float(), str(), etc.
>>> float(5)
5.0
Exemplul 1.24 #diferite tipuri de conversii
#Conversia din virgulă flotantă în întreg trunchiază valoarea (prin rotunjire la întregul inferior)
>>> int(10.6)
10
>>> int(-10.6)
-10
Conversia în, sau dintr-un șir, trebuie să conțină valori compatibile.
>>> float('57.5')
57.5
>>> str(24)
'24'
>>> int('8g')
Traceback (most recent call last):

ValueError: invalid literal for int() with base 10: '8g'
Se poate converti o dată “secvență” dintr-un tip în altul.
>>> set([1,2,3])
{1, 2, 3}
>>> tuple({5,6,7})
(5, 6, 7)
>>> list('litera')
['l', 'i', 't', 'e', 'r', 'a']
La conversia într-un dicționar, elementele trebuie prezentate în perechi:
>>> dict([[1,2],[3,4]])
{1: 2, 3: 4}
>>> dict([(7,32),(9,56)])
{7: 32, 9: 56}

1.5 Conversia de tip și forțarea tipului (casting)


1.5.1 Conversia de tip
Procesul de conversie a valorii unui tip de dată (întreg, șir, float, etc.) în alt tip de dată se
numește conversie de tip. În Python se întâlnesc două variante de conversii de tip.
1. Conversia de tip implicită
2. Conversia de tip explicită
Conversia de tip implicită
În cazul conversiei implicite de tip, Python face conversia automată a unui tip de dată în alt tip
de dată. Acest proces nu cere implicarea programatorului.
Exemplul 1.25 # Conversia unui întreg în float
nr_int = 2134
nr_fl = 5.55
nr_suma = nr_int + nr_fl
43 Elemente introductive în limbajul Python

print("tip data nr_int:",type(nr_int))


print("tip data nr_fl:",type(nr_fl))
print("Valoare nr_suma:",nr_suma)
print("tip data nr_suma:",type(nr_suma))

Output
tip data nr_int: <class 'int'>
tip data nr_fl: <class 'float'>
Valoare nr_suma: 2139.55
tip data nr_suma: <class 'float'>>
În următorul exemplu se adăugă un șir la un întreg
Exemplul 1.26 #Adunarea unui șir (dată mai mare) cu un întreg (dată mai mică)
num_int = 123
num_sir = "456"
print("tip data num_int:",type(num_int))
print("tip data num_sir:",type(num_sir))
print(num_int+num_sir)

Output
tip data num_int: <class 'int'>
tip data num_sir: <class 'str'>
Traceback (most recent call last):

print(num_int+num_sir)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Comentariu. În sevența de program de mai sus s-au adunat două variabile num_int (întreg) și
num_sir (șir), dar s-a obținut eroare deoarece Python nu a reușit să facă conversie implicită
pentru această combinație de tipuri.
Totuși există o soluție și pentru acest caz prin intermediul conversiei explicite.
Conversia de tip explicită
În cazul conversiei explicite de tip, utilizatorul însuși face conversia tipului de dată al unui
obiect în tipul de dată dorit. Astfel, pentru a face conversia forțată de tip de dată se utilizează
funcții predefinite ca int(), float(), str(), etc.
Observație. Acest tip de conversie este denumit casting deoarece programatorul schimbă
(casts) tipul de dată al obiectului. Castingul de tip poate fi făcut printr-o funcție aplicată
expresiei.
Sintaxa:
<tip de data dorit>(expresie)

Exemplul 1.27 #Adunarea unui șir cu un întreg utilizând conversia explicită


num_int = 123
num_sir = "456"
print("tip data num_int:",type(num_int))
print("tip data num_sir:",type(num_sir))
num_sir = int(num_sir) # efectuare casting
print("tip data num_sir dupa casting:",type(num_sir))
num_sum = num_int + num_sir #adunarea este posibila acum,
#operanzii sunt de acelasi tip numeric
print("Suma num_int si num_sir:",num_sum)
print("tip data sum:",type(num_sum))
44 Elemente introductive în limbajul Python

Output
tip data num_int: <class 'int'>
tip data num_sir: <class 'str'>
tip data num_sir dupa casting: <class 'int'>
Suma num_int si num_sir: 579
tip data sum: <class 'int'>

Exemplul 1.28 #conversia unor tipuri în tipul boolean


>>> bool([])
False
>>> bool(['o valoare'])
True
>>> bool(' ')
True
>>> bool('un sir')
True
>>> bool(True)
True
>>> bool(False)
False
>>> bool(0)
False
>>> bool(None)
False
>>> bool(0.0)
False
>>> bool(1)
True
>>>
bool(-1)
True

Observații
1. Conversia de tip este conversia unui obiect de un anumit tip într-un alt obiect de tip diferit.
2. Conversia de tip implicită este făcută automat de către Python.
3. Python evită pierderea de date în conversia implicită prin conversia la tipul cel mai mare.
4. Conversia explicită a tipului mai este denumită și castingul de tip; utilizatorul folosește o
funcție pentru realizarea conversiei.
5. În castingul de tip, poate avea loc o pierdere de date dacă se forțează conversia la un tip
subimensionat.

1.6. Instrucțiunile de Intrare/Ieșire și import


Funcțiile pre-construite (built-in) print() și input() îndeplinesc sarcinile principale de intrare
și ieșire (Input/Output, I/O) în Python.
De asemenea, în Python mai sunt disponibile multe alte funcții pre-definite care pot fi folosite
imediat în consolă, în modul prompter. Alte câteva din funcțiile predefinite frecvent utilizate
pentru intrări și ieșiri standard în system (diferite de input() și print()), vor fi prezentate
la sfârșitul secțiunii.
45 Elemente introductive în limbajul Python

1.6.1 Funcția de ieșire print()

Funcția print() se utilizează pentru afișarea datelor la ieșirea standard (ecran). Se mai poate
folosi și pentru scrierea de date într-un fișier.
Exemplul 1.29 #afișarea pe ecran cu print()
print('Afisare pe ecran')

Output
Afisare pe ecran

Exemplul 1.30 #afișarea valorii unei variabile


x = 7
print('Valoarea lui x este', x)

Output
Valoarea lui x este 7

În alt doilea exemplu se observă că la imprimare a fost adăugat un spațiu între șir și valoarea
variabilei x; acest lucru este implicit, dar acest lucru se poate modifica.
Sintaxa funcției print()este:
print(*objects, sep = ' ', end = '\n', file = sys.stdout, flush = False)

Prin objects se înțelege valoarea (valorile) de imprimat.


Separatorul sep este utilizat între valori. Implicit, este un character spațiu.
Sfârșitul listei de valori de imprimat se termină cu imprimarea valorii atribuită lui end . Dacă
end = '\n' se produce trecerea la o linie nouă, altfel dacă în loc de '\n' este o altă valoare, aceasta
va fi imprimată marcând sfârșitul liniei.
Dacă end = '', se va continua pe aceeași linie.
Prin file se indică obiectul unde sunt imprimate valorile, implicit fiind sys.stdout (ecranul).
Exemplul 1.31 #utilizarea separatorilor
print(1, 2, 3, 4)
print(1, 2, 3, 4, sep = '!')
print(1, 2, 3, 4, sep = ';', end = '%')

Output
1 2 3 4
1!2!3!4
1;2;3;4%

Formatarea ieșirii
Uneori se dorește atribuirirea unui aspect mai atractiv pentru ieșirea la imprimantă. Acest lucru
poate fi făcut prin utilizarea metodei str.format(), aplicabilă unui obiect de tip șir.
>>> x = 5; y = 10
>>> print('x = {} si y = {}'.format(x,y))
x = 5 si y = 10
Parantezele tip acoladă doar marchează poziția valorii imprimate, păstrând ordonarea
variabilelor din format. Se poate însă specifica ordinea apariției valorilor utilizând un index.
Exemplul 1.32 #print() cu formatare prin .format()
46 Elemente introductive în limbajul Python

print('Prefer {0} și {1}'.format('rosu','alb'))


print('Prefer {1} și {0}'.format('rosu','alb'))

Output
Prefer rosu și alb
Prefer alb și roșu
Se poate utiliza și stilul cu argumente pentru formatarea șirului.
Exemplul 1.33 #print() cu formatare prin utilizarea argumentelor
>>> print('Bună {salutul}, {nume}'.format(salutul = 'dimineața', nume =
'Popescu'))
Bună dimineața, Popescu

Observație. Șirurile se pot formata asemănător cu modul în care se procedează în limbajul de


programare C, prin funcția printf(), utilizând operatorul % în acest scop.
Exemplul 1.34 #print() cu formatare similară limbajului C
>>> x = 78.3456789
>>> print('x = %3.2f' %x)
x = 78.35
>>> print('x = %3.4f' %x)
x = 78.3457

1.6.2 Funcția de intrare


În programe sunt utilizate nu numai date de tip constantă, sau inițializate de programator, ci și
date introduse de utilizatorul current al programului, Acest lucru este posibil cu funcția
input().

Sintaxa:
input([prompt])
unde prompt este șirul (opțional) dorit a se afișa pe ectran.
Exemplul 1.35 #introducere valoare de șir
>>>nr = input('Introduceti un numar: ')
Introduceti un numar: 25
>>> nr
'25'
Se observă că valoarea introdusă (25)este un șir, nu un număr. Pentru a converti acest șir într-
un număr, se poate folosi fie funcția int(), fie funcția float().
>>> int('25')
25
>>> float('25')
25.0

În consecință, pentru a se obține direct un număr utilizând funcția input, se procedează ca în


exemplul care urmează:
Exemplul 1.36 #introducere valoare numerică prin conversie cu int()
nr = int(input('Introduceți un numar: '))
Introduceți un numar: 39
>>> nr
39
Aceeași operație de conversie poate fi realizată utilizând funcția eval(). Această funcție poate
evalua chiar și expresii (nu doar simple valori) care îi sunt prezentate ca șiruri.
>>> eval('2+5')
47 Elemente introductive în limbajul Python

Observație. Obținem însă eroare dacă se încearcă:


>>> int('2+5')

Traceback (most recent call last):



int('2+5')
ValueError: invalid literal for int() with base 10: '2+5'

1.6.3 Importul modulelor


Când programul devine prea mare, apar dificultăți în editarea și operarea acestuia. Soluția este
divizarea sa în mai multe module.
Un modul este un fișier conținând definiții și declarații Python. Modulele Python au un nume
de fișier și o extensie .py.
Definițiile conținute de un modul pot fi importate într-un alt modul sau în interpretorul
interactiv Python. Acest lucru se face folosind cuvântul cheie import.
De exemplu, se poate importa modulul math, scriind următoarea linie de cod:
import math

Exemplul 1.37 #utilizarea modulului math


import math
print(math.pi)

Output
3.141592653589793

Nu toate definițiile conținute în interiorul modulului math pot fi utile scopului propus în
programul realizat. În acest caz se pot importa doar elementele și funcțiile necesare, folosind
cuvântul cheie from. De exemplu:
>>> from math import pi
>>> pi
3.141592653589793
La importul unui modul, Python analizează mai multe locații, definite în sys.path.
>>> import sys
>>> sys.path
['C:\\Users\\USER',
'C:\\ProgramData\\Anaconda3\\python37.zip',
'C:\\ProgramData\\Anaconda3\\DLLs',
'C:\\ProgramData\\Anaconda3\\lib',
. . . . . . . . .
'C:\\ProgramData\\Anaconda3\\lib\\site-packages\\win32\\lib',
'C:\\ProgramData\\Anaconda3\\lib\\site-packages\\Pythonwin',
'C:\\ProgramData\\Anaconda3\\lib\\site-packages\\IPython\\extensions',
'C:\\Users\\USER\\.ipython']
. . . . . . . . . . . .
La această listă se pot adăuga și alte locații care interesează utilizatorul.
48 Elemente introductive în limbajul Python

1.7 Operatorii în Python


Definiția operatorilor
Operatorii sunt simboluri speciale în Python cărora li se asociază calcule logice sau aritmetice.
Operatorii se aplică unor valori denumite operanzi. De exemplu:
>>> 4+3
7
În acest caz, operatorul + face ca operanzii 4 și 3 să se adune, rezultatul fiind suma lor.

1.7.1 Operatorii aritmetici


Operatorii aritmetici sunt folosiți pentru a face operații matematice ca adunarea, scăderea,
înmulțirea, etc.
Operator Semnificație Exemplu
+ Adunare doi operanzi (sau plus unar) x + y+ 2
- Scăderea operandului din dreapta din x - y- 2
cel din stânga (sau minus unar)
* Înmulțire doi operanzi x * y
/ Împărțire operand stânga la x / y
operand dreapta, rezultat de tip float
% Rest-modulo la împărțirea operandului x % y
din stânga prin cel din dreapta (restul
lui x/y)
// Împărțire (floor division) cu rezultat x // y
întregul inferior cel mai apropiat de cât
** Operandul din stânga ridicat la puterea x**y
egală cu operandul din dreapta (x la puterea y)

Exemplul 1.38
x = 5
y = 3
print('x + y =',x+y)
print('x - y =',x-y)
print('x * y =',x*y)
print('x / y =',x/y)
print('x // y =',x//y)
print('x ** y =',x**y)
print('x % y =',x%y)

Output
x + y = 8
x - y = 2
x * y = 15
x / y = 1.6666666666666667
x // y = 1
x ** y = 125
x % y = 2

1.7.2 Operatorii de comparație


Operatorii de comparație sunt utilizați pentru compararea valorilor. Ei întorc True sau False
în funcție de relația dintre valorile operanzilor.
49 Elemente introductive în limbajul Python

Operator Semnificație Exemplu


> Mai mare. True dacă operandul stâng este
mai mare ca operandul drept x > y
< Mai mic. True dacă operandul stâng este mai
mic ca operandul drept x < y
== Egal. True dacă operazii sunt egali x == y
!= Inegal (not equal). True dacă operanzii nu
sunt egali. x != y
>= Mai mare, sau egal. True dacă operandul din
stânga este mai mare sau egal cu cel din dreapta
x >= y
<= Mai mic, sau egal. True dacă operandul din stânga
este mai mic sau egal cu cel din dreapta. x <= y

Exemplul 1.39 #operatori de comparație


x = 10
y = 12
print('x > y este:',x>y)
print('x < y este:',x<y)
print('x == y este:',x==y)
print('x != y este:',x!=y)
print('x >= y este:',x>=y)
print('x <= y este:',x<=y)

Output
x > y este: False
x < y este: True
x == y este: False
x != y este: True
x >= y este: False
x <= y este: True

1.7.3 Operatorii logici


Operatorii logici sunt and, or, not.
Operator Semnificație Exemplu
and Adevărat dacă ambii operanzi sunt adevărați x and y
or Adevărat dacă unul din operanzi este adevărat x or y
not Adevărat dacă operandul este fals not x

Exemplul 1.40 #operatori logici


x = True
y = False
print('x and y =',x and y)
print('x or y =',x or y)
print('not x =',not x)
Output
x and y = False
x or y = True
not x = False
50 Elemente introductive în limbajul Python

1.7.4 Operatorii pe biți (bitwise)

Operatorii pe biți acționează asupra operanzilor ca și când aceștia ar fi șiruri de biți. Ei operează
bit cu bit, de aici numele lor.
De exemplu, în binar, 2 este reprezentat ca 10 și 4 este reprezentat ca 100.
În tabelul următor se prezintă operațiile posibile folosind operatorii pe biți, considerând x = 10
(0000 1010 în binar) și y = 4 (0000 0100 în binar)
Operator Semnificație Exemplu
& Bitwise AND x & y = 0 (0000 0000)
| Bitwise OR x | y = 14 (0000 1110)
~ Bitwise NOT ~x = -11 (1111 0101)
^ Bitwise XOR x ^ y = 14 (0000 1110)
>> Bitwise right shift x >> 2 = 2 (0000 0010)
<< Bitwise left shift x << 2 = 40 (0010 1000)

1.7.5 Operatorii de atribuire

Operatorii de atribuire sunt utilizați în Python pentru a atribui valori unor variabile.
Operatorul simplu de atribuire
a=5 atribuie valoarea din dreapta (5) variabilei din stânga (a).
Operatorii compuși de atribuire
De asemenea, există mai mulți operatori compuși în Python, de exemplu a += 5 , care adună o
valoare la variabilă și apoi atribuie variabilei suma obținută. Acesta este echivalent cu a = a + 5.
Tabel cu operatorii compuși de atribuire
Operatorul Exemplu Echivalent cu
= x = 5 x = 5
+= x += 5 x = x + 5
-= x -= 5 x = x - 5
*= x *= 5 x = x * 5
/= x /= 5 x = x / 5
%= x %= 5 x = x % 5
//= x //= 5 x = x // 5
**= x **= 5 x = x ** 5
&= x &= 5 x = x & 5
|= x |= 5 x = x | 5
^= x ^= 5 x = x ^ 5
>>= x >>= 5 x = x >> 5
<<= x <<= 5 x = x << 5
51 Elemente introductive în limbajul Python

1.7.6 Operatorii speciali

Limbajul Python oferă două tipuri speciale de operatori: operatorii de identitate și operatorii de
apartenență. Aceștia sunt descriși în continuare, cu exemple.
Operatorii de identitate
Operatorii de identitate în Python sunt is și is not . Ei sunt utilizați pentru a se verifica dacă
două valori (sau variabile) sunt localizate în aceeași memorie. Dacă două variabile sunt egale,
nu înseamnă că implicit sunt și identice.
Operator Semnificație Exemple
is Adevărat dacă operanzii sunt identici (se referă la
același obiect) x is True
is not Adevărat dacă operanzii nu sunt identici
(nu se referă la același obiect) x is not True

Exemplul 1.41 #testare operatori de identitate


x1 = 5
y1 = 5
x2 = 'operand'
y2 = 'operand'
x3 = [1,2,3]
y3 = [1,2,3]
print(x1 is not y1)
print(x2 is y2)
print(x3 is y3)

Output
False
True
False
Se vede că x1 și y1 sunt întregi de aceeași valoare, astfel că ei sunt atât egali, cât și identici.
Aceeași situație e valabilă și pentru x2 și y2 (șiruri).
x3 și y3 sunt liste. Ele sunt egale, dar nu identice, deoarece interpretorul Python le dispune
separat în memorie, deși ele sunt egale.
1.7.7 Operatorii de apartenență
Operatorii de apartenență în Python sunt in și not in. Ei sunt utilizați pentru a testa dacă o
valoare sau o variabilă se găsește conținută într-o secvență (șir, listă, tuplu, dicționar).
Observație. Într-un dicționar se poate testa doar prezența unei chei, nu a valorii sale.
Operator Semnificație Exemplu
In Adevărat dacă valoarea/variabila este
găsită în secvență 5 in x
not in Adevărat dacă valoarea/variabila nu este
găsită în secvență 5 not in x

Exemplul 1.42 #testare operatori de apartenență


x = 'Drum bun!'
y = {1:'a',2:'b'}
print('D' in x)
print('drum' not in x)
print(1 in y)
52 Elemente introductive în limbajul Python

print('a' in y)

Output
True
True
True
False
Se observă că 'D' este în x , dar 'drum' nu este present în x (de reamintit, Python este sensibil
la majuscule). Similar, 1 este cheie și 'a' este valoare în dictionarul y. Din acest motiv, expresia
'a' in y întoarce False.

1.8 Spațiul numelor (namespace) și domeniul de vizibilitate (scope)


1.8.1 Numele în Python
Numele în Python (mai poate fi înțeles și ca identificator) este numele dat unui obiect (orice, în
Python, este un obiect !). Numele reprezintă calea de acces către obiect.
De exemplu, dacă se face atribuirea a = 2, atunci 2 este un obiect stocat în memorie, iar a este
numele asociat lui. Se poate afla adresa în memoria RAM a unui obiect utilizând funcția
preconstruită id(). În continuare se prezintă un exemplu de utilizare a acesteia.
Exemplul 1.43 #ilustrare funcția built-in id()- aflarea adresei de memorie a unui obiect
a = 2
print('id(2) =', id(2))
print('id(a) =', id(a))

Output
id(2) = 9302208
id(a) = 9302208
Aici, ambele referințe sunt la același obiect 2, astfel ca se obține același id(). Dacă se face o
mică modificare, se pot obține diferite valori pentru id().
Exemplul 1.44 #ilustrare funcția built-in id(), legarea dinamică
a = 2
print('id(a) =', id(a))
a = a+1
print('id(a) =', id(a))
print('id(3) =', id(3))
b = 2
print('id(b) =', id(b))
print('id(2) =', id(2))

Output
id(a) = 9302208
id(a) = 9302240
id(3) = 9302240
id(b) = 9302208
id(2) = 9302208

Rezultatul de mai sus se explică în felul următor:


- Primul creat a fost obiectul 2, căruia i s-a asociat numele a, iar când s-a făcut a = a+1, a fost
creat un nou obiect 3, acesta fiind asociat acum cu numele a. De notat că id(a) și id(3) au
aceleași valori.
53 Elemente introductive în limbajul Python

- În continuare, când b = 2 este executat, noul nume b devine asociat cu obiectul anterior 2.
Observație. Acest mod de lucru este eficient deoarece Python nu mai trebuie să creeze un nou
duplicat al obiectului. Această legare dinamică a numelor (name binding) face puternic limbajul
Python. Un nume poate fi referință la orice tip de obiect.
Exemplul 1.45 #ilustrare funcția built-in id(), redenumirea obiectelor
>>> a = 5
>>> id(a)
140711384218720
>>> a = 'Hello World!'
>>> id(a)
1677728483568
>>> a = [1,2,3]
>>> id(a)
1677728522523

Toate cele trei linii de cod sunt valide, iar a se referă la trei tipuri de obiecte diferite în trei
cazuri diferite, create succesiv.
Funcțiile sunt de asemenea obiecte, astfel că un nume poate de asemenea să facă referință și la
ele.
Exemplul 1.46 #ilustrare funcția built-in id()aplicată la numele funcțiilor
def Salut():
print("Salut!")
a = Salut
a()
id(a)

Output
Salut!
140711383762960
În codul de mai sus, numele a face referire la o funcție. Funcția poate fi apelată apoi utilizând
numele a.

1.8.2 Spațiul numelor (namespace)

Un spațiu al numelor, sau namespace, reprezintă o colecție de nume.


În Python, un namespace poate fi imaginat ca o colecție a tuturor corespondențelor între
obiectele și numele associate lor, conținute de acel spațiu.
Diferite spații de nume pot coexista în același timp, dar sunt complet izolate între ele.
La startul interpretorului Python se creează un spațiu de nume conținând toate numele
preconstruite (built-in names), care există atât timp cât rulează interpretorul.
Acesta este motivul pentru care funcțiile preconstruite, ca id(), print() etc., sunt întotdeauna
disponibile în orice parte a programului. Fiecare modul își creează propriul namespace global.
Deoarece spațiile numelor sunt izolate, în cazul că un același nume există în fiecare spațiu,
totuși nu apar coliziuni între acestea.
Modulele pot avea diverse funcții și clase. La apelul unei funcții se creează un namespace care
conține toate numele definite în funcție. Similar se întâmplă în cazul claselor.
O ierarhizare a spațiilor de nume în Python este prezentată de diagrama din figura 1.2.
54 Elemente introductive în limbajul Python

Built-in

Global (În Modul)

Local (În Funcții)

Fig. 1.2 Spațiile de nume standard

1.8.3 Domeniul de vizibilitate al variabilelor (scope)

Într-un program se pot defini diferite spații ale numelor unice. Acestea nu pot fi însă accesate
toate din oricare parte a programului.
Un domeniu de vizibilitate (scope) este o parte a programului din care un spațiu de nume
(namespace) poate fi accesat direct fără a folosi vreun prefix.
În orice moment, sunt disponibile cel puțin trei domenii de vizibilitate încuibate.
1. Domeniul de vizibilitate al funcției curente - conținând numele locale.
2. Domeniul de vizibilitate al modulului - care conține nume globale.
3. Domeniul de vizibilitate cel mai cuprinzător - care conține numele preconstruite (built-in
names).
Dacă se face o referință printr-un nume în interiorul unei funcții, atunci numele este căutat mai
întâi în spațiul de nume local, apoi în spațiul de nume global și în final în spațiul de nume built-
in (care conține numele funcțiilor preconstruite).
Dacă o funcție se utilizează în interiorul unei funcții, atunci un nou domeniu de vizibilitate
(scope) este încuibat în domeniul de vizibilitate local.
Exemplul 1.47 #ilustrare scope și namespace
a = 11
def functie():
b = 33
def functie_interna():
c = 55

În exemplul de mai sus, variabila a este în spațiul de nume global. Variabila b este în spațiul de
nume local al funcției functie(), iar c este în spațiul de nume local, încuibat, al funcției
functie_interna().

Când execuția se desfășoară în funcția functie_interna(), atunci variabila c este locală, b


este nonlocală, iar a este globală. Se pot citi sau scrie noi valori în c, dar variabilele b și a se
pot doar citi în interiorul funcției functia_interna().
55 Elemente introductive în limbajul Python

Dacă se încearcă să se atribuie o valoare lui b în funcție_interna(), o nouă variabilă b este


creată în numele de spațiu local care este diferit de numele nonlocal b. Același lucru se întâmplă
și când se atribuie o valoare lui a.
Totuși, dacă se declară în funcție_interna() variabila a ca global, atunci toate referințele
și atribuirile lui a sunt destinate variabilei globale a. Similar, dacă se dorește re-legarea de
variabila b, atunci ea trebuie declarată ca nonlocală.
Exemplul 1.48 # a este variabilă locală
def functia():
a = 15
def functia_interna():
a = 30
print('a =', a)
functia_interna()
print('a =', a)
a = 5
functia()
print('a =', a)

Output
a = 30
a = 15
a = 5

În programul de mai sus, trei variabile diferite a sunt definite în spații de nume separate, și
accesate în fiecare spațiu.
În programul următor, toate referințele și atribuirile se fac la variabila globală a, datorită
utilizării cuvântului cheie global.
Exemplul 1.49 # a este definită ca variabilă globală în functia_internă()
def functia():
global a
a = 20
def functia_interna():
global a
a = 30
print('a =', a)
functia_interna()
print('a =', a)
a = 10
functia()
print('a =', a)

Output
a = 30
a = 30
a = 30

1.9 Mutabil, imutabil și alias


Tipurile de date ale căror valori pot fi schimbate după ce sunt create sunt denumite mutabile.
Listele și dicționarele sunt astfel de exemple.
>>>xlista = [2, 4, 5, 3, 6, 1]
>>>xlista[0] = 9
56 Elemente introductive în limbajul Python

>>>xlista
[9, 4, 5, 3, 6, 1]

Tuplele și șirurile sunt exemple de date imutabile. Conținutul lor nu poate fi modificat după
ce au fost create.
>>>xtuplu = (2, 5, 3, 1)
>>>xtuplu[0] = 9
Traceback (most recent call last):
. . . . . .
xtuplu[0] = 9
TypeError: 'tuple' object does not support item assignment

În legătură cu mutabilitatea există aspectul denumit aliasing. Un alias este un alt nume de
variabilă pentru aceeași dată. În acest caz, modificând valoarea unui nume se modifică și
valoarea celuilalt nume (aliasul).
Exemplul 1.50 # numele alias - proprietăți
>>>xlista_1 = [1, 2, 3, 4, 6]
>>>xlista_2 = xlista_1 #atribuire alias (alt nume)
>>>xlista_2[-1] = 5
>>>xlista_1
[1, 2, 3, 4, 5]

Acest lucru se întâmplă deoarece atât xlista_1 cât și xlista_2 referă aceeași adresă de
locație de memorie.
>>>xlista_1 = [1, 2, 3, 4, 6]
>>>xlista_2 = xlista_1
>>>id(xlista_1) == id(xlista_2)
True

Se poate depăși această problemă prin utilizarea funcției de copiere copy, astfel:
>>>xlista_1 = [1, 2, 3, 4, 6]
>>>xlista_2 = xlista_1[:] # copiere !
>>>id(xlista_1) == id(xlista_2)
False
>>>xlista_2[-1] = 5
>>>xlista_2
[1, 2, 3, 4, 5]
>>>xlista_1 #modificarea din xlista_2 nu a modificat xlista_1
[1, 2, 3, 4, 6]

1.10 Întrebări, exerciții și probleme


1.10.1 Care va fi ieșirea secvenței următoare:
x = True * False +3
y = False * False + 9**2
print(x + y)
R1.10.1 84
1.10.2 Care din împărțirile de mai jos este floor division.
a. / b.// c.% d. Nici una din cele mentionate

R1.10.2 b
57 Elemente introductive în limbajul Python

1.10.3 Fie programul:


a = [8, 4, 16, 20]
b = [x/2 for x in a]
print(b)

Ieșirea programului va fi: [4, 2, 8, 10] ?


a. True b. False
R.10.3 False (mai exact, [4.0, 2.0, 8.0, 10.0], valori float. Rezultatul diviziunii prin “/” este
intotdeauna float).
1.10.4 Care nume de variabilă nu este corect:
a. x-var b. _xvar c.Xvar d.x_var
R1.10.4 a
1.10.5 Cum se creează o variabilă cu valoare numerică egală cu 7 ?
a. x = int(7) b. x = 7 c. 7 = x d.Toate răsp.sunt corecte.

R1.10.5. a și b

1.10.6 Cum se introduc comentariile în Python?


a. // acesta este un comentariu
b. # acesta este un comentariu
c. /* acesta este un comentariu
d. [* acesta este un comentariu *]

R1.10.6 b

1.10.7 Ce operator se poate utiliza pentru a compara două valori?


a. >< b. = c. <> d. ==
R1.10.7 d
1.10.9 În Python este același lucru: 'Python ' și "Python " ?
a. True b. False

R1.10.9 a
1.1010 Care este sintaxa corectă pentru a afișa "Salut " :
a. print " Salut!" b. echo "Salut!" c. print("Salut!") d. p("Salut!")

R1.10.10 c

1.10.11 Care este sintaxa corectă pentru afișarea tipului unei variable în Python ?
a. print(type(x)) b. print(typeof x) c. print(typeof(x)) d. print(type x)

R1.10.11 c

1.10.12 Pentru ce tip de dată se poate folosi operatorul in ?


a. Liste b. Dictionare c. Toate anterioare

R1.10.12 a

1.10.13 Care va fi ieșirea următoarei secvențe de program:


58 Elemente introductive în limbajul Python

n1 = -5
n2 = 2.5
print(n1//n2)
a) -3.0 b) -2 c} -2.0 d) 1.0

R1.10.13 c

1.10.14 Afirmația următoare: „Python nu este un limbaj compilat”, este:

a) adevărată b) falsă

R1.10.14 a

1.10.15 Care va fi ieșirea următoarei secvențe de program:


a1 = ["12", -21]
a2 = ["-3", -15]
print (a1 + a2)

R1.10.15 ['12', -21, '-3', -15]

1.10.16 Care este ieșirea următoarei secvențe de program:


x = 3
y = 5
x *= y*x + 1
print(x)

R1.10.16 Penultima instrucțiune este echivalentă cu:


x = x * (y * x + 1)

În consecință, ieșirea este egală cu 48.


Eugen Diaconescu Limbajul și ecosistemul de programare Python

CAPITOLUL 2

Controlul fluxului de prelucrare


2.1 Concepte
În programare, controlul fluxului de prelucrare este ordinea în care declarațiile, instrucțiunile
sau apelurile de funcții sunt evaluate sau executate.
Controlul fluxului de prelucrare se face de regulă prin intermediul unor instrucțiuni dedicate
acestui proces. Aceste instrucțiuni (if, if-else, match/case, while, for – în Python,
în plus switch, do while, goto, etc., în alte limbaje) determină, în funcție de rezultatul
evaluării logice a unei expresii (True, False), care va fi următoarea instrucțiune care se va
executa.
Sublinierea explicită a controlului fluxului într-un program face diferența între un limbaj
imperativ (procedural) și un limbaj declarativ (neprocedural, sau funcțional).
Python îmbină caracteristici din ambele categorii.
În Python, structurile pentru controlul fluxului se împart în următoarele tipuri:
- Construcții condiționale care determină fluxul de acțiuni ca urmare a evaluării unei expresii
(rezultând True, sau False).
- Structuri condiționale de tipul if, if-else, sau if-elif-else.
- Instrucțiuni match-case care produc ramificări pe mai multe căi prin comparația unei valori
cu constante specificate și determinând o acțiune în funcție de prima constantă “potrivită”
(similar doar într-o anumită măsură cu switch-case din C/C++).
- Instrucțiuni pentru bucle cu cicluri cu număr controlat (for).
- Instrucțiuni pentru bucle repetitive până la îndeplinirea unor condiții (while).
- Instrucțiuni pentru bucle repetitive determinate de parcurgerea unor colecții. Sunt
construcții speciale care permit parcurgerea elementelor membre ale unor colecții de date.
2.2 Construcția condițională
Construcțiile condiționale în Python sunt expresii condiționale, scrise astfel:
>>> z = x if c else y

Condiția c este evaluată prima. Dacă rezultatul este True, se evaluează x și se întoarce valoarea
lui x, iar dacă rezultatul este False, se evaluează y și se întoarce valoarea lui y.
Expresia condițională poate fi scrisă utilizând operatori aritmetici și logici.
Observație. Se reamintește că în limbajul C/C++ (și în alte limbaje) există expresia
condițională de forma:
z = c ? x : y

Se constată că ordinea operanzilor este diferită (cxy vs. xcy).


Exercițiul 2.1 #construcția (expresia) condițională
a = int(input('Numarul = '))
nr = 'par ' if a % 2 == 0 else 'impar '
print('este ' + nr)
60 Controlul fluxului de prelucrare

Output 1
Numarul = 5
este impar

Output 2
Numarul = 6
este par

2.3 Instrucțiunea if … else în Python


Definiție. Prin instrucțiunea if … else se ia o decizie sau alta în funcție de cum este îndeplinită
o condiție (adevărat sau fals).
În Python se utilizează mai multe forme ale
instrucțiunii de decizie if.

a) if …
b) if … else
c) if … elif … else
d) if … elif … elif … … else

a) Sintaxa instrucțiunii if …

if test_expresie:
instrucțiuni Fig. 2.1 Diagrama logică a instrucțiunii
if în Python
La execuție, programul evaluează test_expresie și va executa instrucțiunile doar dacă
expresia testată este adevărată (True), figura 2.1.
Dacă expresia testată este falsă (False), instrucțiunile nu sunt executate.
În Python, corpul instrucțiunii if este indicat prin indentare. Începutul corpului este marcat de
o indentare, iar sfârșitul său este delimitat de prima linie neindentată care îi urmează, sau cu
indentare diferită.
Observație. Python interpretează valorile diferite de zero ca True, iar valorile 0 și None ca
False.

Exemplul 2.2 #instrucțiunea if …

# Se imprima un mesaj, dacă numărul este pozitiv

num = 3
if num > 0:
print(num, "numărul este pozitiv")
print("Mesaj dupa sfarsit if)")

num = -1
if num > 0:
print(num, " numărul este pozitiv")
print("Mesaj de asemenea dupa sfarsit if")

Output
61 Controlul fluxului de prelucrare

3 numărul este pozitiv


Mesaj dupa sfarsit if
Mesaj de asemenea dupa sfarsit if

Expresia de test din exemplul de mai sus este num > 0.


Corpul lui if este executat numai dacă rezultatul evaluării este True.
Când variabila num este egal cu 3, expresia testată este True și ca urmare instrucțiunea din
corpul lui if este executată.
Dacă variabila num este egală cu -1, expresia de test este False, iar instrucțiunea din
interiorul corpului if este sărită.
O instrucțiune print() este utilizată pentru a marca precis rezultatul execuției instrucțiunii
if.

b) Sintaxa lui if...else

if expresie_test:
corpul lui if
else:
corpul lui else

Instrucțiunea if...else evaluează


expresie_test și va executa corpul lui if
doar când valoarea condiției testate este True,
figura 2.2.

Dacă valoarea condiției este False, corpul lui


else este executat. Pentru separarea Fig. 2.2 Diagrama logică în Python pentru
blocurilor este utilizată indentarea. if..else

Exemplul 2.3 #instrucțiunea if …else …

# Programul următor verifică dacă un număr este pozitiv sau negativ


# și afișează un mesaj potrivit
# Se testează două valori numerice
# num = 3 si num = -3
num = 3

if num >= 0:
print("Pozitiv sau zero")
else:
print("Număr negativ")

num = -3

if num >= 0:
print("Pozitiv sau zero")
else:
print("Număr negativ")

Output
Pozitiv sau zero
Număr negativ
62 Controlul fluxului de prelucrare

În exemplu de mai sus, când num este egal cu 3, expresia testată este True și corpul lui if
este executat, iar corpul lui else este sărit.
Dacă num este egal cu -3, expresia testată este False, corpul lui if este sărit, iar corpul lui
else executat.

Dacă num este egal cu 0, expresia testată este True și corpul lui if este executat, iar corpul
lui else este sărit.

c) Sintaxa lui if...elif...else

if expresie_test1:
Corpul lui if
elif expresie_test2:
Corpul lui elif
else:
Corpul lui else

Denumirea elif este o


prescurtare pentru else if.
Ea permite verificarea multi-
expresie.

Dacă rezultatul testului pentru


if este False, se verifică
condiția pentru blocul următor
elif , ș.a.m.d.
Fig. 2.3 Diagrama logică în Python pentru if...elif…else
Dacă toate condițiile sunt False, corpul lui else este executat, figura 2.3.

În funcție de condiție, doar un singur bloc dintre mai multe blocuri aparținând instrucțiunii
if...elif...else este executat.

Blocul if poate avea un singur bloc else, dar poate avea mai multe blocuri elif.

Exemplul 2.4 #instrucțiunea if...elif…else

'''Se verifica dacă numarul este pozitiv,


negativ, sau zero'''

num = 3.4

if num > 0:
print("Numar pozitiv")
elif num == 0:
print("Zero")
else:
print("Numar negativ")

num = -4.5

if num > 0:
print("Numar pozitiv")
63 Controlul fluxului de prelucrare

elif num == 0:
print("Zero")
else:
print("Numar negativ")

num = 0

if num > 0:
print("Numar pozitiv")
elif num == 0:
print("Zero")
else:
print("Numar negativ")

Output
Numar pozitiv
Numar negativ
Zero

d) Sintaxa lui if...elif...elif... else

În interiorul unei structuri de tipul if...elif...else pot exista mai multe clause elif.

Observație. Posibilitatea existenței mai multor clause elif este motivul pentru care Python
nu conține structura switch-case, pentru selecții multiple, întâlnită în alte limbaje. Se presupune
că structura switch-case se poate simula prin instrucțiunea if cu elif multiplu.
if expresie_1:
instrucțiune/instrucțiuni
elif expresie_2:
instrucțiune/instrucțiuni
. . . . . . . . . .
elif expresie_n:
instrucțiune/instrucțiuni
else:
instrucțiune/instrucțiuni

Instrucțiunea if încuibată

Poate exista o
instrucțiune if...elif...else în interiorul altei instrucțiuni
if...elif...else. Această structură este denumită încuibare (nesting) în limbajele de
programare.
Orice număr de instrucțiuni sau niveluri de încuibare pot fi încuibate. Singurul mod de marcare
a nivelului este utilizarea indentării. Deoarece acest lucru poate produce confuzie, este
recomandată evitarea incuibării, dacă nu este absolut necesară.

Exemplul 2.5 # Instrucțiunea if încuibată

'''În această secvență de program se introduce un număr care


este apoi verificat dacă este pozitiv, negativ, sau zero și
se afișează un mesaj corespunzător'''
64 Controlul fluxului de prelucrare

num = float(input("Introduceti un numar:"))


if num >= 0:
if num == 0:
print("Zero")
else:
print("Numar Pozitiv")
else:
print("Numar negativ")

Output 1
Introduceti un numar: 5
Număr Pozitiv

Output 2
Introduceti un numar: -1
Număr negativ

Output 3
Introduceti un număr: 0
Zero

Exemplul 2.6 #elif multiplu - Calculul indicelui de masă corporală IMC


Inaltimea=float(input("Inaltimea in centimetri = "))
Greutatea=float(input("Greutatea in Kg = "))
Inaltimea = Inaltimea/100
IMC=Greutatea/(Inaltimea*Inaltimea)
print("Indicele de masa corporala este = ",IMC)
if(IMC>0):
if(IMC<=16):
print("Slab")
elif(IMC<=18.5):
print("Moderat subponderal")
elif(IMC<=25):
print("Sanatos")
elif(IMC<=30):
print("Moderat supraponderal")
else: print("Obezitate")
else: print("introduceți date corecte sau reale")
print("IMC=", IMC)

Output
Inaltimea in centimetri = 170
Greutatea in Kg = 80
Indicele de masa corporala este = 27.68166089965398
Moderat supraponderal
IMC= 27.68166089965398

2.4 Instrucțiunea match-case


Instrucțiunea match-case a fost introdusă în Python 3.10.
Observație. Structura switch-case existentă în alte limbaje (de exemplu C/C++) este absentă
în Python. Instrucțiunea match-case este asemănătoare cu switch-case. Ea însă doar
suplinește parțial instrucțiunea switch-case, simplificând și prezentând mai clar unele situații.
65 Controlul fluxului de prelucrare

Instrucțiunea match-case compară valoarea unei variabile cu valori de diferite forme


(denumite și “pattern”). Ideea este compararea succesivă a variabilei până la potrivirea cu una
din forme (“pattern matching”).
Instrucțiunea match-case constă din trei entități:
- cuvântul cheie match
- una sau mai multe clauze case
- câte o expresie pentru fiecare case.
Sintaxa:
match nume_variabilă/expresie:
case pattern_1 :
#instructiune
case pattern_2 :
#instructiune
. . . . . . . . .
case pattern_n :
#instructiune

În structura de calcul de mai sus, clauza case constă dintr-un șablon/pattern (pattern_1,
pattern_2, etc.) care va fi potrivit prin comparație cu valoarea variabilei nume_variabila. În
cazul în care valoarea de adevăr a potrivirii este True pentru una din clauze, se va executa
blocul de instrucțiuni corespunzător acelei clauze.

Exemplul 2.7 #instrucțiunea match-case


x = 2
match 5*x+3:
case 10:
print('Potrivirea 1')
case 13:
print('Potrivirea 2')
case 15:
print('Potrivirea 3')
Output

Potrivirea 2

Observație. Pattern-urile pot fi:


- constante simple (numere întregi sau reale, valori de adevăr: True/False, etc.);
- constante cu nume (ex.: x.on, x.off);
- paternuri multiple produse prin operatorul OR (ex.: True|False);
- wildcard (underscore), corespunde altor valori decât cele testate explicit;
- colecții de valori (ex., liste: ['a','b','c','d']);
- gărzi (garda este în acest caz un pattern urmat de o expresie condițională if, de exemplu:
case n if n == 0:).

Observație. Se consideră că avantajele structurii match-case față de structura if cu elif


multiplu sunt o viteză mai mare de procesare și o claritate a codului mai bună.
66 Controlul fluxului de prelucrare

2.5 Bucla for


Definiție. Bucla for este utilizată în Python în legătură cu parcurgerea/iterarea unei secvențe
(list, tuple, string) sau cu obiecte iterabile. Iterarea unei secvențe se mai numește și traversare.
Sintaxa buclei for
for i in secventa:
Corpul lui for

unde i este variabila care ia valoarea


elementului din secventa la fiecare
iterație, figura 2.4.
Bucla continuă până la atingerea
ultimului articol al secvenței. Corpul
buclei for este separat de restul codului
utilizând indentarea.
Exemplul 2.8
# Suma numerelor dintr-o lista
Fig. 2.4 Funcționarea buclei for
# Lista numerelor
numere = [6, 5, 3, 8, 4, 2, 5, 4, 11]
# Variabila care conține suma
sum = 0
# parcurgerea listei
for val in numere:
sum = sum+val

print("Suma este = ", sum)

Output
Suma este = 48

Funcția range()
Se poate genera o secvență de numere utilizând funcția range().
range(10) va genera numere de la 0 to 9 (10 numere).
Se pot defini argumentele start, stop și mărime pas, astfel:
range(start, stop, mărime pas).

Mărimea implicită a pasului este 1, dacă nu este precizat altfel.


Observație. Despre obiectul range se spune că este "leneș/întârziat" în sensul că nu generează
toate numerele pe care le "conține" în momentul când este creat, deoarece ar fi ineficient să le
stocheze pe toate în memorie. Din acest motiv se memorează start, stop și mărime pas, iar
următorul număr va fi generat din mers. Totuși, nu este un iterator deoarece suportă operațiile
in, len și __getitem__ .

Pentru a forța această funcție să producă toate valorile la ieșire se poate utiliza funcția list().
Exemplul 2.9 #ilustrarea funcționării funcției range()
print(range(10))
print(list(range(10)))
67 Controlul fluxului de prelucrare

print(list(range(2, 8)))
print(list(range(2, 20, 3)))
Output
range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 3, 4, 5, 6, 7]
[2, 5, 8, 11, 14, 17]

Se poate utiliza funcția range() în buclele for pentru a itera o secvență de numere. Aceasta
se poate combina cu funcția len() pentru iterarea secvenței prin indexare.

Exemplul 2.10 #utilizarea funcției range() în bucla for


# Iterarea unei liste prin indexare
gen_muzica = ['pop', 'rock', 'jazz']
for i in range(len(gen_muzica)):
print("Gen muzica : ", i, gen_muzica[i])

Output
Gen muzica : 0 pop
Gen muzica : 1 rock
Gen muzica : 2 jazz

Bucla for cu else


O buclă for poate avea optional o condiție else . Blocul care urmează după else este executat
după ce elementele iterate ale secvenței s-au terminat (indexul a fost epuizat).
Cuvântul cheie break poate fi utilizat pentru oprirea parcurgerii buclei. Într-o astfel de situație,
partea else este ignorată, dacă există. În consecință, partea determinată de else se execută
doar dacă nu intervine un break.
Observație. Regulile de bună practică în programarea Python recomandă totuși evitarea
clauzei else în bucla for, dacă nu este strict necesar, din diverse motive.
Exemplul 2.11 #bucla for cu else
# Bucla imprimă elemente până ce indexul
#depășește numărul de elemente al listei.
digits = [0, 1, 5]
for i in digits:
print(i)
else:
print("Nu mai sunt elemente")

Output
0
1
5
Nu mai sunt elemente.

Exemplul 2.12 #execuția blocului else se face doar dacă nu a fost executat break
# notele unui student sunt afișate doar daca
#numele studentului exista in catalog
catalog = {'Ion': 9, 'Alex': 5, 'George': 7}
student_name = 'Mihai'
for student in catalog:
68 Controlul fluxului de prelucrare

if student == student_nume:
print(catalog[student])
break
else:
print('Numele nu a fost gasit')

Output
'Numele nu a fost gasit'

Exemplul 2.13 #exemplificare for - conversia numerelor romane în zecimale


bazanum = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000,
}
def RomanDecimal(romanNumeral):
sum = 0
for i in range(len(romanNumeral) - 1):
left = romanNumeral[i]
right = romanNumeral[i + 1]
if bazanum[left] < bazanum[right]:
sum -= bazanum[left]
else:
sum += bazanum[left]
sum += bazanum[romanNumeral[-1]]
return sum

print(RomanDecimal('XI'))

Output
11

2.6 Bucla while


Definiție. Bucla while este utilizată pentru a executa repetat o secvență de program atât timp
cît condiția (expresia) de test este adevărară.
În general, se utilizează bucla while atunci când nu se cunoaște anticipat numărul de repetări.

Sintaxa instrucțiunii while

while expresie_test:
Corpul lui while

În bucla while se testează mai întâi expresia, urmând să se execute corpul de instrucțiuni care
urmează doar dacă expresie_test a fost evaluată ca fiind True. După o iterație,
69 Controlul fluxului de prelucrare

expresie_test este evaluată din nou. Procesul iterare-verificare continuă până cînd
expresie_test devine False, figura 2.5.
În Python, corpul buclei while este delimitat prin indentare.
Corpul începe cu indentarea și se termină la prima linie neindentată.
Python interpretează orice valoare non-zero ca fiind True, iar None și 0 sunt interpretate ca
False.

Exemplul 2.14 #utilizare bucla while

#Însumarea primelor n
#numere naturale
#suma = 1+2+3+...+n
#Utilizatorul introduce n
n = int(input("n = "))
suma = 0
i = 1
while i <= n:
suma = suma + i
i = i+1
# Afișează suma
print("Suma este = ", suma)

Output

n = 10
Suma este 55 Fig. 2.5 Funcționarea buclei while
În secvența program de mai sus, expresia va fi True atât timp cât variabila contor i este mai
mica sau egală cu n (10 în exemplu)
În corpul buclei while este necesar să se crească valoarea variabilei contor, altfel se obține o
buclă infinită.

Bucla while cu else

Ca și în cazul buclei for, bucla while poate avea un bloc else optional.
Blocul else este executat în momentul în care condiția din bucla while este evaluată ca fiind
False.

Bucla while se poate termina cu o instrucțiune break. În acest caz, blocul else este ignorat.
În consecință, blocul else se execută doar dacă condiția este falsă și dacă nu apare un break.
Observație. Regulile de bună practică în programarea Python recomandă totuși evitarea
clauzei else în bucla while, dacă nu e strict necesar, din diverse motive.
Exemplul 2.15 #bucla while cu else
'''Utilizarea lui else
cu bucla while'''
contor = 0
while contor < 3:
print("Interior bucla")
contor = contor + 1
else:
70 Controlul fluxului de prelucrare

print("Interior else")

Output

Interior bucla
Interior bucla
Interior bucla
Interior else

La a patra iterație, condiția buclei while devine False. În consecință, blocul else este
executat.

2.7 Instrucțiunile break și continue


Definiție. Instrucțiunile break și continue se utilizează pentru modificarea fluxului normal
al unei bucle.
O secvență de program este executată ciclic în cadrul unei bucle până când expresia condiție
de test devine falsă, dar uneori apare situația că se dorește terminarea iterației curente, sau chiar
a întregii bucle fără (re)evaluarea expresiei. În acest caz, se utilizează instrucțiunile break și
continue.

2.7.1 Instrucțiunea break


Instrucțiunea break încheie bucla care o conține. Controlul programului este preluat de
instrucțiunea care urmează imediat după corpul buclei while.
Observație. Dacă instrucțiunea break se află în interiorul unei bucle imbricate (buclă în buclă),
instrucțiunea break va termina bucla interioară.
Sintaxa
break

Modul de lucru al instrucțiunii break în bucla for și în bucla while este prezentat mai jos.
A. break în bucla for:

n+1 for j in secvența:


n+2 #corpul buclei for
n+3 if conditie:
n+4 break #salt la n+6
n+5 #corpul buclei for #salt la n+2
n+6 #cod extern buclei for

B. break în bucla while:

n+1 while expresie_test:


n+2 #corpul buclei while
n+3 if conditie:
n+4 break #salt la n+6
n+5 #corpul buclei while #salt la n+2
n+6 #cod extern buclei while
71 Controlul fluxului de prelucrare

Exemplul 2.16 #break în interiorul buclei for

for val in "instructiune":


if val == "t":
break
print(val)
print("Terminat")

Output
i
n
s
Terminat

În secvența program de mai sus se parcurge șirul "instructiune". Se verifică apariția literei
t, iar când se confirmă se părăsește cu break bucla. În consecință, se afișează toate literele
până la t, după care bucla se încheie.

2.7.2 Instrucțiunea continue

Înstrucțiunea continue este utilizată pentru a sări restul codului în cadrul buclei, doar în
cursul iterației curente. Bucla repetitivă nu se încheie, dar se reia începând cu iterația
următoare.

Sintaxa
continue

Modul de lucru al instrucțiunii continue în buclele for și while este prezentat mai jos.
C. continue în bucla for:

n+1 for j in secvența:


n+2 #corpul buclei for
n+3 if conditie:
n+4 continue #salt la n+2
n+5 #corpul buclei for #salt la n+2
n+6 #cod extern buclei for

D. break în bucla while:

n+1 while expresie_test:


n+2 #corpul buclei while
n+3 if conditie:
n+4 continue #salt la n+2
n+5 #corpul buclei while #salt la n+2
n+6 #cod extern buclei while

Exemplul 2.17 #continue în interiorul buclei for

for val in "program":


if val == "g":
continue
print(val)
72 Controlul fluxului de prelucrare

print("Sfarsit")

Output
p
r
o
r
a
m
Sfarsit

Având instrucțiunea continue conținută în buclă, la întâlnirea caracterului g în șir, restul buclei
nu se mai execută. Se observă în exemplul de mai sus că toate literele, exceptând g, sunt
imprimate.

2.8 Instrucțiunea pass


Definiție. În limbajul de programare Python, există o instrucțiune care nu are nici un efect la
execuție, fiind neoperantă (no operation, NOP). Aceasta este instrucțiunea pass, care mai este
denumită și instrucțiunea nulă. Diferența dintre un comentariu și instrucțiunea pass în Python
este că în timp ce comentariul este total ignorat de interpretorul Python, instrucțiunea pass nu
este ignorată.
Instrucțiunea pass este folosită în general pentru marcarea locului unde se vor poziționa ulterior
anumite secvențe de cod.
Sintaxa
pass

Se utilizează în general ca înlocuitor temporar.


Exemplul 2.18 #instrucțiunea pass
'''pass este doar un inlocuitor pentru o funcționalitate
care va fi adăugată mai tarziu'''
sir = {'p', 'r', 'o', 'g', 'r', 'a', 'm'}
for val in sir:
pass
În același mod se poate proceda în cadrul unei funcții sau clase care nu conțin încă instrucțiuni.
def xFunct(args):
pass
class xClasa:
pass

2.9 Întrebări, exerciții și probleme


2.9.1 Care va fi ieșirea secvenței:
x = 0.1
for num in range (10):
x += 0.1
print (x == 1)

# A. True
# B. Error
73 Controlul fluxului de prelucrare

# C. False
# D. None

R2.9.1 Răspuns: C
2.9.2 Care va fi ieșirea secvenței:
for num in range(5):
pass
print(num)

A. 5
B. 4
C. Error
D. None

R2.9.2 Raspuns: B (indexul ia pe rând valorile: 0, 1, 2, 3, 4)


2.9.3 Explicați de ce secvența de program care urmeaza are rezultat = 56.
def fun(a):
if a > 30:
return 3
else:
return a + fun (a + 3)
print (fun(25))

R2.9.3 Raspuns: recursivitatea explicitată este:


fun(25)= 25+fun(28)=25+28 + fun(31) = 25 + 28 + 3 = 56

2.9.4 Se rulează secvența de program care urmează:


i = 1
while True:
if i % 2 ==0:
break
i +=1
print(i)
Care este ieșirea corectă produsă:
A. 0
B. Error
C. 1
D. 2

R2.9.4 Raspuns: D
2.9.5 Să se scrie o secvență de program pentru exemplificarea utilizării wildcardului “_” cu
instrucțiunea match-case.
R2.9.5
buton5 = 7 #valoare non-booleană
#buton5 = bool(7) #valoare booleană
match buton5:
case (True):
print("Sistemul este pornit")
exit()
case (False):
74 Controlul fluxului de prelucrare

print("Sistemul este oprit")


exit()
case (_):
print("Sistemul nu a fost inițializat")

Output
Sistemul nu a fost inițializat
2.9.6 Să se scrie o secvență de program pentru exemplificarea utilizării paternului multiplu
obținut cu operatorul logic OR / ( | ) , utilizând instrucțiunea match-case.
R2.9.6
VarTest = True
match VarTest:
case (True|False):
print("VarTest conține o valoare booleană")
case _ :
print("VarTest nu conține o valoare booleană")

Output
VarTest conține o valoare booleană

2.9.7 Să se scrie o secvență de program pentru verificarea unei colecții de valori, utilizând
instrucțiunea match-case.
R2.9.7
lista1 = ['a', 'b', 'c', 'd']
match lista1:
case ['e','f'] : print("e,f nu exista")
case ['a','b','c','d'] : print("a,b,c,d prezente")

Output
a,b,c,d prezente

2.9.8 Să se scrie o secvență de program pentru potrivirea cu constante cu nume, utilizând


instrucțiunea match-case.
R2.9.8
class comutator:
on = 1
off = 0
stare = 0
match stare:
case comutator.on :
print('Comutator = on')
case Comutator.off :
print('Comutator = off')
Output
Comutator = off

2.9.9 Să se scrie o secvență de program cu gardă, utilizând instrucțiunea match-case.


R2.9.9
n = -7
match n:
case n if n < 0:
75 Controlul fluxului de prelucrare

print("Numar negativ")
case n if n == 0:
print("Numar egal cu zero")
case n if n > 0:
print("Numar pozitiv")

Output
Numar negativ
2.9.10 Să se scrie o secvență de program utilizând for care să imprime o piramidă.
R2.9.10 Se scrie și se rulează codul:
def piramida(n):
k = 2*n - 2
for i in range (0, n):
for j in range(0, k):
print(end=" ")
k = k - 1
for j in range(0, i+1):
print("*", end = " ")
print("")
piramida(5)

Output
*
* *
* * *
* * * *
* * * * *
2.9.11 Să se scrie o secvență de program utilizând for care să imprime o piramidă inversată.
R2.9.11
def pattern_inv(n):
k = 2*n - 2
for i in range (n, -1, -1):
for j in range(k, 0, -1):
print(end=" ")
k = k + 1
for j in range(0, i+1):
print("*", end=" ")
print("")
pattern_inv(5)

Output
* * * * * *
* * * * *
* * * *
* * *
* *
*
2.9.12 Să se scrie o secvență de program utilizând for cu enumerate care să imprime lista și
să afișeze numărul de ordine al elementelor:
Lista_fructe = ["Cireasa", "Para", "Pruna", "Gutuie"]
76 Controlul fluxului de prelucrare

R2.9.12
Lista_fructe = ["Cireasa", "Para", "Pruna", "Gutuie"]
for numar, fruct in enumerate(Lista_fructe):
print(numar, fruct)

Output
0 Cireasa
1 Para
2 Pruna
3 Gutuie

2.9.13 Să se scrie codul care imprima:


1
12
123
1234
12345
R2.9.13
for i in range(1, 7):
for j in range(1, i):
print(j, end = " ")
print("\n")

Output
1
12
123
1234
12345
2.9.14 Cum se poate obține din intrarea dată mai jos, ieșirea care urmează:
input=[10,20,30,40,50,60,70,80,90,100]
output=[10,30,60,100]

R2.9.14
input = [x for x in range(10, 101, 10)]
output = []
y=0
i=2
while y < len(input):
output.append(input[y])
y=y+i
i=i+1
print(output)

Output
[10, 30, 60, 100]
2.8.15 Să se scrie o secvență de program folosind if…elif…else care să precizeze că un
număr este pozitiv par sau impar, sau zero, sau negativ.
R2.9.15
77 Controlul fluxului de prelucrare

x = int(input('prompt='))
if x > 0:
print("numar pozitiv")
if x%2 == 0:
print('numar par')
else:
print('numar impar')
elif x<0:
print('numar negativ')
else:
print('numarul este zero')
Eugen Diaconescu Limbajul și ecosistemul de programare Python

CAPITOLUL 3

Funcții, module și pachete


3.1 Funcții
3.1.1 Definiția funcției în Python

Definiție. În Python, funcția este un grup de instrucțiuni, având un nume, care îndeplinește o
acțiune determinată.
Funcțiile ajută la împărțirea unui program în părți mai mici, mai ușor de administrat. Pe măsură
ce un program crește, se observă mai bine avantajele modularizării prin funcții, deoarece
programul poate fi mai ușor organizat și controlat.
În plus, funcțiile permit evitarea repetițiilor și fac codul reutilizabil.
Sintaxa
def nume_functie(parametri):
"""șir documentar (docstring)"""
instrucțiuni

În definiția de mai sus a funcției se regăsesc următoarele componente:


1. Cuvântul cheie def care marchează începutul de antet al funcției.
2. Un nume unic de identificare a funcției. Regulile denumirii sunt la fel cu ai celorlalți
identificatori în Python.
3. Parametrii (argumentele) prin care se transferă funcției una sau mai multe valori. Prezența
parametrilor este opțională.
4. Un simbol (:) care marchează sfârșitul antetului funcției.
5. Șirul optional de documentare (docstring) care descrie ce face funcția.
6. Corpul funcției format din una sau mai multe instrucțiuni care au aceeași identare.
7. O instrucțiune return, opțională, întorcând o valoare din funcție.
Exemplul 3.1 #ilustrarea definiției funcției
def mes_vaccin(nume):
"""
Această funcție reamintește unei persoane
ziua cind este programată la vaccinare. Numele său
este comunicat sub forma de parametru funcției
"""
print("Domnule/Doamnă " + nume + ", luni sunteți programat/ă la
vaccinare!")

Apelarea unei funcții


Odată definită funcția, ea poate fi apelată dintr-o altă funcție, program, sau prompter, precizând
și parametrii, dacă e cazul.
79 Funcții, module și pachete

>>> mes_vaccin("Popescu G.")


Domnule/Doamnă Popescu G., luni sunteți programat/ă la vaccinare!

Tipuri de funcții
În Python, funcțiile pot fi clasificate în două categorii:
1. Funcții preconstruite (built-in).
2. Funcții definite de utilizator.

3.1.2 Docstringuri

Primul șir situat după antetul funcției se numește “docstring”, rolul său fiind să explice ce face
funcția. De notat că, deși este opțională, documentarea programului este în practică foarte
folositoare, fiind utilă pentru reamintirea detaliilor de realizare și funcționare ale codului.
Utilizând ghilimele simple sau duble, triplate, așa cum s-a văzut și în exemplul de mai sus,
docstringul poate fi extins la mai multe linii. Docstringul este disponibil programatorului ca
atribut al funcției având numele __doc__ . Atributul mes_vaccin.__doc__ se poate imprima
la consola Python.
Exemplul 3.2 #afișarea docstringului multilinie
>>> print(mes_vaccin.__doc__)

Output
Această funcție reamintește unei persoane
ziua cind este programată la vaccinare. Numele său
este comunicat sub forma de parametru funcției

3.1.3 Instrucțiunea return


Definiție. Instrucțiunea return este utilizată pentru a ieși din funcția apelată cu reîntoarcere la
locul de unde s-a făcut apelul.
Sintaxa
return [expresie]

Instrucțiunea return poate întoarce o expresie sau niciuna (întoarce obiectul None). Expresia
întoarsă reprezintă valoarea sau lista de valori rezultate ca urmare a evaluărilor în cadrul
funcției.
Exemplul 3.3 #Apel funcție fără return.
>>> print(mes_vaccin("Ionescu B."))

Output
Domnule/Doamnă Ionescu B., luni sunteți programat/ă la vaccinare!
None

Funcția mes_vaccin() imprimă direct un mesaj, dar nu conține instrucțiunea return.


Exemplul 3.4 #Apel funcție cu return
def val_absoluta(nr):
"""Această funcție întoarce
valoarea absoluta a unui număr introdus anterior"""
if nr >= 0:
80 Funcții, module și pachete

return nr
else:
return -nr
print(val_absoluta(5))
print(val_absoluta(-7))

Output
5
7

3.1.4 Domeniul de vizibilitate și durata de viață a variabilelor în cadrul


funcțiilor

Definiție. Domeniul de vizibilitate (scope) este partea programului unde variabila este
recunoscută.
Parametrii și variabilele definite în cadrul funcției nu sunt vizibile din exteriorul funcției. În
acest caz, domeniul de vizibilitate este local.
Definiție. Durata de viață a unei variabile este (coincide cu) perioada în care funcția există în
memorie. Dacă memoria este dealocată, sau este rescrisă, variabila dispare. În consecință,
durata sau timpul de viață (lifetime) există doar cât timp se execută funcția.
Variabilele locale sunt distruse în momentul în care are loc reîntoarcerea din funcție. Altfel
spus, funcția nu-și reamintește valorile variabilelor din apelurile sale anterioare.
Exemplul 3.5 #Domeniul de vizibilitate este interiorul funcției
x = 20
def xfunc():
x = 10
print("Valoarea în interiorul functiei=",x)
xfunc()
print("Valoarea în exteriorul functiei=",x)

Output
Valoarea în interiorul functiei= 10
Valoarea în exteriorul functiei= 20

În programul de mai sus, valoarea inițială a lui x este 20. Chiar dacă functia xfunc() a schimbat
valoarea lui x în 10, nu este afectat x în afara funcției. Deși apare același nume x, există de fapt
două variabile, una locală funcției, iar cealaltă, externă ei.
Pe de altă parte, variabilele externe funcției sunt vizibile în interior. Se spune că acestea au un
domeniu de vizibilitate global. Variabilele globale pot fi vizibile în interiorul unei funcții, dar
nu pot fi modificate decât dacă li se adaugă cuvântul cheie global.

3.1.5 Funcții cu argumente în Python

Definiție. Parametrii transmiși funcțiilor, în număr variabil, sub forma conținutului unei liste
plasate în antetul funcției, sunt denumiți argumentele funcției.
3.1.5.1 Funcții cu număr fix de argumente
Se observă că folosind funcția care urmează, de tipul “definită de utilizator”, dacă se apelează
folosind același număr de parametri egal cu cel folosit la definiție, nu se obține nici o eroare.
81 Funcții, module și pachete

Exemplul 3.6 #apelul cu același număr de argumente


def mes_vaccin(nume, zi):
"""
Această funcție reamintește unei persoane
ziua cind este programată la vaccinare. Numele său
este comunicat sub forma de parametru funcției
"""
print("Domnule/Doamnă " + nume + ", " + zi + " sunteți programat/ă la
vaccinare!")
mes_vaccin("Georgescu M.", "joi")

Output
Domnule/Doamnă Georgescu M., joi sunteți programat/ă la vaccinare!

Observație. Funcția mes_vaccin() are două argumente. Dacă se apelează folosind un număr
diferit de argumente, se va obține o eroare de la interpretor.
>>> mes_vaccin("Georgescu M.")
mes_vaccin() missing 1 required positional argument: 'zi'
>>> mes_vaccin()
mes_vaccin() missing 2 required positional arguments: 'nume' and 'zi'

3.1.5.2 Funcții cu număr variabil de argumente


În Python există trei posibilități de a defini o funcție care poate avea un număr variabil de
argumente.
A. Funcții cu argumente implicite
Argumentele unor funcții pot avea valori implicite.
Se pot atribui valori implicite argumentelor prin utilizarea operatorului de atribuire (=).
Exemplul 3.7 #argumente inițializate la momentul apelului
def mes_vaccin(nume, zi = "vineri"):
"""
Această funcție reamintește unei persoane
ziua cind este programată la vaccinare. Numele său
este comunicat sub forma de parametru funcției
"""
print("Domnule/Doamnă " + nume + ", " + zi + " sunteți programat/ă la
vaccinare!")
mes_vaccin("Georgescu M.", "joi")
mes_vaccin("Georgescu M.")

Output
Domnule/Doamnă Georgescu M., joi sunteți programat/ă la vaccinare!
Domnule/Doamnă Georgescu M., vineri sunteți programat/ă la vaccinare!

În această funcție, parametrul nume nu are o valoare implicită și în consecință este obligatoriu
să fie folosit în antetul funcției, la apelare.
Pe de altă parte, parametrul zi are valoarea implicită "vineri" și drept urmare poate fi optional
în timpul apelului. De asemenea, valoarea implicită poate fi suprascrisă.
Numărul de argumente din antetul funcției care pot avea valori implicite nu este restricționat.
Observație. Odată ce un argument are valoare implicită, toate argumentele de la dreapta sa
trebuie să aibă valori implicite. Acest lucru impune ca, la definiție, argumentele ne-inițializate
nu pot să urmeze după argumentele cu valori inițializate (implicite).
82 Funcții, module și pachete

De exemplu, dacă se definește funcția de mai sus, astfel:


def mes_vaccin(zi="vineri", nume):

Se va genera o eroare:
SyntaxError: non-default argument follows default argument

B. Apelul prin numele explicit al argumentelor

Când se apelează o funcție folosind parametri efectivi (adică valori), aceștia se atribuie
argumentelor corespunzător poziției lor.
De exemplu, folosind funcția de mai sus, mes_vaccin(), când este apelată ca
mes_vaccin("Georgescu M.", "joi"), valoarea " Georgescu M." este atribuită
argumentului nume și similar valoarea "joi" argumentului zi.
Observație. Python permite funcțiilor să fie apelate utilizând și numele argumentelor. Când se
apelează funcțiile în acest mod, ordinea argumentelor poate fi schimbată, ca în exemplul care
urmează.
Exemplul 3.8 #ordinea argumentelor, chiar inițializate, schimbată la apel
def mes_vaccin(nume, zi = "vineri"):
"""
Această funcție reamintește unei persoane
ziua cind este programată la vaccinare. Numele său
este comunicat sub forma de parametru funcției
"""
print("Domnule/Doamnă " + nume + ", " + zi + " sunteți programat/ă la
vaccinare!")

# ordinea inversată a argumentelor


mes_vaccin(zi="joi", nume="Popescu")
# un argument pozițional și un nume de argument precizat
mes_vaccin("Popescu", zi = "joi")
# eroare: argumentul pozitional urmează după cel precizat prin nume
#mes_vaccin(nume="Popescu", "joi") ->eroare

Argumentele poziționale se pot mixa cu argumentele identificate prin nume, la momentul


apelării.
Observație. Totuși, întotdeauna, argumentele precizate prin nume trebuie să urmeze celor
poziționale. Nerespectarea acestei reguli va produce eroare:
mes_vaccin(nume="Popescu", "joi")

Va apărea eroarea:
SyntaxError: positional argument follows keyword argument

Număr variabil de argumente

Uneori nu se cunoaște dinainte numărul de argumente cu care va fi apelată funcția. În Python


se permite apelul unei funcții cu un număr variabil de parametri.
În definiția funcției, se utilizează un asterisc (*) înaintea numelor argumentelor pentru a preciza
acest tip de apelare.
83 Funcții, module și pachete

Exemplul 3.9 #apel cu număr variabil de parametri


def culori_preferate(*culori):
"""Funcția afișează culorile
preferate """
for culoare in culori:
print("Culoare preferata: ", culoare)
culori_preferate("alb", "rosu", "verde", "galben", "albastru")

Output
Culoare preferata: alb
Culoare preferata: rosu
Culoare preferata: verde
Culoare preferata: galben
Culoare preferata: albastru
În acest exemplu, s-a apelat funcția utilizându-se mai multe argumente. Argumentele au fost
grupate într-un tuplu înainte de a fi trecute funcției. În interiorul funcției se utilizează o buclă
for pentru a se recepționa toate argumentele transmise la momentul apelului.

3.1.6 Recursivitatea

Definiție. Recursivitatea este un process de definire a unei entități prin aceasta însăși. O funcție
recursivă este o funcție care se apelează pe ea însăși.
Avantajele recursivității
1. Funcțiile recursive fac programul mai clar și mai elegant.
2. Un process complex poate fi divizat în subprograme mai simple prin utilizarea recursivității.
3. Codul generat este mai ușor de înțeles decât dacă se utilizează iterații încuibate.
Dezavantajele recursivității
1. Sunt cazuri când logica din spatele recursivității este mai greu de urmărit.
2. Apelurile recursive consumă mai multe resurse de memorie și timp de execuție.
3. Funcțiile recursive sunt mai dificil de depanat.
Exemplul 3.10 #ilustrarea recursivității la calculul factorialului
#Funcția factorial face produsul tuturor numerelor
#de la 1 până la acel întreg. De exemplu, factorial de 5 (notat 5!) este
#1*2*3*4*5 = 120.
def factorial(x):
"""Functia calculeaza
factorialul unui numar intreg"""
if x == 1:
return 1
else:
return (x * factorial(x-1))
nr = 3
print("Factorialul lui ", nr, "este", factorial(nr))

Output
Factorialul lui 3 este 6
84 Funcții, module și pachete

Când se execută funcția factorial() de mai sus, cu întregul 3, ea se apelează pe sine insăși
(recursiv) prin descreșterea numărului întreg dat ca argument, până ce acesta devine egal cu 1,
astfel:
factorial(3) # primul apel cu 3
3 * factorial(2) # al doilea apel cu 2
3 * 2 * factorial(1) # al treilea apel cu 1
3 * 2 * 1 # return din al treilea apel
3 * 2 # return din al doilea apel
6 # return din primul apel

De notat că orice funcție recursivă trebuie să aibă o condiție de oprire a apelurilor recursive,
altfel ea ar putea să se autoapeleze de un număr infinit de ori. În exemplul de mai sus, condiția
de oprire este n = 1.
Interpretorul Python limitează totuși adâncimea recursiunii la 1000, pentru a nu se depăși stiva
implicată. Dacă limita este depășită, apare RecursionError.

3.1.7 Funcțiile lambda


Definiție. În Python, se poate defini o funcție anonimă care nu are nume. În timp ce o funcție
normală (cu nume) este definită utilizând cuvântul cheie def, funcțiile anonime sunt definite
utilizând cuvântul cheie lambda. Acesta este motivul pentru care funcțiile anonime sunt
denumite funcții lambda.
Sintaxa
lambda argumente: expresie

Funcțiile lambda pot avea oricâte argumente, dar reunite într-o singură expresie, care este
evaluată și returnată.
Exemplul 3.11 #definirea unei funcții lambda
# Dublarea valorii variabilei, utilizând o funcție lambda
putere2 = lambda x: x**2
p2 = lambda x: x**2
print(putere2(7))
print(p2(9))

Output
49
81
În exemplul de mai sus, funcția lambda este: lambda x: x*2 , unde x este argumentul și x**2
este expresia ce trebuie evaluate și returnată. Funcția nu are nume, totuși întoarce un obiect
funcție căruia îi sunt atribuite numele putere2, respectiv p2. Cu aceste nume se poate face
un apel ca și cu o funcție normală.
Instrucțiunea:
putere2 = lambda x: x * 2

este echivalentă cu:


def putere2(x):
return x * 2
85 Funcții, module și pachete

Utilitatea funcțiilor lambda


Funcțiile lambda sunt utile în cazurile când este necesară o funcție fără nume pentru o scurtă
perioadă de timp.
În Python, dar și în alte limbaje, funcțiile lambda sunt folosite în general ca argumente în
funcțiile de ordin mai înalt (higher-order) care pot avea ca argumente alte funcții. Frecvent,
funcțiile lambda sunt utilizate de funcțiile preconstruite (built-in) ca de exemplu: filter(),
map() etc.

Exemplul 3.12 #funcție higher-order care are ca argument o funcție lambda


"""Funcția filter() are ca argumente o funcție (lambda) și o listă. Funcția
este apelată cu o listă inițială și o funcție lambda. Funcția produce o
listă nouă obținută prin utilizarea funcției lambda. În acest exemplu,
funcția filter()filtrează o listă, lăsând să treacă doar elementele pare
ale acesteia. """
xlista = [12, 57, 14, 61, 78, 11, 18, 21]
ylista = list(filter(lambda x: (x%2 == 0) , xlista))
print(ylista)

Output
[12, 14, 78, 18]

Exemplul 3.13
"""Funcția map()poate avea ca argumente o funcție (lambda) și o listă.
Funcția este apelată având argumente o listă inițială și funcția lambda și
produce o listă nouă obținută prin utilizarea funcției lambda. Lista
rezultată conține elementele returnate de funcția lambda pentru fiecare
element al listei inițiale. """
# Dublarea elementelor unei liste folosind funcția map()
xlista = [12, 57, 14, 61, 78, 11, 18, 21]
ylista = list(map(lambda x: x*2 , xlista))
print(ylista)

Output
[24, 114, 28, 122, 156, 22, 36, 42]

3.1.8 Variabile globale

Definiție. În Python, o variabilă declarată în afara funcției sau cu vizibilitate globală se numește
variabilă globală. Aceasta poate fi accesată fie în interiorul funcției, fie în exteriorul ei.
Exemplul 3.14 #variabila globală
#Crearea unei variabile globale
x = "var_global"
def test():
print("x in interior:", x)
test()
print("x in exterior:", x)

Output
x in interior: var_global
x in exterior: var_global
86 Funcții, module și pachete

În secvența de program de mai sus, s-a creat o variabilă globală denumită x și s-a definit o
funcție test() pentru imprimarea variabilei globale x. La sfârșit s-a apelat funcția test() care
a apelat valoarea lui x.
Dacă se dorește modificarea valorii lui x în interiorul funcției se va obține o eroare, ca în
exemplul următor:
Exemplul 3.15 #eroare la modificarea unei variabile globale
x = " var_global"
def test():
x = 5 * x
print(x)
test()

Output

UnboundLocalError: local variable 'x' referenced before assignment

Eroare apare datorită faptului că Python tratează x ca o variabilă locală în interiorul funcției
test(), dar x nu este definit în interiorul acestei funcții (nu are atribuită o valoare).

Pentru a reuși totuși ce s-a propus, este necesară utilizarea cuvântului cheie global.
Observație. Cuvântul cheie global este necesar pentru variabilele folosite cu același nume în
exteriorul și interiorul funcției. În cazul exemplului de mai sus, dacă nu este necesară
memorarea modificării suportate în cazul funcției, lucrurile se rezolvă simplu, utilizând x doar
ca variabilă locală:
x = " var_global"
def test(x):
x = 5 * x
print(x)
test(x)

Output
var_global var_global var_global var_global var_global

Dar nu acest lucru se dorește! În acest context, x din interiorul funcției nu mai este variabilă
globală, ci locală!
Observație. Variabila globală poate fi modificată în interiorul unei funcții doar dacă este
mutabilă (de exemplu, o variabilă numerică, o listă, etc.). Variabilele imutabile nu pot fi
modificate, ci doar citite (de exemplu, un tuplu).

3.1.9 Variabile locale

Definiție. O variabilă declarată în corpul unei funcții, cu vizibilitate locală, se numește


variabilă locală.

Exemplul 3.16 #Crearea unei variabile locale


#Se declară o variabilă în interiorul unei funcții cu destinație de
variabilă locală și se inițializează
def test():
z = "local"
print(z)
87 Funcții, module și pachete

test()

Output
Local

Variabila z se poate accesa și eventual modifica deoarece acest lucru se face din interiorul
spațiului său de vizibilitate (comanda print()se află în interiorul funcției test()).
Exemplul 3.17 #accesarea unei variabile locale din exteriorul funcției
#Accesarea unei variabile locale din afara domeniului de vizibilitate
def test():
z = "local"
test()
print(z)

Output
NameError: name 'z' is not defined

La ieșire apare eroare deoarece se încearcă accesarea variabilei locale z din exteriorul funcției,
variabila z existând doar în interiorul funcției test(), unde are o vizibilitate locală.

3.1.10 Variabile globale și locale

În același program, se pot utiliza simultan variabile locale și globale, după cerințele
algoritmului.
Examplul 3.18 #Variabile locale și globale în același program
x = "global"
def test():
global x
y = "local"
x = x*3 #multiplicarea continutului variabilei globale x
print(x)
print(y)
test()

Output
globalglobalglobal
local

În secvența de program de mai sus, s-a declarat x ca variabilă globală și y ca variabilă locală în
funcția test(). Apoi s-a utilizat operatorul de multiplicare * pentru a modifica variabila
globală x și s-au imprimat atât x cât și y.
După apelarea funcției test(), valoarea lui x devine globalglobalglobal deoarece s-a
utilizat x*3 pentru a imprima textul multiplicat global. După aceasta, s-a imprimat valoarea
variabilei locale y ca local.
Exemplul 3.19 #Variabila globală și variabila locală au același nume
x = 5
def test():
x = 10
print("local x:", x)
test()
print("global x:", x)
88 Funcții, module și pachete

Output
local x: 10
global x: 5

În secvența de program de mai sus, s-a utilizat același nume x atât pentru variabila globală cât
și cea locală. Se obține un rezultat diferit la imprimarea variabilelor deoarece variabila este
declarată în două domenii de vizibilitate, domeniul local de vizibilitate în interiorul funcției
test() și domeniul global de vizibilitate în exteriorul funcției test().

Când se imprimă variabila în interiorul funcției test() se afișează valoarea local x: 10.
Acesta este domeniul de vizibilitate local al variabilei.
Asemănător, când se imprimă variabila în exteriorul funcției test(), se afișează global x:
5. Acesta este domeniul de vizibilitate global al variabilei.

3.1.11 Variabile nelocale


Definiție. Variabilele utilizate în funcțiile încuibate al căror domeniu de vizibilitate nu este
definit se numesc variabile nelocale. Acest lucru înseamnă că variabila nu este nici în domeniul
de vizibilitate local și nici în cel global.
Pentru a crea variabilele nelocale se utilizează cuvântul cheie nonlocal. Prin această denumire
se comunică faptul că variabila nu este locală, adică nu aparține funcției încuibate.
Exemplul 3.20 #Crearea unei variabile nelocale
def exterior():
x = "local"
def interior():
nonlocal x
x = "nonlocal"
print("interior:", x)
interior()
#schimbarea din variabila nonlocală va apărea în variabila locala
print("exterior:", x)
exterior()

Output
interior: nonlocal
exterior: nonlocal

În secvența de program de mai sus se află funcția încuibată interior(). Se utilizează cuvântul
cheie nonlocal pentru a crea o variabilă nelocală. Funcția interior() este definită în
domeniul de vizibilitate al funcției exterior().
Observație: Dacă se schimbă valoarea unei variabile nelocale, schimbările apar în variabila
locală.
3.1.12 Utilizarea cuvântului cheie global
În Python, cuvântul cheie global permite modificarea variabilei în afara domeniului de
vizibilitate current. El se utilizează pentru a crea o variabilă globală și apoi pentru a permite
efectuarea de modificări ale variabilei într-un context local.
Regulile de bază pentru cuvântul cheie global în Python sunt:
• Când se creează o variabilă în interiorul unei funcții, aceasta este implicit locală.
89 Funcții, module și pachete

• Când se definește o variabilă în exteriorul unei funcții, aceasta este implicit globală. Nu este
necesar să se folosească cuvântul cheie global.
• Se utilizează cuvântul cheie global pentru a citi și scrie o variabilă globală, în interiorul
unei funcții.
• Utilizarea cuvântului cheie global în exteriorul unei funcții nu are efect.
Exemplul 3.21 #Accesarea variabilei globale din interiorul unei funcții
c = 3 # variabila globala implicita
def aduna():
print(c+5)
aduna()

Output
8

În exemplul de mai sus s-a citit valoarea variabilei globale c, dar fără ca aceasta să se modifice.
Pot fi însă situații când se dorește modificarea variabilei globale din interiorul funcției.
Exemplul 3.22 #Modificarea variabilei globale din interiorul funcției
c = 3 # variabila globala
def aduna():
c = 2*c #se modifica c, variabila globală în domeniul local
print(c + 5)
aduna()

Output
UnboundLocalError: local variable 'c' referenced before assignment

La rularea programului de mai sus, s-a obținut eroare deoarece din interiorul funcției se poate
doar citi variabila, dar nu se poate modifica. De fapt, în acest context, în interiorul funcției
variabila c este interpretată ca fiind o variabilă locală, neinițializată.
Soluția la problema de mai sus este utilizarea cuvântului cheie global.
Exemplul 3.23 #modificarea unei variabile declarate global în funcție
#Modificarea unei variabile globale din interiorul funcției utilizând
#cuvântul cheie global.
c = 3 # variabila globala
def aduna():
global c
c = 2*c # modifica c
print("In interiorul funcției aduna(), c = ", c)
aduna()
print("In exterior, c =", c)

Output
In interiorul funcției aduna(), c = 6
In exterior, c = 6

În secvența de program de mai sus, se atribuie inițial variabilei c valoarea 3. Apoi, în interiorul
funcției aduna(), se definește c utilizând cuvântul cheie global. În continuare, variabila c
se dublează, adică va fi c = 2*c. După aceasta, la apelul funcției aduna() se va imprima
valoarea variabilei globale c, dublate (c = 6). În acest caz, variabila c nu mai este interpretată
90 Funcții, module și pachete

ca fiind o variabilă locală neinițializată, ci ca o variabilă globală, căreia i-a fost alocată memorie
și a fost inițializată deja în exteriorul funcției aduna().

3.1.13 Variabilele globale în cazul mai multor module Python

Observație. În Python, se recomandă crearea unui singur modul config.py pentru a conține
variabilele globale și a le distribui între module, în cadrul unei aceleiași aplicații formate din
mai multe module.
În exemplul care urmează se partajează și se testează două variabile globale între module.
Exemplul 3.20 #utilizarea unui fișier config.py conținând variabilele globale
# Se creează un fișier config.py, pentru a memora variabilele globale.
a = 10
b = "vid"

Se creează un fișier schimba.py , pentru a modifica variabilele globale.


import config
config.a = 20
config.b = "abcdefg"

Se creează un fișier verifica.py, pentru a testa modificările valorilor:


import config
import schimba
print(config.a)
print(config.b)

La rularea fișierului verifica.py, se obține:


20
abcdefg

Mai sus s-au creat trei fișiere: config.py, schimba.py și verifica.py.


Modulul config.py memorează variabilele globale a și b. În fișierul schimba.py, se importă
modulul config.py și se modifică valorile lui a și b. Asemănător, în fișierul verifica.py, se
importă atât config.py cât și modulul schimba.py. La final se imprimă și se testează
efectuarea schimbării valorilor globale.

3.1.14 Globalitatea în funcțiile încuibate


Modul de utilizare a variabilelor în funcții încuibate se poate clarifica prin exemplul care
urmează.
Exemplul 3.21 #Utilizare variabile globale în funcții încuibate
#nivel program
def fprinc():
#nivel funcție
x = 20
def fincuib():
#nivel funcție incuibată
global x
x = 50
print("Înainte de apelul fincuib, x = ", x)
print("Apelare fincuib")
fincuib()
91 Funcții, module și pachete

print("După apelarea fincuib, x = ", x)


fprinc()
print("x in program: ", x)

Output
Înainte de apelul fincuib, x = 20
Apelare fincuib
După apelarea fincuib, x = 20
x in program: 50

În programul de mai sus, s-a declarat o variabilă globală în interiorul unei funcții încuibate,
fincuib(). În interiorul funcției fprinc(), x nu este afectat de cuvântul cheie global. În
consecință, înainte și după apelul funcției fincuib(), variabila x ia valoarea unei variabile de
tip local, în acest caz, x = 20. În exteriorul funcției fprinc(), variabila x va lua valoarea
definită în funcția fincuib(), adică x = 50. Acest lucru e posibil pentru că s-a utilizat
cuvântul cheie global pentru x pentru a crea variabila globală în funcția fincuib().
Dacă se vor face schimbări în interiorul funcției fincuib(), acestea vor apărea în exteriorul
domeniului de vizibilitate locală, adică în program, dar nu și în fprinc().

3.1.15 Utilizarea *args și **kwargs


În momentul declarării unei funcții este posibil să nu se cunoască numărul argumentelor cu
care va fi apelată acea funcție. Din acest motiv a apărut posibilitatea declarării unei funcții cu
număr variabil de argumente prin utilizarea parametrilor *args și **kwargs.
A. Cazul *args
*args este utilizat pentru a preciza o listă cu număr variabil de argumente de tip non-
keyworded (care nu sunt de tipul cheie-valoare, nu sunt argumente cărora li se atribuie o
valoare chiar în lista de argumente).
Exemplul 3.22 #media unui număr variabil de valori
def media(*args):
return sum(args)/len(args)
print(media(1,2))
print(media(1,2,3))
print(media(1,2,3,4))

Output
1.5
2.0
2.5
Observație. Numele args nu este obligatoriu. Se poate folosi orice nume de variabilă se
dorește.
Despachetarea argumentelor utilizând *
Dacă lista variabilă de argumente opționale este memorată sub forma unei liste sau a unui
tuplu, ca de exemplu: valori = [5, 6, 7] (listă), sau valori = (5, 6, 7) (tuplu), este necesar, pentru
ca valorile să fie preluate individual de funcție, să se utilizeze operatorul '*' pentru a
despacheta (unpack) valorile, astfel ca ele să poată fi trecute funcției. Exemplu:
media (3,4, *valori)
92 Funcții, module și pachete

B. Cazul **kwargs
În declarația sau definiția unei funcții poate să apară ca argument și **kwargs. Denumirea
kwargs este o prescurtare pentru keyworded - argumente care au nume și sunt elemente ale
unui dicționar – perechi cheie/valoare. În exemplul de mai jos, 7 și 9 sunt argumente de tip
non-keyworded, iar x și y sunt argumente de tip keyworded (chei într-un dicționar).
func_calc(7, 9, x = 4)
func_calc(7, 9, x = 4, y = 5)

Argumentele semnalizate prin **kwargs pot fi extrase utilizând o construcție for – in.
Exemplul 3.23 #se extrage o pereche cheie/valoare la fiecare iterație
def func_calc(nr1, nr2, **kwargs):
print(nr1)
print(nr2)
for i,j in kwargs.items():
print(i,j)
func_calc(7, 9, x = 7, y = 9)

Output
7
9
x 7
y 9

Într-o altă versiune a programului se pot extrage mai întâi cheile și apoi valorile
corespunzătoare, astfel:
Exemplul 3.24 #extrage cheile, apoi valorile pe baza cheilor
def func_calc(nr1, nr2, **kwargs):
print(nr1)
print(nr2)
for k in kwargs.keys():
print(k, kwargs[k])
func_calc(7, 9, x = 7, y = 9)

Output
7
9
x 7
y 9

3.2 Modulele
Definiție. Modulele sunt fișiere conținând instrucțiuni și declarații Python.
Un fișier conținând cod Python, de exemplu: calcul.py, este denumit modul, numele
modulului fiind calcul.
Modulele se utilizează pentru divizarea programelor de mare dimensiune în părți mai mici, mai
ușor de editat și organizat. În plus, modulele permit reutilizarea codului.
Cele mai utilizate funcții se pot defini ca module, astfel că ele se pot importa ori de câte ori
este nevoie, fără a fi rescrise din nou, în fiecare loc unde se utilizează.
93 Funcții, module și pachete

Modulele pot conține însă mai mult decât o funcție; ele pot conține mai multe funcții, clase sau
variabile.
Următorul exemplu reprezintă un modul care conține o singură funcție, aduna(), Această
funcție adună două numere, întoarce suma lor și poate fi apelată ori de cite ori este necesar.
Exemplul 3.25 # Un exemplu de modul. Numele său poate fi calcul.py
def aduna(a, b):
"""Programul aduna doua
numere si intoarce rezultatul"""
suma = a + b
return suma

3.2.1 Importarea modulelor în Python


3.2.1.1 Instrucțiunea import
Se pot importa definițiile conținute într-un modul, parțial sau total, în cadrul unui alt modul,
sau în interpretorul interactiv Python cu ajutorul cuvântului cheie import.
Pentru a importa modulul definit mai sus, exemplu, se folosește comanda import în interpretor,
astfel:
>>> import calcul

Observație. Comanda nu importă numele funcțiilor definite în calcul.py direct în tabelul de


simboluri current. Se importă doar numele modulului ( calcul).În consecință, pentru a utiliza
funcția aduna(), va fi necesară calificarea numelui acesteia prin operatorul punct/dot ( . ):
>>> calcul.aduna(4,5.5)
9.5

Python este un limbaj care dispune de un număr foarte mare de module standard (în locația Lib
a instalării). Atât modulele standard, cât și modulele definite de utilizator, se importă la fel.
Pentru a afla numele tuturor modulelor (pre)instalate, se utilizează comanda:
>>> help("modules")

Pentru a afla doar numele unui/unor module care conțin o secvență de caractere, de exemplu
"run", se folosește comanda sub forma:
>>> help ("modules run")

Sunt disponibile două moduri de acces la definițiile funcțiilor conținute într-un modul:
păstrându-le denumirea sau redenumindu-le, astfel:
A. Se poate importa un modul folosind instrucțiunea import și accesând definițiile conținute
utilizând operatorul punct descris mai sus.
Exemplul 3.26 #instrucțiunea import, simplă
import math
print("Valoarea lui pi este =", math.pi)

Output
Valoarea lui pi este = 3.141592653589793

B. Se poate importa un modul redenumindu-l, ca în exemplul care urmează:


94 Funcții, module și pachete

Exemplul 3.27 #instrucțiunea import, cu redenumire


import math as m
print("Valoarea lui pi este =", m.pi)

Output
Valoarea lui pi este = 3.141592653589793

S-a redenumit modulul math cu noul nume m. Motivul redenumirii poate fi scurtarea lungimii
numelui, sau o sugestivitate mai bună.
Observație. Vechiul nume math nu mai este recunoscut în domeniul de vizibilitate al
programului current, astfel că math.pi devine o denumire invalidă, varianta corectă fiind
m.pi.

Observație importantă. Importarea unui modul va avea ca efect executarea codului din
modulul importat înainte de executarea codului modulului care importă. Exemplul următor
clarifică această afirmație.
Exemplul 3.28 #Execuția modulului importat la momentul importării
Fie două module p1.py și p2.py. Modulul p2.py importă modulul p1.py.
Modulul p1.py
def f1():
print('Faza 1')
print('Faza 2')

Modulul p2.py
import p1
print('Faza 3')
p1.f1()

Rezultatul execuției modulului p2.py este următorul


Output
Faza 2
Faza 3
Faza 1

3.2.1.2 Instrucțiunea from … import


Dintr-un modul se pot importa doar anumite nume de definiții, fără a se importa totul ca un
întreg.
Exemplul 3.29
from math import pi
print("Valoarea lui pi este =", pi)

În exemplul de mai sus s-a importat doar constanta pi din modulul math, ignorându-se restul
modulului.
Observație. În acest caz de import fără redenumire, nu mai este necesară calificarea numelui
(utilizarea operatorului punct/dot ( . )).
Observație. Se pot importa mai multe definiții dintr-un modul fără a folosi operatorul punct,
cu o singură instrucțiune
95 Funcții, module și pachete

Exemplul 3.30 #importul mai multor nume, simultan


>>> from math import pi, e
>>> pi
3.141592653589793
>>> e
2.718281828459045

3.2.1.3 Importarea tuturor numelor

Se pot importa toate numele (definițiile) dintr-un modul folosind următoarea construcție:
from math import *
print("Valoarea lui pi este =", pi)

S-au importat toate definițiile din modul prin utilizarea simbolului *, cu excepția acelora care
încep cu underscore. Această metodă nu este totuși recomandată deoarece poate conduce la
dublarea unor definiții sau identificatori și dificultăți în gestionarea codului program.

3.2.2 Calea de căutare (path) a modulelor

Pentru a încărca un modul, se caută în mai multe locuri. Mai întâi se caută modulele
preconstruite (built-in) care ar putea avea același nume. Dacă nu se găsesc în biblioteca din
directorul implicit, Python caută în lista de directoare definite în sys.path, în următoarea
ordine:
• directorul curent.
• PYTHONPATH (variabila de mediu conținând o listă de directoare).
• directorul definit implicit la instalare.
>>> import sys
>>> sys.path
['', 'C:\\Program Files\\Python310\\Lib\\idlelib', 'C:\\Program
Files\\Python310\\python310.zip', 'C:\\Program Files\\Python310\\DLLs',
'C:\\Program Files\\Python310\\lib', 'C:\\Program Files\\Python310',
'C:\\Program Files\\Python310\\lib\\site-packages']
Calea conținând lista de directoare poate fi modificată de utilizator pentru adăugarea unei căi
specifice.

3.2.3 Reîncărcarea unui modul

Interpretorul Python importă un modul doar o singură dată în timpul unei sesiuni.
Se presupune că se dorește încărcarea unui modul numit xmodul, al cărui conținut este:
# Demonstratia ca un modul
# se importa o singura data, chiar daca s-au facut mai
# multe importuri-reîncărcări
print("Această secvență program s-a executat")

Importarea acestui modul în interpretor generează rezultatul următor, care arată că importul s-
a efectuat o singură dată.

Exemplul 3.31 #se arată că importul unui modul se face doar o dată
96 Funcții, module și pachete

>>> import xmodul


Această secvență program s-a executat
>>> import xmodul
>>> import xmodul

Pot să apară totuși situații care să facă necesară reîncărcarea unui modul, de exemplu modulul
se poate să se fi modificat. O cale eficientă (mai bună decât restartarea interpretorului) este
utilizarea funcției reload() care aparține modulului imp. Se procedează astfel:
Exemplul 3.32 #reîncărcarea unui modul cu funcția reload
>>>import imp
>>> import xmodul
Această secvență program s-a executat
>>> import xmodul
>>>imp.reload(xmodul)
Această secvență program s-a executat

3.2.4 Funcția built-in dir()și atributul '__name__'

Pentru aflarea numelor conținute într-un modul se poate utiliza funcția dir().
Mai sus s-a definit modulul calcul.py conținând funcția aduna(). Acum se poate aplica
funcția dir() modulului calcul:
>>> dir(calcul)
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'aduna']
Observație. Toate numele care încep cu underscore sunt atribute implicite Python asociate cu
modulul, nefiind definite de utilizator. De exemplu, atributul '__name__' conține numele
modulului importat (fără extensia .py).
>>>import calcul
>>>calcul.__name__
'calcul'
Observație importantă. Variabila '__name__' conține numele modulului, cu o singură
excepție: în modulul care este curent executat această variabilă are valoarea '__main__'.
Observație. Toate numele definite în spațiul de nume curent pot fi afișate utilizând funcția
dir() fără argumente.

Exemplul 3.33
>>> x=5
>>> y=34567
>>> alfa ='333'
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'alfa', 'x', 'y']
97 Funcții, module și pachete

3.3 Pachetele
3.3.1 Definiția pachetului

Definiție. O structură ierarhică compusă din module Python este denumită pachet. În Python
se poate face o analogie între aplicație și o structură ierarhică de pachete, între
director/subdirector și pachet/subpachet și între fișier și modul.
O aplicație poate fi compusă din mai multe fișiere. Acestea nu întotdeauna pot fi ținute în
aceeași locație. Mai potrivită este o structură ierarhică de directoare pentru ușurarea accesului
la fișiere. În cazul aplicațiilor formate din multe module, este avantajoasă plasarea modulelor
similare în același pachet, iar modulele diferite în pachete diferite. Această organizare face mai
simplă și mai clară gestionarea componentelor aplicației.

Achizitie_date

__init__.py Intrare_date Calcul Afisare

__init__.py __init__.py __init__.py

Presiune.py Debit.py Monitor.py

Viteza.py Totaluri.py Printer.py

Temperatura.py

Fig. 3.1 Structura aplicației “Achiziție de date”


Asemănător structurii ierarhice compusă din directoare, subdirectoare și fișiere, pachetele pot
avea subpachete și module.
Un director trebuie să conțină un fișier numit __init__.py pentru a putea fi considerat drept
pachet. Acest fișier __init__.py poate fi lăsat vid sau poate să conțină o secvență de
inițializare.

Exemplul 3.34 #aplicație structurată în pachete, subpachete și module

Se consideră o aplicație de tip “achiziție de date” care poate avea o structură de organizare a
pachetelor și modulelor ca in figura 3.1.

3.3.2 Importul modulelor din pachete

Se poate realiza importul modulelor din pachete utilizând operatorul punct (.).
98 Funcții, module și pachete

De exemplu, modulul debit din exemplul anterior se poate importa astfel:


import Achizitie_date.calcul.debit

Dacă modulul debit conține o funcție denumită select_masa_volum(), trebuie să se utilizeze


numele său în întregime (cu prefix) pentru a fi referită.
Achizitie_date.calcul.debit.select_masa_volum(m)

Dacă numele pare prea lung, se poate importa modulul fără a mai fi necesar să se utilizeze
prefixul, astfel:
from Achizitie_date.calcul import debit

Acum, se poate apela funcția mai simplu:


debit.select_masa_volum(m)

O altă cale de import doar al funcției cerute (sau clasă, sau variabilă) dintr-un modul aflat într-
un pachet, se poate face astfel:
from Achizitie_date.calcul.debit import select_masa_volum

În acest moment se poate apela funcția fără nici un prefix:


select_masa_volum(m)

Deși pare mai simplă, această metodă nu este recomandată. Utilizarea complete a spațiului de
nume (namespace) asigură evitarea confuziilor și previne coliziunile datorate numelor dublate.
Observație. Și la importul modulelor din pachete se poate utiliza aliasul "as", redenumindu-
le.
Observație. Este importantă poziția relativă a modulului care importă față de modulul importat
în calea de acces sys.path. Modulul care importă trebuie să se afle în fața modulului importat
(să preceadă), adică mai spre baza structurii.

3.4 Întrebări, exerciții și probleme


3.4.1 Care afirmație nu este adevărată în legătură cu parametrii formali și actuali ?
a) Numărul și tipul trebuie să corespundă.
b) Nu trebuie să fie în aceeași ordine.
c) Numele lor trebuie să fie identic.
d) Toate afirmațiile de mai sus sunt adevărate.
R3.4.1 Răspuns: c. Numele parametrilor utilizați în definiția funcțiilor sunt “formale” (de
formă, nerestrictive), de aceea nu trebuie să fie aceleași cu numele variabilelor ale căror valori
sunt trecute când se apelează funcția. Numărul și tipul lor totuși contează. În lista de argumente,
ordinea la definirea funcției poate să difere de ordinea la apel.
3.4.2 Limbajul Python admite notația (precizarea tipului) în cazul funcțiilor ?
a) Da b) Nu
R3.4.2 Răspuns: Da. Începând cu versiunea Python 3.5 (și următoarele) Python acceptă
“notația tipurilor”. Acest lucru apare la declararea tipului argumentelor și a tipului de rezultat
ce va fi returnat, ca în exemplul următor:
>>> def suma(a: int, b: int)->int :
. . . return a + b
99 Funcții, module și pachete

>>> print(suma(4,5))
9
3.4.3 Arătați prin exemplificare diferența dintre o funcție lambda și o funcție lambda anonimă.
R3.4.3 Funcția lambda:
>>> func = lambda x: x *x
>>> print (func(5))
25
Funcția lambda anonimă:
>>> (lambda x: x*x) (5)
25
3.4.4 Se poate spune că transmiterea unor parametri unei funcții în Python se poate face prin
valoare, ca în cazul limbajului C/C++ ?
a) Da b) Nu
R3.4.4 Nu. Se reamintește că în Python, nu se pot atribui în mod real valori variabilelor. Ceea
ce se întâmplă de fapt este memorarea în variabilă a referinței la obiect (adresa de memorie a
obiectului). În consecință, în Python nu poate exista transferul prin valoare ca în cazul
limbajului C/C++. Transferul se face prin referință, iar acest lucru poate cauza probleme în
cazul variabilelor mutabile (de exemplu o listă), adică o modificare în interiorul
funcției/obiectului, se transmite în exteriorul său (side effect). Dacă variabila este imutabilă (de
exemplu un tuplu), variabila nu se poate modifica. Cele spuse se pot verifica prin următorul
exemplu:
Fie funcţia:
def funct(xlista):
xlista[3] = 'albastru'
Dacă argumentul de apelare a funcției de mai sus este variabila ylista, definită ca mai jos,
ylista = ['alb', 'verde', 'rosu', 'negru']

se poate observa cum ylista a fost modificată după apel (are rol de variabilă globală):
def funct(xlista):
xlista[3] = 'albastru'
ylista = ['alb', 'verde', 'rosu', 'negru']
print('Lista initială', ylista)
funct(ylista)
print('Lista după apel', ylista)

Output
Lista initială ['alb', 'verde', 'rosu', 'negru']
Lista după apel ['alb', 'verde', 'rosu', 'albastru']

Efectul colateral este clar vizibil. Orice variabilă definită în afara funcției este variabilă globală
pentru acea funcție. O soluție pentru împiedicarea modificării, dacă se dorește acest lucru,
poate fi utilizarea unei variabile locale, care poate avea același nume cu variabila mutabilă
externă.
Concluzie: În Python nu există “transfer prin valoare” (funcția nu își face o copie proprie de
lucru a argumentului).
3.4.5 Se poate atribui o funcție unei variabile ?
a) Da b) Nu
R3.4.5
100 Funcții, module și pachete

Da. În exemplul de mai jos funcția funct() se atribuie variabilei y, care se va apela y().
def funct():
print("******")
y = funct
y()

Output
******

3.4.6 O funcție poate returna o altă funcție ?


a) Da b) Nu
R3.4.6 Da, ca în exemplul care urmează:
def funct(y):
def multiplicare(x):
return x*y
return multiplicare
dublare = funct(2)
triplare = funct(3)
a = dublare(5)
b = triplare(7)
print(a)
print(b)

Output
10
21

3.4.7 O funcție poate fi trecută ca parametru unei alte funcții ?


a) Da b) Nu
R3.4.7 Da, ca în exemplul care urmează:
def a(x,y):
#functia care va fi trecuta ca argument
print(x, 'si', y)

def b(funct, sir):


#functia care va primi ca argument o alta functie
funct ('textul-unu', sir)

b(a, 'textul-doi')

Output
textul-unu si textul-doi

3.4.8 Ce întoarce o funcție care nu conține return ?


R3.4.8 Întoarce None.
3.4.9 O funcție poate întoarce multiple valori grupate ca mai jos. Care este răspunsul corect?
a) listă b) tuplu c) dicționar d) set
R3.4.9 Răspuns: b). Fie următorul exemplu:
def dimensiuni():
L = 5
l = 3
101 Funcții, module și pachete

return L, l #combinația L, l este un tuplu


#(parantezele unui tuplu nu sunt obligatorii)
lungime, latime = dimensiuni()
print(lungime)
print(latime)

Output
5
3
3.4.10 Ce se înțelege prin “semnătura unei funcții” ?
Numele funcției și lista sa de argumente sunt denumite “semnătura funcției”.
3.4.11 Cum se definește o funcție cu argumente opționale ?
R3.4.11 Argumentele pot fi definite prin atribuirea (utilizând =) unei valori implicite la numele
argumentului:
def fct(acceleratie = "zero") :
# .....
return actiune
Apelul acestei funcții este posibilă în trei feluri:
func("creste")
func(acceleratie = "scade")
func()

3.4.12 Analizați codul următor și precizați dacă funcționează corect:


print('Functie recursiva')
def factorial(n):
return (n*factorial(n - 1))
factorial(4)

R3.4.12 Funcția recursivă de mai sus, ce ar trebui să calculeze factorialul, nu funcționează


corect deoarece lipsește condiția de start. Codul corect este următorul:
print('Functie recursiva')
def factorial(n):
if n == 1:
return 1
else:
return (n*factorial(n - 1))
print(factorial(4))
Output
Functie recursiva
24
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 4

Tipuri numerice de date în Python


4.1 Tipuri numerice
Tipurile numerice de date întâlnite în Python sunt întregii (int), numerele în virgulă flotantă
(float) și numerele complexe (complex).
Întregii și virgula flotantă se diferențiază prin prezența sau absența punctului zecimal. De
exemplu, 8 este un întreg, în timp ce 8.0 este un număr în virgulă flotantă.
În Python nu există limită pentru lungimea unui întreg (numărul de cifre).
Numerele complexe sunt scrise sub forma x + jy, unde x este partea reală, y este partea
imaginară, iar j este √−1.
Se poate utiliza funcția type(), pentru a ști carei clase aparține o variabilă sau valoare și funcția
isinstance() pentru a verifica apartenența la o anumită clasă.

Exemplul 4.1 #aflarea tipului unei mărimi numerice


a = 5
print(type(a))
print(type(5.0))
c = 5 + 3j
print(c + 3)
print(isinstance(c, complex))

Output
<class 'int'>
<class 'float'>
(8+3j)
True
În timp ce întregii pot fi de orice lungime, un număr în virgulă flotantă are o precizie doar de
15 cifre zecimale.
În domeniul calculatoarelor se utilizează frecvent și alte sisteme de numerație în afară de cel
zecimal (baza 10): sistemele binar (baza 2), hexazecimal (baza 16) și octal (baza 8). În Python,
se pot reprezenta toate sistemele prin plasarea unor prefixe inaintea numerelor, astfel:
Sistemul numeric Prefixul
Binary '0b' sau '0B'
Octal '0o' sau '0O'
Hexazecimal '0x' sau '0X'
Exemplul 4.2 #reprezentarea în alte sisteme de numerație: binar, hexa și octal
print(0b1101011)
print(0xFB + 0b10)
print(0o15)

Output
107
253
103 Tipuri numerice de date în Python

13

4.2 Conversia de tip


Se poate converti un tip numeric în alt tip numeric. Această operație este denumită casting
(“forțarea tipului”).
Operații ca adunarea, scăderea, etc., forțează implicit (automat) conversia unui întreg în float,
dacă unul dintre operanzi este float.
>>> 1 + 2.0
3.0
Se poate vedea mai sus că 1 (întreg) este convertit (forțat) în 1.0 (float) pentru adunare , iar
rezultatul este de asemenea un număr flotant.
De asemenea, se pot utiliza funcții built-in ca: int(), float() și complex() pentru a converti
explicit între tipuri. Aceste funcții pot converti chiar și din șiruri.
Exemplul 4.3 #conversia explicită de tip
>>> int(2.3)
2
>>> int(-2.8)
-2
>>> float(5)
5.0
>>> complex('3+5j')
(3+5j)
Când are loc o conversie dintr-un număr float într-un întreg, numărul se trunchiază (partea
zecimală este eliminată).

4.3 Numere fracționare și zecimale


Calculele în Python reflectă modul de reprezentare a numerelor în calculator. Efectul preciziei
finite se poate face vizibilă prin executarea următoarei instrucțiuni:
Exemplul 4.4 #vizualizarea preciziei finite
>>> (1.1 + 2.2) == 3.3
False

Rezultatul de mai sus apare ca urmare a reprezentării numerelor în sistemul de numerație binar
utilizat de calculator. Numerele în virgulă flotantă sunt exprimate ca fracțiuni binare deoarece
calculatorul înțelege doar 0 și 1. Din acest motiv, cele mai multe din fracțiunile zecimale nu
pot fi stocate precis în memorie.
Exemplul 4.5 #erori la reprezentarea numerelor datorate calculatorului
- Numărul fracționar 1/3 nu poate fi reprezentat precis ca număr zecimal, deoarece rezultatul
împărțirii lui 1 cu 3 produce 0.33333333..., care are o lungime infinită și nu poate fi aproximat.
- Numărul zecimal fracționar 0.1 este reprezentat în binar printr-un șir infinit lung
0.000110011001100110011... , iar calculatorul poate stoca doar un număr finit de biți, doar
aproximând pe 0.1. Ca urmare eroarea apărută aparține calculatorului și nu limbajului Python.
Exemplul 4.6 #eroarea dată de calculul cu precizie finită
>>> 1.1 + 2.2
104 Tipuri numerice de date în Python

3.3000000000000003

Pentru a rezolva erorile de aproximare (de exemplu, inadmisibile în calculul financiar) și în


Python, ca și în alte limbaje, se utilizează un modul special proiectat pentru calcule zecimale.
Spre deosebire de precizia calculului în virgulă flotantă, limitată la 15 zecimale, modulul
zecimal dispune de precizie controlabilă care poate fi prestabilită.
Exemplul 4.7 #modulul decimal utilizat pentru calcule financiare precise
import decimal
print(0.1)
print(decimal.Decimal(0.1))

Output
0.1
0.1000000000000000055511151231257827021181583404541015625

Acest modul efectuează calculele cu mai mare precizie decât virgula flotantă. Ca urmare se vor
obține rezultate mult mai acceptabile, sau mai semnificative la operațiunile cu mărimi fizice,
fiind permisă alegerea numărului de zecimale afișabile.
Exemplul 4.8 #utilizarea modulului decimal
from decimal import Decimal as D
print(D('1.1') + D('2.2')) #adunare
print(D('1.5') * D('2.50')) #înmulțire

Output
3.3
3.750

Observație. Deși calculul zecimal este mai precis, se preferă calculele folosind virgula flotantă
din motive de execuție mai rapidă.
Observație. Calculul zecimal utilizând modulul decimal este de preferat în următoarele
situații:
- Calcule financiare.
- Se dorește controlul nivelului de precizie a calculelor.
- Se face limitare la un număr semnificativ de poziții zecimale.

4.4 Fracțiile în Python


Python permite operații cu numere fracționare prin intermediul modulului fractions.
O fracție are numărător și numitor, ambii întregi. Modulul fractions este utilizat în calculele
cu numere raționale.
Obiectele de tip Fraction se pot crea în diferite moduri.
Exemplul 4.9 #crearea obiectelor de tip Fraction
import fractions
print(fractions.Fraction(1.5)) #un număr zecimal
print(fractions.Fraction(5)) #un întreg
print(fractions.Fraction(1,3)) #doi întregi

Output
105 Tipuri numerice de date în Python

3/2
5
1/3

La utilizarea funcției Fraction din modulul fractions, pot apărea uneori rezultate imprecise,
datorate reprezentării imperfecte a modelului virgulei flotante în binar.
În compensație, Fraction permite afișarea cu șiruri. Această opțiune este preferată la lucrul
cu numere zecimale.
Exemplul 4.10 #afișarea rezultatului ca șir de caractere
import fractions
print(fractions.Fraction(1.1)) #rezultatul va fi un număr float
print(fractions.Fraction('1.1')) #rezultatul va fi o fracție

Output
2476979795053773 / 2251799813685248 #raportul a două numere de 16 cifre
11/10 #raportul a două numere reprezentate
#ca șir

Cu tipul fracționar, utilizând modulul fractions, sunt permise toate operațiunile aritmetice
de bază.
Exemplul 4.11 #operații aritmetice cu funcția Fraction()
from fractions import Fraction as F
print(F(1, 3) + F(1, 3))
print(1 / F(5, 6))
print(F(-3, 10) > 0)
print(F(-3, 10) < 0)

Output
2/3
6/5
False
True

4.5 Module matematice


Python oferă module ca math și random pentru a efectua operații matematice de trigonometrie,
logaritmi, probabilitate și statistică, etc.
Exemplul 4.12 #funcții matematice în modulul math
#modulul math
import math
print(math.pi)
print(math.cos(math.pi))
print(math.exp(10))
print(math.log10(1000))
print(math.log10(1500))
print(math.sinh(1))
print(math.sinh(3))
print(math.factorial(6))

Output
3.141592653589793
-1.0
106 Tipuri numerice de date în Python

22026.465794806718
3.0
3.1760912590556813
1.1752011936438014
10.017874927409903
720

Exemplul 4.13 #lucrul cu numere aleatoare


#modulul random
import random
print(random.randrange(10, 20))
x = ['a', 'b', 'c', 'd', 'e']
print(random.choice(x))
random.shuffle(x)
print(x)
print(random.random())

Output
19
e
['d', 'e', 'a', 'b', 'c']
0.8707283154064434

Tabel cu operatorii aritmetici care se utilizează cu tipurile numerice de date


Operatorul Descrierea
(+) Adunare Adună operanzii între care se află
(-) Scădere Scade operandul din dreapta din operandul din stânga
(*) Înmulțire Înmulțește operanzii între care se află
(/) Împărțire Împarte operandul din stânga prin operandul din dreapta
(%) Rest-modulo Întoarce restul împărțirii operandului din stânga la operandul din dreapta
(**) Exponent Calculează valoarea exponentului din stânga ridicat la puterea egală cu
operandul din dreapta
(//) Floor division Împărțire având ca rezultat cel mai apropiat număr întreg mai mic față de
cât.
Tabel cu funcțiile built-in pentru tipurile numerice de date
Funcția Rezultat
int(x) Întoarce un obiect – număr întreg dintr-un float sau șir conținând digiți.
float(x) Întoarce un obiect – număr în virgulă flotantă dintr-un număr întreg sau șir
conținând digiți cu punct zecimal sau notație științifică.
complex(re, im) Întoarce un număr complex cu parte reală și imaginară.
c.conjugate() Întoarce conjugatul numărului c
hex() Face conversia unui număr întreg într-un număr hexazecimal cu prefix 0x.
oct() Face conversia unui număr întreg într-un număr octal cu prefix 0o.
pow() Întoarce puterea numărului specificat.
abs() Întoarce valoarea absolută fără considerarea semnelor.
round() Întoarce numărul rotunjit la cel mai apropiat întreg par (cf. IEEE 754).
divmod(x, y) Întoarce perechea (x // y, x%y)

4.6 Întrebări, exerciții și probleme


4.6.1 Unde se regăsesc informațiile specifice mașinii de lucru, referitoare la caracteristicile
reprezentării numerelor de tip float (virgulă flotantă) ?
107 Tipuri numerice de date în Python

R4.6.1 Python păstrează aceste informații în sys.float_info, fiind corespunzătoare tipului


double din limbajul C (rutina de implementare este scrisă în C).
>>>sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308,
min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15,
mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
4.6.2 Care este diferența dintre întregii simpli și întregii lungi ?
R4.6.2 Diferențierea între întregii scurți și lungi a fost o caracteristică a versiunii Python 2.x
și care a dispărut în Python 3.x.
În Python 2.x pentru reprezentarea întregilor se utilizează două tipuri de date. Întregii simpli
(int, plain integers) sunt întregi standard în Python, pozitivi sau negativi. Întregii lungi
(long, long integers) pot avea valori nelimitate și sunt scriși asemănător întregilor simpli, dar
urmați de litera L (de exemplu 53216536L).
4.6.3 Care număr nu este complex:
a. k = complex(2, 3) b. k = 2 + 3l c. k = 2 + 3j

R4.6.3 răspuns: b
4.6.4 Care număr este incorect:
a. x = 0b101 b. x = 0x4c8 c. x = 03783

R4.6.4 c
4.6.5 Care este ieșirea instrucțiunii următoare:
>>>9 // 2
a) 4.5 b) 4.0 c) 4

R4.6.5 răspuns: c
4.6.6 Care este valoarea lui x,valoara sa fiind dată de expresia x = math.factorial(0)?
a. 0 b. 1 c. error

R4.6.6 Răspuns: b
4.6.7 Ce se obține la execuția instrucțiunii: print(math.fabs(5.4)) ?
a. -5.4 b. 5.4 c. 5
R4.6.7 b
4.6.7 Care este utilitatea modulului operator în Python ? Exemplificați.
R4.6.8 Modulul operator conține și exportă în Python un set de funcții eficiente, scrise în
limbajul C, corespunzând operatorilor intrinseci din Python. Denumirile sunt atribuite atât
funcțiilor speciale (cu prefix și sufix dublu underscore), cât și funcțiilor obișnuite. De exemplu:
>>>import operator
>>>operator.add(2,3) #echivalent cu +
5
>>>operator.mul(32,56) #echivalent cu *
1792
>>>operator.truediv(25,6) #echivalent cu /
4.166666666666667
>>>operator.mod(25,6) #echivalent cu %
1
108 Tipuri numerice de date în Python

4.6.9 Cum se indică încercarea de reprezentare a unui număr float peste limitele posibile în
Python?
R4.6.9 La încercarea de obținere a unui număr float peste capacitatea de reprezentare,
răspunsul Python va fi inf, sau -inf, după caz.
>>>nr = 2e500
>>>nr
inf
>>>type(nr)
<class 'float'>
>>>print(-2e500)
-inf

4.6.10 Este eroarea de reprezentare tipică Pythonului?


R4.6.10 Eroarea de reprezentare nu are nimic de a face cu Python. Această eroare este specifică
oricărui calculator, fiind legată de lungimea finită a dimensiunii unei celule de memorie. De
exemplu, prin împărțirile 1/3, sau 1/5 se obține un număr nelimitat de zecimale pentru fiecare
caz, nefiind posibilă reprezentarea acestora în calculator.
>>>1/3
0.3333333333333333

Mai mult, impresia că un număr ca 0.1 este un număr exact este greșită. Dacă se execută o
comandă print(x), se afișează implicit doar primele 16 zecimale, ceea ce înseamnă că două
numere fracționare care diferă printr-o valoare sub un anumit prag, pot avea aparent aceeași
valoare. Dacă se impune precizia, se poate observa acest lucru.
x = 0.1
>>>print(x)
0.1
>>>print('%1.35f ' %x)
0.10000000000000000555111512312578270

4.6.11 Ce probleme pot apărea la rotunjirea numerelor datorate erorilor de reprezentare? Care
este strategia Python privind rotunjirea?
R4.6.11 La utilizarea unui program ca round() pentru rotunjirea numerelor, din cauza erorilor
de reprezentare, se pot obține rezultate imprevizibile, deoarece așteptările sunt ca rotunjirea să
se facă fie numai la numărul întreg superior (cel mai frecvent), fie numai la cel inferior:
>>>round(2.5)
2
>>>round(3.5)
4
Strategia Pythonului este ca rotunjirile să se facă la numărul par (rounding ties to even),
conform standardului IEEE 754. Prin legătură (tie) se înțelege numărul având cifra 5 aflată pe
ultimul loc al părții fracționare. De exemplu, 5.1415 este o legătură (tie), dar nu și 4.7. Acest
lucru explică rezultatul de mai sus.
Rotunjirea cu round() funcționează și precizând un număr de zecimale la care aceasta se poate
face.
>>>round(3.2685)
3
>>>round(3.2685, 1)
3.3
>>>round(3.2685, 2)
3.27
109 Tipuri numerice de date în Python

>>>round(3.2685, 3)
3.268

4.6.12 Ce se înțelege prin operațiile de ”trunchiere”, ”floor“ și “ceil” în Python ?


R4.6.12 Prin ‘”trunchiere” se înțelege rotunjirea la un număr întreg prin eliminarea cifrelor
fracționare.
>>>import math
>>>math.trunc(2.99)
2
>>>math.trunc(-2.99)
-2
Prin floor se înțelege întregul cel mai apropiat, mai mic, față de un număr fracționar.
>>>math.floor(2.99)
2
>>>math.floor(-2.99)
-3
>>>math.floor(-2.13)
-3
Operatorul // se mai numește și “floor division” și corespunde funcției math.floor().
Rezultatul aplicării sale este numărul întreg, cel mai apropiat, mai mic, față de câtul operației
simple de împărțire (/) aplicate operanzilor.
>>>7 / 2, 7 / -2
(3.5, -3.5)
>>>7 // 2, 7 // -2
(3, -4)
>>>7 // 2.0, 7 // -2.0
(3.0, -4.0)

Funcția math.ceil() rotunjește un număr zecimal la valoarea întregului superior, cel mai
apropiat:
>>>math.ceil(4.35)
5
math.ceil(-4.35)
-4
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 5

Liste
5.1 Precizare
Acest capitol este dedicat în principal tipului listă din Python. Deorece tipul tablou este
absent în limbajul Python, va fi tratat și aspectul utilizării modulului array pentru suplinirea
absenței tipului tablou (doar unidimensional!) prin liste,.
De asemenea, se va detalia lucrul cu listele de liste, care pot suplini absența tipului tablou
bidimensional.

5.2 Definiția și crearea listelor


Definiție. O listă conține un număr de elemente plasate între paranteze pătrate [], separate de
virgule.
Observație. O listă poate avea orice număr de elemente, iar tipul lor poate fi de orice fel
(intreg, float, șir, etc.).
Exemplul 5.1 #diferite liste
lista = [] #lista vidă
lista = [1, 2, 3]
lista = [1, "abcdefg", 3.4]

O listă poate conține o altă listă.


lista = ["abcd", [8, 4, 6], ['a']]

5.3 Accesarea elementelor unei liste


5.3.1 Indexarea unei liste

Se poate utiliza operatorul index [] pentru accesarea elementelor unei liste. În Python,
indicii încep de la zero. De exemplu, o listă cu 12 elemente va avea un index de la 0 la 11.
Încercarea folosirii unui index dincolo de limite va produce eroarea IndexError.
Indexul trebuie să fie un intreg. Alegerea unui alt tip de index decât întreg va produce
TypeError.

Listele incuibate sunt accesibile prin intermediul indecșilor incuibați.


Exemplul 5.2 #accesarea listelor simple și încuibate prin intermediul indecșilor
lista = ['n', 'e', 'g', 'r', 'u']
print(lista[0])
print(lista[2])
print(lista[4])
lista_incuibata = ["primul", [2, 0, 1, 6, 8]]
print(in_lista[0][2])
print(in_lista[1][3])
print(lista[4.0]) #eroare de tip de index
111 Liste

Output
n
g
u
i
6
Traceback (most recent call last):
. . . . . . . . . .
print(lista_incuibata[4.0]) #eroare de tip de index
TypeError: list indices must be integers or slices, not float

5.3.2 Indexarea negativă

În Python este permisă indexarea negativă pentru tipurile de date de tip secvență. Indexul
egal cu -1 se referă la ultimul element, indexul -2 se referă la penultimul element, etc.
Exemplul 5.3 #indexarea negativă
lista = ['n','e','g','r','u']
print(lista[-1])
print(lista[-5])

Output
u
n

5.4 Felierea (slicing)


Se poate accesa un domeniu de elemente aparținând unei liste utilizând operatorul de feliere
(:) (colon).
Exemplul 5.4 #utilizarea operatorului de feliere/slicing
lista = ['u','n','i','v','e','r','s','a','l']
print(lista[-1])
print(lista[-9])
print(lista[2:5]) #de la al treilea pana la al cincelea element
print(lista[:-5]) #elementele care încep cu al patrulea element
print(lista[5:]) #elementele începând cu al saselea pana la sfarsit
print(lista[:]) #elementele de la început pana la sfarsit
print(lista[-9])

Output
l
u
['i', 'v', 'e']
['u', 'n', 'i', 'v']
['r', 's', 'a', 'l']
['u', 'n', 'i', 'v', 'e', 'r', 's', 'a', 'l']
u

Slicing-ul poate fi văzut cel mai bine considerând indexul între două elemente. Dacă se
dorește a se accesa un domeniu, este nevoie de doi indici pentru a extrage o porțiune din listă.
Lista u n i v e r s a l
Indexul pozitiv 0 1 2 3 4 5 6 7 8
Indexul negativ -9 -8 -7 -6 -5 -4 -3 -2 -1
112 Liste

5.5 Schimbarea sau adăugarea elementelor unei liste


Listele sunt mutabile. Aceasta însemnă că elementele lor sunt schimbabile, spre deosebire
de șiruri sau tupluri.
Schimbarea unui element sau a unui grup de elemente se face cu operatorul de atribuire ( =).
Exemplul 5.5 #mutabilitatea listelor
lista = [2, 4, 6, 8]
lista[0] = 1 #modificarea primului element
print(lista)
lista[1:4] = [3, 5, 7] #schimbarea începând cu al doilea până la
#al patrulea element inclusiv
print(lista)

Output
[1, 4, 6, 8]
[1, 3, 5, 7]

Se poate adăuga un element la o listă utilizând metoda append(), sau mai multe elemente
cu metoda extend().
Exemplul 5.6 #ilustrare metoda extend()
lista = [15, 31, 52]
lista.append(7)
print(lista)
lista.extend([9, 22, 13])
print(lista)

Output
[15, 31, 52, 7]
[15, 31, 52, 7, 9, 22, 13]

Definiție. Se poate utiliza operatorul + pentru combinarea a două liste. Acest proces se
numește concatenare.
Observație. Operatorul * repetă o listă de un număr dat de ori.
Exemplul 5.7 #operatorii + și *
lista = [1, 3, 5]
print(lista + [9, 7, 5]) # Concatenarea listelor
print(["text"] * 3) # repetarea listelor

Output
[1, 3, 5, 9, 7, 5]
[' text', 'text', 'text']

În plus, se poate insera un element la o locație dorită prin utilizarea metodei insert(), sau
se pot insera multiple elemente în interiorul unei liste utilizând metoda slice.

Metoda insert() are doi parametri: primul indică poziția de inserare, iar al doilea
precizează elementul inserat.
Exemplul 5.8 #inserare cu metodele insert() și slice
lista = [1, 9]
lista.insert(1,3) # Utilizare metoda insert()
print(lista)
113 Liste

lista[2:2] = [5, 7] # Utilizare metoda slice


print(lista)

Output
[1, 3, 9]
[1, 3, 5, 7, 9]

5.6 Ștergerea sau eliminarea elementelor dintr-o listă


Se pot șterge unul sau mai multe elemente dintr-o listă (chiar întreaga listă) utilizând cuvântul
cheie del.
Exemplul 5.9 #ștergere utilizând del
lista = ['p', 'r', 'o', 'b', 'l', 'e', 'm', 'a']
del lista[2] #sterge un element
print(lista)
del lista[1:5] #sterge mai multe elemente
print(lista)
del lista #sterge toata lista
print(lista) # Eroare: Lista adefinita

Output
['p', 'r', 'b', 'l', 'e', 'm', 'a']
['p', 'm', 'a']
Traceback (most recent call last):
. . . . . .
print(lista) # Eroare: Lista nedefinită
NameError: name 'lista' is not defined

Se poate utiliza metoda remove() pentru eliminarea unui element dat sau metoda pop()
pentru a elimina un element al cărui index este dat.
În caz că indexul nu este dat, se elimină ultimul element al listei. Astfel, se poate implementa
modelui de stivă de date.
Pentru ștergerea tuturor elementelor unei liste se utilizează funcția clear().
Exemplul 5.10 #ștergere elemente cu remove(), pop(), clear()
lista = ['p', 'r', 'o', 'b', 'l', 'e', 'm', 'a']
lista.remove('p')
print(lista)
print(lista.pop(1)) #sterge (extrage) 'o'
print(lista)
print(lista.pop()) #sterge (extrage)'a'
print(lista)
lista.clear()
print(lista)

Output
['r', 'o', 'b', 'l', 'e', 'm', 'a']
o
['r', 'b', 'l', 'e', 'm', 'a']
a
['r', 'b', 'l', 'e', 'm']
[]
114 Liste

De asemenea, se pot șterge elemente din lista prin atribuirea unei liste vide unei felii (slice)
de elemente.
Exemplul 5.11 #ștergere elemente din listă prin metoda slice
lista = ['p', 'r', 'o', 'b', 'l', 'e', 'm', 'a']
lista[2:3] = [] #se sterge 'o' = elementul 2
print(lista)
lista[2:5] = [] #se sterg 'b', 'l', 'e' = elementele 2,3,4
print(lista)

Output
['p', 'r', 'b', 'l', 'e', 'm', 'a']
['p', 'r', 'm', 'a']

5.7 Tabel cu metodele obiectului listă


Tabelul de mai jos conține metodele obiectului list. Ele pot fi accesate prin comanda
dir(list).

append() – Adaugă un element la sfârșitul listei


extend() – Adaugă toate elementele unei liste la o altă listă
insert() – Inserează un element la indexul definit
remove() – Elimină un element din listă
pop() – Elimină și întoarce un element având un index dat
clear() – Șterge toate elementele din listă
index() – Întoarce indexul unui element dat, primul egal întâlnit
count() – Întoarce numărul de elemente de același fel dat ca argument
sort() – Sortează elementele dintr-o listă în ordine ascendentă
reverse() – Inversează ordinea elementelor dintr-o listă
copy() – Întoarce o copie a listei (copie distinctă, shallow copy)

Observație. Enumerarea metodelor obținute cu comanda dir() este mai sumară comparativ
cu aceea obținută cu comanda help(). În acest ultim caz se obțin mai multe informații, de
exemplu o scurtă descriere și lista argumentelor cu care se apelează metoda. În unele cazuri,
dacă textul care trebuie afișat este foarte mare, se afișează textul Squeezed text(nr.
linii). Pentru a expanda textul se aplică un dublu click pe acest mesaj.

Exemplul 5.12 #sortarea și inversarea listelor


lista = [3, 8, 1, 6, 0, 8, 4, 12]
print(lista.index(8))
print(lista.count(8))
lista.sort()
print(lista)
lista.reverse()
print(lista)

Output
1
2
[0, 1, 3, 4, 6, 8, 8, 12]
[12, 8, 8, 6, 4, 3, 1, 0]
115 Liste

5.8 Lista comprehensivă


În Python există o cale elegantă și concisă de a construi o nouă listă pornind de la secvențe
(iterabile) ca liste, tupluri, șiruri, etc.
Lista comprehensivă constă dintr-o expresie urmată de o instrucțiune for, for și if, sau dublu
for, totul fiind pus între paranteze pătrate.
Exemplul 5.13 #generarea unei liste comprehensive dintr-o listă
# Construirea unei liste comprehensive prin ridicarea
#la puterea a doua a elementelor altei liste numerice
pow2 = [2 ** x for x in range(10)]
print(pow2)

Output
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

Acest cod este echivalent cu:


pow2 = []
for x in range(10):
pow2.append(2 ** x)

Exemplul 5.14 #generarea unei liste comprehensive dintr-un tuplu


y_lista = [2*y for y in (1,2,3,4)]
print(y_lista)

Output
[2, 4, 6, 8]

Exemplul 5.15 #generarea unei liste comprehensive dintr-un șir


z_lista = [z.upper() for z in "kjhgf"]
print(z_lista)

Output
['K', 'J', 'H', 'G', 'F']

O “listă comprehensivă” poate conține opțional mai multe instrucțiuni for sau instrucțiuni
if. O instrucțiune opțională if poate fi un filtru la generarea unei noi liste, ca în următoarele
exemple.
Exemplul 5.16 #lista comprehensivă utilizând for și if
>>> pow2 = [2 ** x for x in range(10) if x > 5]
>>> pow2
[64, 128, 256, 512]

Exemplul 5.17 #lista comprehensivă utilizând for și if


>>> impare = [x for x in range(20) if x % 2 == 1]
>>> impare
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

Exemplul 5.18 #lista comprehensivă utilizând dublu for

>>> [x+y for x in ['A ','B '] for y in ['C','D']]


['A C', 'A D', 'B C', 'B D']
116 Liste

5.9 Alte operații cu liste în Python


5.9.1 Testul de apartenență la listă

Se poate testa dacă un element există într-o listă sau nu, utilizând cuvântul cheie in.
Exemplul 5.19 #test apartenență la listă
lista = ['c', 'a', 'l', 'c', 'u', 'l', 'a', 't', 'o', 'r']
print('t' in lista)
print('s' in lista)
print('w' not in lista)

Output
True
False
True

5.9.2 Parcurgerea unei liste

O listă poate fi parcursă element cu element (iterată) cu ajutorul unei bucle for.
Exemplul 5.20 #parcurgerea unei liste cu for și in
for culori in ['rosu','galben','albastru']:
print("O culoare a steagului este:", culori)

Output
O culoare a steagului este: rosu
O culoare a steagului este: galben
O culoare a steagului este: albastru

5.10 Tablourile 1D în Python. Modulul array


5.10.1 Generalități
În Python nu există un tip de dată “tablou” (se mai spune că Python nu are suport built-in
pentru tablouri).
În consecință, tablourile sunt implementate în Python pe baza tipului listă.
Între o listă și un tablou (denumit “array” în Python) este doar o deosebire de conținut,
stabilită prin convenție: în timp ce o listă conține date de tipuri diferite, un tablou (array)
conține numai date de același tip.
În continuare, denumirile “tablou” și “array” sunt echivalente.
Un utilizator care tratează listele ca tablouri nu poate impune constrîngeri asupra tipului
elementelor acesteia. Ca urmare, pentru lucrul cu tablouri în Python se utilizează modulul
array și doar în acest caz este impus ca toate elementele tabloului să fie de același tip.

Sintaxa pentru crearea unui tablou este următoarea:


Nume_tablou = nume_import_modul_array.array(tip_data, [elemente]),
unde:
117 Liste

elemente - elementele tabloului. Sunt accesate prin operațiunea de indexare. Indexul este un
întreg și începe cu valoarea zero.
Tablourile unidimensionale (1D), denumite și vectori, au ca elemente simple valori.
Observație importantă. Modulul array nu suportă structuri bidimensionale (2D). Pentru
lucrul cu acestea se recomandă utilizarea listelor de liste (care simulează tablourile 2D), sau
utilizarea modulului numpy.
tip_data este codul tipului (“typecode”), o literă utilizată pentru a defini tipul valorii
memorate în tablou. Codurile sunt următoarele:
b = numere întregi cu semn, reprezentate pe 1 octet.
B = numere întregi fără semn, reprezentate pe 1 octet.
u = caracter Unicode reprezentat pe 1 octet.
h, i = numere întregi cu semn reprezentate pe 2 octeți.
H, I = numere întregi fără semn reprezentate pe 2 octeți.
l = numere întregi cu semn reprezentate pe 4 octeți.
L = numere întregi fără semn reprezentate pe 4 octeți.
q = numere întregi cu semn reprezentate pe 8 octeți.
Q = numere întregi fără semn reprezentate pe 8 octeți.
f = numere reprezentate în virgulă flotantă pe 4 octeți.
d = numere reprezentate în virgulă flotantă pe 8 octeți.

Atributele și metodele modulului array se pot obține cu comanda:


>>> import array
>>> help(array)

Obiectele de tip array au atributele typecode și itemsize și o lungă listă de metode, dintre
care foarte utile sunt: append(), clear(), copy(), count(), extend(), index(),
inser(), pop(), remove(), reverse(), sort(), fromlist(), etc.

Exemplul 5.21 #crearea unor tablouri 1D


from array import *
xa2 = array('u', 'stelute: \u2731 \u273B \u229B')
for x in xa2:
print(x, ' ', end = '')
xa3 = array('l', [1, 2, 3, 4, 5])
for x in xa3:
print(x, ' ', end = '')
xa4 = array('d', [1.0, 2.0, 3.14])
for x in xa4:
print(x, ' ', end = '')

Output
s t e l u t e : ✱ ✻ ⊛ 1 2 3 4 5 1.0 2.0 3.14

Exemplul 5.22 #crearea unui array de numere reale fracționare


from array import *
xarray = array('d', [2.56,320.88,760.123,23.56,44.965])
for x in xarray:
print(x)
118 Liste

Output
2.56
320.88
760.123
23.56
44.965

5.10.2 Operațiile de bază cu un tablou (array) unidimensional (1D)


A1. Traversarea – parcurgerea sau imprimarea elementelor unul câte unul.
Exemplul 5.23 #accesarea unor elemente din tablou prin indexare
from array import *
xarray = array('d', [2.56,320.88,760.123,23.56,44.965])
print(xarray[0])
print(xarray[3])

Output
2.56
23.56
B1. Inserția – adăugarea unui element la un index dat.
Exemplul 5.24 #Adaugarea unui element folosind metoda insert()
from array import *
xarray = array('d', [2.56,320.88,760.123,23.56,44.965])
xarray.insert(1,50.2)
for x in xarray:
print(x)

Output
2.56
50.2
320.88
760.123
23.56
44.965

C1. Ștergerea – eliminarea unui element precizat prin valoarea sa.


Exemplul 5.25 #ștergerea unui element utilizând metoda remove()
from array import *
xarray = array('d', [2.56,320.88,760.123,23.56,44.965])
xarray.remove(760.123)
for x in xarray:
print(x)

Output
2.56
320.88
23.56
119 Liste

44.965

D1. Căutarea – găsirea unui element utilizând fie indexul, fie valoarea elementului.
Exemplul 5.26 #găsirea unui element prin index sau a poziției sale prin valoarea sa
from array import *
xarray = array('d', [2.56,320.88,760.123,23.56,44.965])
print(xarray[2]) #cautarea valorii utilizind indexul
print(xarray.index(23.56)) #cautarea indexului utilizand valoarea

Output
760.123
3
E1. Actualizarea – modificarea unui element având un index dat.
Exemplul 5.27 #Actualizarea prin reatribuirea unei noi valori elementului avand un index dat
from array import *
xarray = array('d', [2.56,320.88,760.123,23.56,44.965])
xarray[2] = 100
for x in xarray:
print(x)

Output
2.56
320.88
100.0
23.56
44.965

5.11 Liste de liste (tablourile bidimensionale 2D) în Python


5.11.1 Operațiile de bază cu tablourile 2D
Un tablou bidimensional 2D în Python este o listă 1D ale cărei elemente sunt liste 1D. În
tablourile 2D, pozițiile elementelor sunt precizate prin 2 indici (care încep cu valoarea zero),
astfel încât reprezentarea obținută este de tabel cu rânduri și coloane de celule conținând
datele.
Observație. Modulul array nu oferă suport pentru tablouri bidimensionale.
Fie următorul exemplu de catalog cu note obținute de studenți la câteva discipline:
Disc.1 Disc.2 Disc.3 Disc.4
Student1 8 9 7 10
Student2 10 9 10 9
Student3 9 10 8 -
Student4 7 8 6 5
Datele de mai sus pot fi reprezentate ca array 2D, astfel:
>>> TB = [[8, 9, 7, 10], [10, 9, 10, 9], [9, 10, 8, None], [7, 8, 6, 5]]
>>> print(TB[3][2])
6

Operațiile de bază cu un tablou (array) 2D sunt următoarele:


120 Liste

A2. Traversarea – parcurgerea sau imprimarea elementelor unul câte unul.


Exemplul 5.28 #accesarea unor elemente din tablou prin traversare
TB = [[8, 9, 7, 10], [10, 9, 10, 9], [9, 10, 8, None], [7, 8, 6, 5]]
print(TB)
print(TB[0])
print(TB[2])
print(TB[1][2])
print(TB[:][1])
print(TB[2][3])
print("Intreg tabloul:")
for r in TB:
for c in r:
print(c,end = " ")
print()

Output
[[8, 9, 7, 10], [10, 9, 10, 9], [9, 10, 8, None], [7, 8, 6, 5]]
[8, 9, 7, 10]
[9, 10, 8, None]
10
[10, 9, 10, 9]
None
Intreg tabloul:
8 9 7 10
10 9 10 9
9 10 8 None
7 8 6 5

B2. Inserarea – adăugarea unui element la un index dat.


Exemplul 5.29 #adaugarea unui element folosind metoda insert()
TB = [[8, 9, 9, 10], [10, 9, 10, 9], [9, 10, 8, None], [7, 8, 8, 9]]
TB.insert(2, [9,5,10,7])
for r in TB:
for c in r:
print(c,end = " ")
print()

Output
8 9 9 10
10 9 10 9
9 5 10 7
9 10 8 None
7 8 8 9
Observație. Tabloul poate să nu conțină None pentru elementele lipsă. Totuși, se va
semnaliza “index inexistent”, dacă se va dori acces la acele poziții.

C2. Ștergerea – eliminarea unui element precizat prin valoarea sa.


Exemplul 5.30 #ștergerea unui element utilizând metoda remove()
TB = [[8, 9, 9, 10], [10, 9, 10, 9], [9, 10, 8, None], [7, 8, 8, 9]]
TB.remove(TB[3])
for r in TB:
121 Liste

for c in r:
print(c,end = " ")
print()

Output
8 9 9 10
0 9 10 9
9 10 8 None

D2. Căutarea – găsirea unui element utilizând fie indexul, fie valoarea elementului.
TB = [[8, 9, 9, 10], [10, 9, 10, 9], [9, 10, 8, 9], [7, 8, 8, 9]]
print(TB[3][2]) #cautarea valorii utilizind indexul
#se cauta toate elementele TB = 8
n = 8
for i in range(4): #cautarea indexului utilizand valoarea
for j in range(4):
# print("i=", i, "j=", j, TB[i][j])
if TB[i][j] == n:
print("TB[", i, ",", j, "]")
Output
8
TB[ 0 , 0 ]
TB[ 2 , 2 ]
TB[ 3 , 1 ]
TB[ 3 , 2 ]

E2. Actualizarea – modificarea unui element având un index dat.


Exemplul 5.31 #Actualizarea prin reatribuirea unei noi valori elementului avand un index
dat
TB = [[8, 9, 9, 10], [10, 9, 10, 9], [9, 10, 8], [7, 8, 8, 9]]
TB[2] = [9, 7, 6, 5]
TB[0][2] = 8
for r in TB:
for c in r:
print(c,end = " ")
print()

Output
8 9 8 10
10 9 10 9
9 7 6 5
7 8 8 9

5.11.2 Netezirea listelor de liste

În Python, o listă de liste apare ca un tablou bidimensional. Transformarea acestei structuri în


una unidimensională (flatten list), se numește netezire sau aplatizare (flattennig).
Netezirea sau aplatizarea listelor se aplică la liste de liste ca, de exemplu, lista: [[1, 2, 3], [5,
6, ]], care se netezește la [1, 2, 3, 5, 6].
122 Liste

Aplicațiile în Python în care se solicită netezirea listelor sunt frecvente și din acest motiv s-au
dezvoltat mai multe tehnici de flattening.
a. Netezirea unei liste de liste utilizând bucla for
Exemplul 5.32 #netezirea unei liste de liste cu bucla for
lista_de_liste = [[7,4,3,6],["alfa", "beta", "gama"], [5.2, 2+3j,34.92,
11.23, 5, 645]]
lista_neteda = []
for lst in lista_de_liste:
for termen in lst:
lista_neteda.append(termen)
print(lista_neteda)

Output
[7, 4, 3, 6, 'alfa', 'beta', 'gama', 5.2, (2+3j), 34.92, 11.23, 5, 645]

b. Netezirea unei liste de liste utilizând comprehensiunea listelor


Exemplul 5.33 #netezirea unei liste de liste folosind comprehensiunea listelor
lista_de_liste = [[7,4,3,6],["alfa", "beta", "gama"], [5.2, 2+3j,34.92,
11.23, 5, 645]]
lista_neteda = [termen for lst in lista_de_liste for termen in lst]
print(lista_neteda)

Output
[7, 4, 3, 6, 'alfa', 'beta', 'gama', 5.2, (2+3j), 34.92, 11.23, 5, 645]

5.12 Întrebări, exerciții și probleme


5.12.1 Cum se pot prelua aleatoriu elemente dintr-o listă?
R5.12.1 Se utilizează secvența de cod:
from random import choice
x_lista = ["Microsoft", "Linux", "Portocale"]
elem_aleator = choice(x_lista)
print(elem_aleator)

Output
Linux #Rularea 1
Microsoft #Rularea 2

5.12.2 Cum se pot elimina duplicatele dintr-o listă (două metode) ?


R5.12.2 Se utilizează secvența de cod:
y_lista = [3, 2, 1, 1, 2, 3, 4, 5 ,6, 7, 8, 8]
l = list(dict.fromkeys(y_lista)) #prima metoda
print(l)
l = list(set(y_lista)) # a doua metoda - nu se pastreaza ordinea
print(l)

Output
[3, 2, 1, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
123 Liste

5.12.3 Să se parcurgă în sens invers următoarea listă:


Lista_fructe = ["Cireasa", "Para", "Pruna", "Gutuie"]
R5.12.3 Se execută codul:
Lista_fructe = ["Cireasa", "Para", "Pruna", "Gutuie"]
for fruct in reversed(Lista_fructe):
print(fruct)

Output
Gutuie
Pruna
Para
Cireasa

5.12.4 Să se tipărească elementele listei într-o ordine impusă de utilizator.


R5.12.4 Dacă este necesar să se imprime valorile unei liste în diferite ordine, se poate atribui
lista unei serii de variabile și se decide prin program ordinea în care se dorește să se imprime
lista.
Lista = [1,2,3]
w, v, t = Lista
print(v, w, t)
print(t, v, w)

Output
2 1 3
3 2 1

5.12.5 Să se inverseze elementele unei liste folosind secționarea listei (slicing).


R5.12.5 Secționarea listelor este o tehnică foarte puternică în Python, care poate fi folosită și
pentru a inversa ordinea elementelor dintr-o listă.
#Inversarea șirurilor
lista1 = ["a", "b", "c", "d"]
print (lista1 [:: - 1])

#Inversarea numerelor
lista2 = [21,35,16,44,22]
print (lista2 [:: - 1])
['d', 'c', 'b', 'a']
[22, 44, 16, 35, 21]

Output
['d', 'c', 'b', 'a']
[22, 44, 16, 35, 21]

5.12.6 Să se genereze un tablou de numere naturale și un tablou de numere reale cu 3


elemente utilizând modulul array.
R.5.12.6 Se execută codul:
import array as arr
124 Liste

# creare tablou de numere intregi


a = arr.array('i', [1, 2, 3])
print ("Tabloul de numere intregi este: ", end =" ")
for i in range (0, 3):
print (a[i], end =" ")
print()
# creare tablou de numere reale
b = arr.array('d', [2.5, 3.2, 3.3])
print ("Tabloul de numere reale este: ", end =" ")
for i in range (0, 3):
print (b[i], end =" ")

Output
Tabloul de numere intregi este: 1 2 3
Tabloul de numere reale este: 2.5 3.2 3.3

5.12.7 Să se scrie o secvență de utilizare a metodei array.extend().


R5.12.7 În Python un tablou poate fi extins simultan cu mai multe valori prin metoda
extend().Sintaxa este: array.extend(iterable).

import array as ar
x_arr = ar.array('i', [1,2,3,4,5])
print(x_arr)
x_ext_arr = ar.array('i', [6,7,8,9,10])
print(x_ext_arr)
x = x_arr.extend(x_ext_arr)
print(x_arr)

Output
array('i', [1, 2, 3, 4, 5])
array('i', [6, 7, 8, 9, 10])
array('i', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 6

Tupluri
6.1 Definiția și crearea tuplurilor
Definiție. Un tuplu este asemănător unei liste, cu deosebirea că elementele sale sunt fixe (nu
pot fi modificate așa cum este posibil într-o listă). Un tuplu poate avea oricâți membri, de
diferite tipuri (integer, float, listă, șir, etc.).
Crearea unui tuplu. Un tuplu este creat prin plasarea tuturor elementelor sale între paranteze
rotunde (), separate prin virgule. Totuși, parantezele sunt opționale, folosirea lor fiind doar
recomandată.
Exemplul 6.1 # crearea diferitelor tipuri de tupluri
# Tuplu vid
tuplu = ()
print(tuplu)
# Tuplu format din intregi
tuplu = (1, 2, 3)
print(tuplu)
# Tuplu conținand diferite tipuri de data
tuplu = (1, "Buna ziua", 3.4)
print(tuplu)
# Tuplu încuibat
tuplu = ("albastru", [8, 4, 6], (1, 2, 3))
print(tuplu)

Output
()
(1, 2, 3)
(1, 'Buna ziua', 3.4)
('albastru', [8, 4, 6], (1, 2, 3))

Un tuplu poate fi creat fără utilizarea parantezelor, prin asa numita operație de “împachetare a
tuplului”. Este posibilă, de asemenea, “despachetarea tuplului”.
Exemplul 6.2 #împachetarea și despachetarea tuplului
>>> tuplu = 6, 5.5, "verde"
>>> tuplu
(6, 5.5, 'verde')
>>> a, b, c = tuplu
>>> print(a)
6
>>> print(b)
5.5
>>> print(c)
verde

Observație. Crearea unui tuplu cu un singur element implică un artificiu, deoarece existența
unui element între paranteze nu este suficientă. Este necesar să se adauge o virgulă, pentru a
se preciza că este vorba, de fapt, de un tuplu.
Exemplul 6.3 #creare tuplu cu un element
# Fara virgula se creeaza un sir
126 Tupluri

tuplu = ("violet")
print(type(tuplu))
# Crearea unui tuplu cu un element
tuplu = ("violet",)
print(type(tuplu))
# Creare tuplu fără paranteze
tuplu = "violet",
print(type(tuplu))

Output
<class 'str'>
<class 'tuple'>
<class 'tuple'>

6.2 Accesarea elementelor unui tuplu


Pentru a ajunge la elementele unui tuplu se folosesc indexarea pozitivă/negativă și felierea
(slicingul).

6.2.1 Indexarea pozitivă

Indexarea utilizează operatorul index [] pentru accesarea unui element aparținînd unui tuplu.
Valorile indecșilor încep de la 0. Indexul unui tuplu care are 8 elemente are valori între 0 și 7.
Încercarea de accesare a unui index în afara domeniului indexului va produce o eroare de index
de tipul IndexError.
Tipul indexului trebuie să fie întreg. Nu se pot utiliza indecși de tipul float sau alte tipuri
deoarece apare ca rezultat al execuției mesajul TypeError.
Tuplurile încuibate pot fi accesate prin utilizarea indexării încuibate, ca în exemplul următor.
Exemplul 6.4 #accesare tuplu încuibat
# Accesarea tuplului prin indexare
tuplu = ('g','a','l','b','e','n') #interval de valori index: 0 la 5
print(tuplu[0]) # 'g'
print(tuplu[5]) # 'n'
# tuplu încuibat
tuplu = ("verde", [10, 12, 14], (11, 13, 15))
print(tuplu[0][3])
print(tuplu[1][1])
# print(tuplu[6]) # IndexError: index în afara domeniului
# Indexul trebuie să fie un întreg
tuplu[2.0] # eroare de tip: TypeError (indexul nu este întreg)

Output
g
n
d
12
Traceback (most recent call last):
. . . . . .
tuplu[2.0] # eroare de tip: TypeError (indexul nu este întreg)
TypeError: tuple indices must be integers or slices, not float
127 Tupluri

6.2.2. Indexarea negativă

Python permite indexarea negativă a tipurilor sale de tip secvență.


Indexul egal cu -1 se referă întotdeauna la ultimul element al secvenței (primul în sens
invers), indexul egal cu -2 se referă la penultimul (al doilea în sens invers), și așa mai departe.
Exemplul 6.5 # Indexarea negativă la accesarea tuplelor
tuplu = ('a', 'u', 't', 'o', 'm', 'o', 'b', 'i', 'l')
print(tuplu[-1])
print(tuplu[-9])
Output
l
a

6.2.3 Slicingul (felierea) tuplurilor

Definiție.Slicingul este o metodă prin care se poate accesa un domeniu de elemente al unui
tuplu prin utilizarea operatorului “colon” (:).
Exemplul 6.6 # Accesarea elementelor unui tuplu utilizând slicingul
tuplu = ('u','n','i','v','e','r','s','a','l')
# elementele al 2-lea până la al 4-lea
print(tuplu[1:4])
# elementele începând cu al 2-lea
print(tuplu[:-7])
# elementele al 8-lea până la sfârșit
print(tuplu[7:])
# elementele de la început până la sfârșit
print(tuplu[:])

Output
('n', 'i', 'v')
('u', 'n')
('a', 'l')
('u', 'n', 'i', 'v', 'e', 'r', 's', 'a', 'l')
Slicingul poate fi imaginată considerând că indexul se raportează la elementele tuplului ca în
exemplul de mai jos. Dacă se dorește accesarea unui domeniu, este necesar ca indexul să fie
specificat corespunzător domeniului din tuplu.
u n i v e r s a l
0 1 2 3 4 5 6 7 8
-9 -8 -7 -6 -5 -4 -3 -2 -1

6.3 Schimbarea elementelor unui tuplu


Spre deosebire de liste, tuplele sunt imutabile.
“Element imutabil” înseamnă că acesta nu poate fi modificat odată ce a fost atribuit unui tuplu.
Dar dacă elementul este el însuși o listă care prin definiție este mutabilă, pot fi făcute schimbări
în interiorul listei încuibate într-un tuplu.
128 Tupluri

Singurul caz în care funcționează reatribuirea elementelor, în cazul unui tuplu, este acela în
care atribuirea este totală, nu parțială.
Exemplul 6.7 # Elementele unui tuplu sunt imutabile
tuplu = (4, 2, 3, [6, 5])
# tuplu[1] = 9

Output
Traceback (most recent call last):

tuplu[1] = 9
TypeError: 'tuple' object does not support item assignment

Exemplul 6.8 # Cazuri de modificare a valorilor elementelor unui tuplu


tuplu = (4, 2, 3, [6, 5])
# Elementele reprezentând mutabile se pot schimba
tuplu[3][0] = 9 # Output: (4, 2, 3, [9, 5])
print(tuplu)
# Reatribuirea tuplului (totala)
tuplu = ('m', 'o', 'n', 'd', 'i', 'a', 'l')
print(tuplu)

Output
(4, 2, 3, [9, 5])
('m', 'o', 'n', 'd', 'i', 'a', 'l')

Se poate utiliza operatorul + pentru a combina două tupluri. Acest proces este denumit de obicei
concatenare.
Se pot repeta elementele într-un tuplu, de un număr precizat de ori, utilizând operatorul *.
Rezultatul aplicării operatorilor + și * este producerea de noi tupluri.
Exercițiul 6.9 #concatenarea
# Concatenare
print((1, 2, 3) + (4, 5, 6))
# Repetare
print(("Ploaie",) * 3)

Output
(1, 2, 3, 4, 5, 6)
(' Ploaie', ' Ploaie', ' Ploaie')

6.4 Ștergerea unui tuplu


Deoarece elementele unui tuplu nu se pot schimba, nu se poate face nici ștergerea, nici
eliminarea acestora dintr-un tuplu. În consecință, este posibilă doar ștergerea întregului tuplu,
în ansamblul său, prin utilizarea cuvântului cheie del.
Exercițiul 6.10 #ștergere tuplu
tuplu = ('o', 'r', 'g', 'a', 'n', 'i', 'z', 'a', 'r', 'e')
# Stergere intreg tuplul
del tuplu
print(tuplu)

Output
129 Tupluri

Traceback (most recent call last):



print(tuplu)
NameError: name 'tuplu' is not defined

6.5 Alte metode aplicabile obiectului tuplu


Alte metode permit realizarea a diferite operații cu tuple.

a. Testul de apartenență la tuplu

Se poate testa dacă un element există într-un tuplu, folosind cuvântul cheie in.

Exemplul 6.11 #testarea apartenenței și neapartenenței la un tuplu cu in


# Testul de apartenenta la tuplu
tuplu = ('p', 'o', 'm', 'p', 'i', 'e', 'r')
# Apartenenta
print('m' in tuplu)
print('b' in tuplu)
# Neapartenenta
print('x' not in tuplu)

Output
True
False
True

b. Parcurgerea (iterarea) unui tuplu

Se poate utiliza bucla de tip for pentru parcurgerea (iterarea) tuturor elementelor unui tuplu.

Exemplul 6.12 #iterarea tuplului cu for


# Utilizarea buclei for pentru iterarea elementelor unui tuplu
for nume in ('Ion', 'Maria'):
print("Buna ziua,", nume)

Output
Buna ziua, Ion
Buna ziua, Maria

6.6 Avantajele tuplurilor comparativ cu listele, în cazuri specifice


Între liste și tupluri sunt multe asemănări, iar acest fapt determină utilizarea lor în situații
similare. Totuși, utilizarea tuplurilor prezintă unele avantaje comparativ cu listele, în unele
situații. Mai jos sunt redate căteva avantaje mai importante, în anumite cazuri:
• În general tuplurile sunt recomandate când elementele lor componente sunt de tipuri diferite
(sunt eterogene), iar listele sunt recomandate când elementele lor componente sunt de tipuri de
același fel (sunt omogene). Se reamintește că tipul tablou nu există în Python, dar o formă de
substituție utilizabilă ca tablou se realizează pe baza listelor (cu elemente omogene).
130 Tupluri

• Deoarece tuplurile sunt imutabile, iterarea printr-un tuplu este mai rapidă decât printr-o listă
(tuplurile sunt ușor mai performante comparativ cu listele).
• Tuplurile, care conțin elemente imutabile, pot avea rolul de cheie în dicționare. Listele nu
permit acest lucru.
• Dacă aplicația utilizează date care trebuie să rămână fixe, protejarea lor la schimbare se
poate face prin declararea lor ca tupluri.

6.7 Întrebări, exerciții și probleme


6.7.1 Ce efect va avea operandul += în cazul unui tuplu, cunoscând că acesta e imutabil?
R6.7.1 Spre deosebire de liste, tuplurile nu au metoda append. Utilizarea operandului += cu un
tuplu este posibilă, dar el modifică legătura variabilei, dar nu și tuplul însuși. Exemplu:
>>>xtuplu = (4,7)
>>>ytuplu = xtuplu
>>>xtuplu += (8, 9)
>>>xtuplu
(4, 7, 8, 9)
>>>ytuplu
(4, 7)
Concluzie: Pentru a aplica corect operatorul += unui tuplu (pentru a adăuga “ceva” acestuia),
se va crea un tuplu având și elementele de adăugat și apoi se va atribui variabila curentă
vechiului tuplu. Vechiul tuplu nu poate fi schimbat, ci doar înlocuit.

6.7.2 O singură valoare între parantezele () este un tuplu?


R6.7.2 Mai trebuie o virgulă. Pentru a crea un tuplu cu un singur element este necesară
adăugarea unei virgule. Exemplu:
>>>xt = ("a")
>>>type(xt)
<class 'str'>
>>>xt = ("a",)
>>>type(xt)
<class 'tuple'>
>>>a = 1
>>>type(a)
<class 'int'>
>>>a = 1,
>>>type(a)
<class 'tuple'>

6.7.3 Se pot concatena două tuple?


R6.7.3 Da, se va genera un nou tuplu.

xt1 = ("a", "b", "c")


xt2 = (1, 2, 3)
xt = xt1 + xt2
print(xt)
('a', 'b', 'c', 1, 2, 3)

6.7.4 Să se scrie o secvență de program pentru a verifica existența unui element într-un tuplu.
R6.7.4
131 Tupluri

xtuplu = ("a", 1,2,3, "b", "o", "t", "c", "d", "$", 20)
print("h" in xtuplu)
False
print("$" in xtuplu)
True

6.7.5 Să se scrie o secvență de program care să găsească numărul de repetări al unui element
în tuplu.
R6.7.5
>>>xtuplu = ("a", 1,2,3, "b", "a", "t", "c", "a", "$", 20)
>>>repetari_a = xtuplu.count("a")
>>>print(repetari_a)
3

6.7.6 Să exemplifice cazurile cele mai frecvente de slicing pentru tuplul:


xt = (7,4,4,9,5,7,9,1,2,2,6,8)

R6.7.6 Se reamintește că utilizând forma xtuplu(start:stop), elementul start este inclus


(implicit) în selecție, iar elementul stop este exclus din intervalul selectat.
a. >>> xt = (7,4,4,9,5,7,9,1,2,2,6,8)
>>> t = xt[4:7] #se vor selecta elementele cu indexul 4,5 si 6
>>> print(t)
(5, 7, 9)
b. Dacă indexul de start nu este definit, este luat implicit 0.
>>> t = xt[:5]
>>> print(t)
(7, 4, 4, 9, 5)
c. Dacă indexul stop nu este definit, se ia implicit indexul maxim al
tuplului.
>>> t = xt[5:]
>>> print(t)
(7, 9, 1, 2, 2, 6, 8)
d. Dacă start și stop nu sunt precizați, se consideră întreg tuplul.
>>> t = xt[:]
>>> print(t)
(7, 4, 4, 9, 5, 7, 9, 1, 2, 2, 6, 8)
e. Indexul poate fi definit cu valori negative.
>>> t = xt[-7:-4]
>>> print(t)
(7, 9, 1)

Selecția se poate redefini și în forma: t = xt[start:stop:pas], pas fiind valoarea


de incrementare între indecșii elementelor selectate .
f. Obținerea unei selecții cu elemente din 4 în 4.
>>> t = xt[::4]
>>> print(t)
(7, 5, 2)
g. Dacă pasul este negativ, saltul indexului se face înapoi.
>>> t = xt[10:3:-3]
>>> print(t)
(6, 1, 5)

6.7.7 Să se scrie o secvență de program pentru conversia unui tuplu în dicționar.


R6.7.7
132 Tupluri

>>> xt = ((5, "w"), (9, "z"))


>>> d = dict((w,z) for w,z in xt)
>>> print(d)
{5: 'w', 9: 'z'}

6.7.8 Să se scrie o secvență de programare pentru numărarea elementelor dintr-un tuplu.


R6.7.8
>>>xt = (7,4,4,9,(1,2),5,7,9,(3,4),1,2,2,(5,6),6,8)
>>>contor = 0
...for n in xt:
... if isinstance(n,tuple):
... contor += 1
... else: continue
>>>print(contor)
3

6.7.9 Care este ieșirea următoarei secvențe de program?


xt = (100,)
print(xt*2)
a) 200 b) TypeError c) (100, 100) d) (10000,)

R6.7.9 c)

6.7.10 Care va fi ieșirea următoarei secvențe de program?


xt = (10, 20, "M")
print(max(xt))

a)20 b)”a” c)TyoeError d) None

R6.7.10 c
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 7

Șiruri
7.1 Conceptul de șir
Definiție. Un șir este o secvență de caractere (caracterul este un simbol: cifră, literă a
alfabetului, semn de punctuație, etc.).
Calculatoarele utilizează caracterele indirect, prin intermediul codurilor corespunzătoare lor în
binar. În memoria calculatorului, orice caracter este reprezentat ca o combinație de 0 și 1.
Conversia unui caracter într-un număr binar se numește codare, iar operația inversă, decodare.
Cele mai răspândite și mai utilizate sisteme de coduri sunt ASCII și Unicode.
In Python, un șir este o secvență de caractere Unicode. Spre deosebire de ASCII, limitat la cele
26 de litere ale alfabetului englez și având în total maximum 256 de coduri, Unicode, dezvoltat
mai târziu, include coduri pentru toate limbile și are și alte particularități.

7.2 Crearea șirurilor


Șirurile pot fi create în Python prin includerea caracterelor între ghilimele simple (apostrofuri)
sau duble. Sunt utilizate chiar și ghilimele triple pentru reprezentarea șirurilor multilinie sau a
docstringurilor.
Exemplul 7.1 # Crearea (definirea) unui șir
# toate variantele care urmează sunt echivalente
sirul1 = 'Buna ziua!'
print(sirul1)
sirul2 = "Buna ziua!"
print(sirul2)
sirul3 = '''Buna ziua!'''
print(sirul3)
# Ghilimelele triple permit întinderea șirurilor pe mai multe linii
sirul4 = """Buna ziua,
bine ati venit!"""
print(sirul4)

Output
Buna ziua!
Buna ziua!
Buna ziua!
Buna ziua,
bine ati venit!

7.3 Accesarea caracterelor unui șir


Se poate accesa un caracter individual dintr-un șir utilizând indexarea, iar un domeniu de
caractere se poate accesa utilizând slicingul. Indexarea începe de la valoarea 0. Tentativa de
accesare în afara domeniului indexului va genera eroarea IndexError. Indexul va fi un întreg,
nu se pot folosi float sau alte tipuri deoarece rezultă TypeError.
134 Șiruri

Python permite indecși negativi și în cazul șirurilor. Indexul egal cu -1 va referi ultimul element
al șirului, -2 va referi penultimul (al doilea de la sfârșit spre început), etc. Se poate accesa un
domeniu în șir prin utilizarea operatorului colon (:).
Exemplul 7.2 #Accesarea unui șir de caractere
sirul = 'activitate'
print('sirul = ', sirul)
print('sirul[0] = ', sirul[0]) #primul caracter
print('sirul[-1] = ', sirul[-1]) #ultimul caracter
print('sirul[1:5] = ', sirul[1:5]) #domeniul incepand cu al doilea,
#sfarsind cu al cincilea caracter
print('sirul[5:-2] = ', sirul[5:-2]) #domeniul incepand cu al 6-lea,
#sfarsind cu al 8-lea caracter

Output
sirul = activitate
sirul[0] = a
sirul[-1] = e
sirul[1:5] = ctiv
sirul[5:-2] = ita

Dacă se încearcă accesarea unui index în afara intervalului definit, sau se utilizează ca indecși
altceva decât întregi, se va obține o eroare.
Exemplul 7.3 # indexul trebuie să fie în interiorul domeniului
>>> sirul[15]

Output
...
IndexError: string index out of range

Exemplul 7.4 # indexul trebuie să fie un întreg


>>> sirul[1.5]

Output
...
TypeError: string indices must be integers

Slicingul poate fi implementat dând indexului valori între două elemente.


În figura de mai jos se vizualizează legătura dintre poziția elementului în șir și valoarea
indexului pozitiv, sau negativ.
u n i v e r s a l
0 1 2 3 4 5 6 7 8
-9 -8 -7 -6 -5 -4 -3 -2 -1

7.4 Modificarea sau ștergerea unui șir


Șirurile sunt imutabile. Acest fapt înseamnă că elementele unui șir nu pot fi modificate din
momentul în care au fost alocate. Cel mult este posibilă reatribuirea denumirii vechiului șir la
un nou șir creat.
Exemplul 7.5 #un șir este imutabil
>>> sirul = 'perforator'
>>> sirul[5] = '4'
135 Șiruri

Output
. . . . .
sirul[5] = '4'
TypeError: 'str' object does not support item assignment

Exemplul 7.6 #numele `sirul` este atribuit unui alt șir


>>> sirul = 'Python'
>>> sirul

Output
'Python'

În concluzie, nu se pot sterge sau elimina caractere dintr-un șir. Este posibilă totuși ștergerea
întregului șir cu cuvântul cheie del.

Exemplul 7.7 # ștergerea întregului șir cu cuvântul cheie del

>>> del sirul[1]

Output
...
TypeError: 'str' object doesn't support item deletion

>>> del sirul


>>> sirul

Output
...
NameError: name 'sirul' is not defined

7.5 Operații cu șiruri


Șirurile sunt printre cele mai folosite tipuri de date în Python. Ca urmare, s-au definit multe
operații care pot fi aplicate șirurilor.

7.5.1 Concatenarea a două sau mai multe șiruri


Definiție. Alipirea a două sau mai multe șiruri într-unul singur este denumită concatenare.
Concatenarea este posibilă prin intermediul operatorului +.
Exemplul 7.8 # concatenare șiruri
sir1 = 'Buna'
sir2 =' Ziua!'
# utilizarea operatorului +
print('sir1 + sir2 = ', sir1 + sir2)

Output
sir1 + sir2 = Buna Ziua!

Operatorul * poate fi utilizat pentru repetarea de un număr de ori a unui șir.


Exemplul 7.9 # utilizarea operatorului * pentru multiplicarea șirurilor
print('sir1 * 3 =', sir1 * 3)
136 Șiruri

Output
Sir1 * 3 = BunaBunaBuna
De asemenea, simpla punere alăturată prin scriere a două șiruri produce concatenarea (la fel
ca operatorul +).
Exemplul 7.10 #concatenare prin lipirea șirurilor
>>> 'Buna ''Ziua!'

Output
'Buna Ziua!'
Dacă șirurile concatenate se întind pe linii diferite, atunci este necesară folosirea parantezelor.
Exemplul 7.11 # utilizarea parantezelor
>>> ('Buna '
... 'Ziua!')

Output
'Buna Ziua!'

7.5.2 Iterarea unui șir


Un șir se poate parcurge (itera) cu ajutorul unei bucle for. În exemplul următor se numără
aparițiile caracterului ‘a’ într-un șir.
Exemplul 7.12 # iterarea unui sir
contor = 0
for litera in 'Buna ziua, stimat auditoriu!':
if(litera == 'a'):
contor += 1
print(contor,'litere gasite')

Output
4 litere gasite

7.5.3 Testarea apartenenței la șir


Se poate testa dacă un subșir există într-un șir sau nu, cu ajutorul cuvântului cheie in.
Exemplul 7.13 #apartenența sau neapartenența la un șir
>>> 'g' in 'program'
True
>>> 'rc' not in 'remarcabil'
False

7.5.4 Funcții pre-construite pentru lucrul cu șiruri


Pentru lucrul cu tipuri de date tip “secvență” sunt disponibile în Python multe funcții pre-
construite (built-in). Unele dintre aceste funcții sunt aplicabile șirurilor.
Printre acestea sunt enumerate() și len(). Funcția enumerate() întoarce un obiect
enumerare, care conține indexul și valorile elementelor dintr-un șir, ca perechi. Acest obiect
poate fi util pentru realizarea unei iterări.
Asemănător, funcția len() întoarce lungimea șirului (numărul de caractere).
137 Șiruri

Exemplul 7.14 #utilizarea funcțiilor enumerate() și len()


sir = 'oltean'
# functia enumerate(), numărarea individuală a caracterelor din sir
list_enum = list(enumerate(sir))
print('enumerare_caractere_sir = ', list_enum)

#functia len, lungimea sirului


print('lungime_sir = ', len(sir))

Output
enumerare_caractere_sir = [(0, 'o'), (1, 'l'), (2, 't'), (3, 'e'), (4,
'a'), (5, 'n')]
lungime_sir = 6

7.6 Formatarea șirurilor


7.6.1 Secvența Escape
Dacă se dorește imprimarea unui text ca, de exemplu: Filmul se numeste "D'ale lui
Pacala", nu se pot folosi nici ghilimele simple, nici ghilimele duble deoarece nu se poate evita
SyntaxError pentru că șirul conține atât ghilimele simple, cât și duble.

Exemplul 7.15 #eroare la imprimarea unui text cu ghilimele fără secvența escape
>>> print("Filmul se numeste "D'ale lui Pacala"")

Output
...
SyntaxError: invalid syntax

O cale de a ocoli aceste probleme este utilizarea ghilimelelor triple. O altă cale este utilizarea
secvențelor Escape.
O secvență Escape începe cu un backslash (\) , fiind interpretată diferit. Dacă se utilizează o
ghilimea simplă pentru reprezentarea unui șir, toate ghilimele simple din interiorul șirului
trebuie să fie marcate ca Escape. Asemănător în cazul ghilimelelor duble.
Exemplul 7.16 #utilizare secvențe Escape
# utilizarea ghilimelelor triple
print('''Filmul se numeste "D'ale lui Pacala"''')

# marcarea ghilimelelor simple ca escape


print('Filmul se numeste "D\'ale lui Pacala"')

# marcarea ghilimelelor duble ca escape


print("Filmul se numeste \"D'ale lui Pacala\"")

Output
Filmul se numeste "D'ale lui Pacala"
Filmul se numeste "D'ale lui Pacala"
Filmul se numeste "D'ale lui Pacala"

În continuare se prezintă o listă cu secvențe Escape acceptate în Python și descrierea


semnificației lor.
138 Șiruri

Secvența Escape Descriere


\newline – Linie nouă
\\ - Backslash
\' - Ghilimea simplă (apostrof)
\" - Ghilimea dublă
\a - ASCII Bell (sonerie)
\b - ASCII Backspace
\f - ASCII Formfeed
\n - ASCII Linefeed
\r - ASCII Carriage Return
\t - ASCII Horizontal Tab
\v - ASCII Vertical Tab
\ooo - Valoare octală ooo
\xHH - Valoare hexazecimală HH

Exemplul 7.17 #utilizare secvențe Escape


>>> print("C:\\Python32\\Lib")
C:\Python32\Lib
>>> print("Text tipărit\n pe două linii")
Text tipărit
pe două linii
>>> print("Reprezentare \x48\x45\x58")
Reprezentare HEX

7.6.2 Șiruri brute (raw)

Uneori se dorește imprimarea unui șir fără modificările determinate de secvențele Escape.
Acest lucru se poate face prin punerea unui r sau R în fața șirului. Ca urmare, secvențele Escape
din interiorul șirului vor fi ignorate.
Exemplul 7.18 #imprimarea cu ignorarea secvențelor Escape
>>> print("Imprimare sir \n pe un singur rand")
Imprimare sir
pe un singur rand
>>> (r"Imprimare sir \n pe un singur rand")
'Imprimare sir \\n pe un singur rand'

7.6.3 Formatarea șirurilor cu metoda format()


Pentru obiectele de tip șir este disponibilă o metodă de formatare foarte puternică și versatilă,
denumită format(). Șirurile formatate trebuie să conțină perechi de acolade {} ca indicatori
sau câmpuri de substituție care vor fi înlocuite cu informație.
De asemenea, se pot plasa (opțional) argumente numerice sau argumente cuvinte cheie
poziționale pentru a specifica ordinea.
Exemplul 7.19 # metoda format() de formatare șiruri
# ordonarea naturală (implicita)
ordonare_naturala = "{}, {} si {}".format('Mihai','George','Stefan')
print('\n--- Ordonare naturala (implicit) ---')
print(ordonare_naturala)

# ordonare folosind argumente poziționale


ordonare_poziționala = "{1}, {0} si {2}".format('Mihai','George','Stefan')
139 Șiruri

print('\n--- Ordonare poziționala ---')


print(ordonare_poziționala)

# ordonare folosind argumente cuvinte cheie


ordonare_cuv_cheie = "{s},{b} si {j}".format(j='Mihai', b='George',
s='Stefan')
print('\n--- Ordonare cuvinte cheie ---')
print(ordonare_cuv_cheie)

Output
--- Ordonare naturala ---
Mihai, George si Stefan

--- Ordonare poziționala ---


George, Mihai si Stefan

--- Ordonare cuvinte cheie ---


Stefan, George si Mihai

Metoda format() poate avea specificații formale, separate de numele câmpului prin colon (:).
Exemplu de specificații referitoare la șiruri: alinere la stânga (<), aliniere la dreapta (>), sau
centrare (^).
De asemenea, se pot formata întregii ca binar, hexazecimal, etc., iar numerele fracționare (float)
pot fi rotunjite sau afișate în format exponențial. Există un număr mare de metode de formatare
variată a șirurilor în vederea reprezentării la ieșire.
Exemplul 7.20 #formatarea numerelor
>>> # formatarea întregilor
>>> "Reprezentarea binara a lui {0} este {0:b}".format(12)
' Reprezentarea binara a lui 12 este 1100'

>>> # formatarea numerelor flotante


>>> "Reprezentarea exponențiala: {0:e}".format(1566.345)
' Reprezentarea exponențiala: 1.566345e+03'

>>> # rotunjirea
>>> "O treime este: {0:.3f}".format(1/3)
'O treime este: 0.333'

>>> # alinierea șirurilor


>>> "|{:<10}|{:^10}|{:>10}|".format('strada','sapca','apa')
'|strada | sapca | apa|'

7.6.4 Formatare clasică (specifică limbajului C standard)

Este posibil și stilul de formatare mai vechi (cunoscut din limbajul C standard), cu ajutorul
operatorului %.
Exemplul 7.21 #formatarea ca în limbajul C standard
>>> x = 12.3456789
>>> print('Valoarea lui x este: %3.2f' %x)
Valoarea lui x este: 12.35
>>> print(' Valoarea lui x este: %3.4f' %x)
Valoarea lui x este: 12.3457

Formatarea ca în limbajul C standard cu utilizarea unui tuplu:


140 Șiruri

>>>nume = 'Dorel'
>>>varsta = 25
>>print('Varsta lui %s este %d ani'%(nume, varsta))
Varsta lui Dorel este 25 ani

7.6.5 Formatarea cu prefix f (metoda f-strings)

Formatarea literalelor șir prin metoda “f-strings” a apărut în versiunea Python 3.6 și a introdus
o simplificare semnificativă a printării. Șirurile “f-strings” au un f (sau F) la început și acolade
conținând expresii (chiar și funcții) care vor fi înlocuite de valori. Expresiile (și/sau funcțiile,
dacă este cazul) sunt evaluate la execuție și formatate cu protocolul __format__.
Observație. Prefixul “f” provine de la cuvântul “fast” (rapid).
Exemplul 7.22 #formatarea cu prefix f (metoda f-strings)
>>>nume = 'Mihai'
>>>nota = 10
>>>print(f'Lucrarea lui {nume} a primit nota {nota})
Lucrarea lui Mihai a primit nota 10
>>>print(F'Lucrarea lui {nume} a primit nota {nota})
Lucrarea lui Mihai a primit nota 10
#utilizarea și evaluarea metodei upper()
print(f'Lucrarea lui {nume.upper()} a primit nota {nota}')
Lucrarea lui MIHAI a primit nota 10

Dacă se dorește precizarea unui format la imprimarea variabilei se poate utiliza următoarea
formă:
>>>n = 6.837
>>>f'Valoarea lui n este {n:.2f}'
'Valoarea lui n este 6.84'

Colonul (:) după numele variabilei precizează că tot ce urmează aparține formatului specificat.
Se poate insera între acolade și virgula, pentru a preciza separatorul cifrelor părții întregi:
>>>n = 1234567890.12
>>>f"Valoarea lui n este {n:,.2f}"
'Valoarea lui n este 1,234,567,890.12'

De asemenea, se poate afișa procentajul fără, sau cu una sau mai multe zecimale:
>>>p = 0.155
>>>f'Taxa este de {p:.2%}'
'Taxa este de 15.50%'

7.7 Metodele uzuale ale șirurilor


Obiectul șir dispune de numeroase metode. Printre cele mai utilizate este metoda format()
prezentată mai sus, dar mai există multe altele: lower(), upper(), join(), split(),
find(), replace() etc.

Exemplul 7.23 #metode aplicate șirurilor


>>> "Automat Programabil - PLC".lower()
'automat programabil - plc'
>>> "Automat Programabil - PLC".upper()
'AUTOMAT PROGRAMABIL - PLC'
>>> "Automat Programabil - PLC".split()
['Automat', 'Programabil', '-', 'PLC']
141 Șiruri

>>>' '.join(['Automat', 'Programabil', '-', 'PLC'])


'Automat Programabil - PLC'
>>> 'Automat Programabil - PLC'.find('Pr')
8
>>> 'Automat Programabil - PLC'.replace('Automat','Sistem')
'Sistem Programabil - PLC'

7.8 Întrebări, exerciții și probleme


7.8.1 Ce metodă poate fi utilizată pentru a întoarce un șir în litere mari?
a. uppercase() b. toUpper() c. upper() d. uperCase()

R.7.8.1 c ('a '.upper -> A)


7.8.2 Cum se pot crea copii multiple concatenate ale unui șir ?
R7.8.2 Se utilizează operatorul "*".
7.8.3 Cum se poate afișa la imprimare un backslash conținut într-un șir?
R7.8.3 Dublând backslash-ul (\\), sau definind un șir brut/raw utilizând prefixul r sau R.
7.8.4 Creați un caracter, apoi obțineți reprezentarea sa ca întreg.
R7.8.4
>>> x = "M"
>>> ord(x)
77

7.8.5 Creați un șir care să conțină un singur apostrof (').


R7.8.5
>>>x = '1234\'5678'
>>>print(x)
1234'5678

7.8.6 Creați un șir format din 25 de liniuțe "-" utlizând operatorul de multiplicare.
R7.8.6
>>> x = '-'
>>> y = 25*x
>>> print(y)
---------------------

7.8.7 Utilizând metoda join(), formați un singur șir prin concatenarea elementelor listei C4
= ['Uzina ', 'Dacia ', 'din ', 'Pitești'].

R7.8.7
>>>C4 = ['Uzina ', 'Dacia ', 'din ', 'Pitești']
>>>s = "".join(C4)
>>>print(s)
Uzina Dacia din Pitești

7.8.8 Să se obțină o listă de cuvinte prin separarea șirului s = "maglev inseamna magnetic
levitation".

R7.8.8
>>>s = "maglev inseamna magnetic levitation"
>>>cuvinte = s.split()
142 Șiruri

>>>print(cuvinte)
['maglev', 'inseamna', 'magnetic', 'levitation']

7.8.9 Metoda strip() a obiectelor șir se utilizează pentru eliminarea unor caractere precizate,
din fața și de la sfârșitul șirurilor, sintaxa fiind următoarea:
sir.strip([caractere])

Să se exemplifice modul de utilizare pentru șirurile: s1 = “%%%sir_test1%%%” și s2 = “


sir_test2 ” (3 spații înainte și după).

R7.8.9
>>>s1 = '%%%sir_test1%%%
>>>print(s1.strip('%%%'))
sir_test1
>>>s2 = ' sir_test2 '
>>>print(s2.strip(' '))
sir_test2

7.8.10 Să se exemplifice utilizarea metodelelor removeprefix() și removesuffix() pentru


eliminarea unor caractere din fața și de la sfârșitul unui șir.
R7.8.10
>>>s1 = '%%%sir_test1%%%'
>>>print(s1.removeprefix('%%%'))
sir_test1%%%
>>>print(s1.removesuffix('%%%'))
%%%sir_test1
7.8.11 Care este ieșirea pentru secvența:
a = [‘a’, ‘bra’, ‘ca’, ‘da’]
’bra’.join(a)
R7.8.11 Metoda join() creează și returnează un șir nou prin concatenarea tuturor elementelor
dintr-o listă (sau un obiect asemănător), separate prin virgule sau un șir separator specificat.
Dacă lista are un singur articol, atunci acel articol va fi returnat fără a utiliza separatorul. În
cazul acestui exemplu, separatorul specificat este șirul ‘bra’.
a = ['a', 'bra', 'ca', 'da']
r = 'bra-'.join(a)
print(r)

Output
abra-brabra-cabra-da
Pentru a vedea mai clar efectul metodei join(), se poate rula secvența:
b = ['a', 'bra', 'ca', 'da', 'bra']
s = '#'.join(b)
print(s)

Output
a#bra#ca#da#bra
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 8

Tipul de date set


8.1 Definiția și crearea seturilor
Definiție. Un set (mulțime) este o colecție neordonată de elemente imutabile. Fiecare element
din set este unic (nu poate avea dubluri). Un set poate avea orice număr de elemente de tipuri
diferite (intreg, float, tuplu, șir, etc.). Un set nu poate avea totuși elemente mutabile ca liste,
mulțimi, sau dicționare.
Creare. Un set se poate crea prin plasarea tuturor elementele în interiorul acoladelor {},
separate prin virgulă, sau prin utilizarea funcției predefinite set().
Observație. Deși elementele unui set sunt imutabile (nu pot fi schimbate), setul însuși este
mutabil, adică permite adăugarea și ștergerea elementelor constituente.
Cu seturile (mulțimile) pot fi efectuate operațiunile matematice logice specifice mulțimilor:
uniunea, intersecția, diferența, etc.
Exemplul 8.1 #crearea seturilor
# set continand intregi
xset = {1, 2, 3}
print(xset)
# set continand diferite tipuri de date (aici float, sir, tuplu)
xset = {1.0, "program", (1, 2, 3)}
print(xset)

Output
{1, 2, 3}
{1.0, 'program', (1, 2, 3)}

Exemplul 8.2 #proprietăți ale seturilor


# setul nu poate contine duplicate
xset = {1, 2, 3, 4, 3, 2}
print(xset)
# transformarea unei liste în set
xset = set([1, 2, 3, 2])
print(xset)
# setul nu poate avea elemente mutabile
# [3, 4] este o lista mutabila care va produce o eroare
xset = {1, 2, [3, 4]}

Output
{1, 2, 3, 4}
{1, 2, 3}
Traceback (most recent call last):
. . . . . . . .
xset = {1, 2, [3, 4]}
TypeError: unhashable type: 'list'

Crearea unui set vid se poate face printr-un artificiu, după cum se descrie în continuare.
144 Seturi

Prin utilizarea acoladelor {} se va produce un dicționar vid. Pentru a face un set fără membri
se utilizează funcția set() fără argumente.
Exemplul 8.3 # Crearea unui set vid
# initializarea lui a cu {} (dicționar vid)
a = {}
# verificarea tipului de data a variabilei a
print(type(a))
# initializarea lui a cu set()
a = set()
# verificarea tipulu de data a lui a
print(type(a))
print(a)

Output
<class 'dict'>
<class 'set'>
set()

8.2 Modificarea unui set


Setul este mutabil. Totuși, fiind neordonat, nu este indexabil; indexarea nu are sens. În
consecință, nu se poate accesa sau schimba un element al unui set prin indexare sau slicing.
Indexarea și slicingul nu sunt acceptate de seturi.
Se pot adăuga însă elemente, astfel: câte un singur element folosind metoda add() și mai multe
elemente utilizând metoda update(). Metoda update() poate avea ca argumente tupluri,
liste, șiruri, sau alte seturi. În toate cazurile, duplicatele sunt interzise.
Exemplul 8.4 #adăugare elemente la seturi
# initializare xset
xset = {1, 3}
print(xset)
# adaugare un element
xset.add(2)
print(xset)
# adaugare multiple elemente
xset.update([2, 3, 4])
print(xset)
# adaugarea unei liste si a unui set
xset.update([4, 5], {1, 6, 8})
print(xset)
# indexarea produce eroare
xset[0]

Output
{1, 3}
{1, 2, 3}
{1, 2, 3, 4}
{1, 2, 3, 4, 5, 6, 8}
TypeError: 'set' object does not support indexing
145 Seturi

8.3 Eliminarea de elemente dintr-un set


Un element poate fi înlăturat dintr-un set utilizând metodele discard() și remove().
Singura diferență între cele două este că funcția discard() lasă setul neschimbat dacă
elementul nu este prezent în set, fără a semnaliza absența, iar funcția remove() va genera o
eroare (dacă elementul lipsește din set).
Exemplul 8.5 # Ilustrare diferență dintre discard() și remove()
# inițializare xset
xset = {1, 3, 4, 5, 6}
print(xset)
# funcția discard()
xset.discard(4)
print(xset) # rezultat: {1, 3, 5, 6}
#funcția remove()
xset.remove(6)
print(xset) # rezultat: {1, 3, 5}
# funcția discard(), elementul este absent in xset
xset.discard(2)
print(xset) # rezultat: {1, 3, 5}
# eliminare, elementul este absent in xset
xset.remove(2) # rezultă eroare: KeyError

Output
{1, 3, 4, 5, 6}
{1, 3, 5, 6}
{1, 3, 5}
{1, 3, 5}
Traceback (most recent call last):
File "<string>", line 28, in <module>
KeyError: 2
Asemănător, se poate elimina și întoarce un element din set utilizând metoda pop().
Deoarece setul nu este ordonat, elementul eliminat va fi complet arbitrar, nu poate fi precizat.
De asemenea, se pot elimina toate elementele dintr-un set utilizând funcția clear().
Exemplul 8.6 #utilizare funcții pop() și clear()
# initializare xset, rezulta un set cu elemente unice
xset = set("Buna_ziua!")
print(xset)
#elementele setului sunt ordonate aleator
#ca urmare, extragerea cu functia pop() va fi corespunzatoare
#ordonarii aleatoare
print(xset.pop())
print(xset)
print(xset.pop()) # se extrage alt element
print(xset) # afisare multimea ramasa
print(xset.pop()) # se extrage alt element
print(xset) # afisare multimea ramasa
# stergere xset cu metoda clear()
xset.clear()
print(xset) #set vid

Output
{'a', 'i', 'n', '!', '_', 'u', 'B', 'z'}
146 Seturi

a
{'i', 'n', '!', '_', 'u', 'B', 'z'}
i
{'n', '!', '_', 'u', 'B', 'z'}
n
{'!', '_', 'u', 'B', 'z'}

set()

8.4 Operații logice cu seturi (mulțimi)


Seturile (mulțimile) pot fi utilizate pentru efectuarea unor operații matematice ca uniunea,
intersecția, diferența, etc. În acest scop se pot folosi operatori sau metode.
Fie mulțimile A și B pentru care se vor face operații în continuare.
>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}

Uniunea seturilor

Definiție. Uniunea dintre A și B este mulțimea de elemente obținută din cele două mulțimi
(fig. 4.1).

Fig. 4.1 Uniunea a două seturi


Uniunea se poate face folosind operatorul | . Același rezultat se obține dacă se utilizează
metoda union().
Exemplul 8.7 #reuniunea a două seturi utilizând operatorul “|” și metoda union()
# initializare A si B
>>>A = {1, 2, 3, 4, 5}
>>>B = {4, 5, 6, 7, 8}
# utilizarea operatorului |
>>>print(A | B)
{1, 2, 3, 4, 5, 6, 7, 8}
# utilizarea metodei union()
>>> A.union(B)
{1, 2, 3, 4, 5, 6, 7, 8}
# comutativitatea reuniunii
>>> B.union(A)
{1, 2, 3, 4, 5, 6, 7, 8}

Intersecția seturilor
Definiție. Intersecția dintre A și B este mulțimea comună (aparținând ambelor) mulțimilor A
și B.
147 Seturi

Intersecția poate fi realizată prin operatorul & sau metoda intersection().


Exemplul 8.8 # Intersecția seturilor cu & și metoda intersection().
# initializare A si B
>>>A = {1, 2, 3, 4, 5}
>>>B = {4, 5, 6, 7, 8}
# utilizarea operatorului &
>>>print(A & B)
{4, 5}
# utilizarea metodei intersection()cu A
>>> A.intersection(B)
{4, 5}
# comutativitatea intersecției
>>> B.intersection(A)
{4, 5}

Diferența mulțimilor
Definiție. Diferența dintre mulțimea A și mulțimea B (notat A – B) este mulțimea elementelor
care aparțin numai lui A și nu și lui B. Asemănător, B – A este mulțimea de elemente care sunt
în B, dar nu în A.
Diferența dintre două seturi se face utilizând operatorul ( – ), sau metoda difference().
Exemplul 8.9 # Diferența dintre doua seturi utilizând metoda difference().
>>>A = {1, 2, 3, 4, 5}
>>>B = {4, 5, 6, 7, 8}
>>>print(A - B)
{1, 2, 3}
# utilizarea funcției difference(), se calculeaza A - B
>>> A.difference(B)
{1, 2, 3}
#se calculeaza B - A
>>> B - A
{8, 6, 7}
# utilizarea funcției difference(), se calculeaza A - B
>>> B.difference(A)
{8, 6, 7}

Diferența simetrică
Definiție. Diferența simetrică dintre A și B este mulțimea de elemente din A și B, dar nu în
ambele (din reuniune se exclude intersecția).
Pentru diferența simetrică se utilizează operatorul ^ , sau metoda symmetric_difference().
Exemplul 8.10 # Diferența simetrică a două seturi utilizând operatorul ^
>>>A = {1, 2, 3, 4, 5}
>>>B = {4, 5, 6, 7, 8}
>>>print(A ^ B)
{1, 2, 3, 6, 7, 8}
# Diferența simetrică a doua seturi, metoda symmetric_difference()
>>> A.symmetric_difference(B)
{1, 2, 3, 6, 7, 8}
# operația este comutativa
>>> B.symmetric_difference(A)
{1, 2, 3, 6, 7, 8}
148 Seturi

8.5 Alte metode pentru operații cu seturi


În tabelul următor sunt prezentate metodele obiectului set.

Tabelul 8.1 Metodele obiectului set

add() - Adună un element la set


clear() - Elimină toate elementele din set
copy() - Întoarce o copie a setului
difference() - Diferenta a două seturi
difference_update() - Elimină toate elementele unui set conținut într-un set
discard() - Elimină un element dintr-un set (nu produce eroare dacă elementul
nu există în set)
intersection() - Intersecția a două seturi (întoarce un nou set)
intersection_update() - Actualizează setul cu intersecția dintre el și un
alt set (elimină elementele care nu fac parte din intersecție)
isdisjoint() - Întoarce True dacă două seturi au intersecția nulă
issubset() - Întoarce True dacă alt set conține acest set
issuperset() - Întoarce True dacă acest set conține un alt set
pop() - Elimină și întoarce arbitrar un element din set. Produce KeyError
dacă setul este vid
remove() - Elimină un element din set. Dacă elementul nu este um membru al
setului, se produce KeyError
symmetric_difference() - Întoarce ca set nou diferența simetrică dintre două
seturi (întoarce un set nou cu elementele necomune ambelor seturi)
symmetric_difference_update() - Actualizează un set cu diferența simetrică
dintre acel set și un altul (elimină elementele prezente în ambele seturi și
inserează elementele care diferă)
union() - Întoarce ca nou set reuniunea seturilor
update() - Actualizează un set cu reuniunea dintre acel set și altele

8.6 Alte operații cu seturi


Testarea apartenenței la set
Prin utilizarea cuvântului cheie in (sau not in) se poate testa dacă există un anumit element
în set.
Exemplul 8.11 # testarea apartenenței sau neapartenenței la set
# initializarea setului
xset = set("programare")
# verificare daca 'a' exista in set
print('a' in xset)
# verificare daca 'p' există in set
print('p' not in xset)

Output
True
False

Parcurgerea unui set


Se pot itera elementele intr-un set cu ajutorul buclei for.
Eemplul 8.12 #iterarea unui set utilizând for
149 Seturi

>>> for car in set ("programare"):

... print(car, " ", end = '')

Output
o g p e m r a

8.7 Funcții built-in pentru tipul de date set


Funcțiile built-in : all(), any(), enumerate(), len(), max(), min(), sorted(), sum() etc.
pot fi utilizate împreună cu seturile pentru diferite scopuri.

Tabelul 8.2 Funcții built-in pentru lucrul cu seturi

all() – Întoarce True dacă toate elementele setului sunt adevărate (sau dacă
setul este gol).
any() - Întoarce True dacă oricare element al setului este adevărat. Dacă
setul este gol, întoarce False.
enumerate() – Întoarce un obiect enumerare care conține indexul și valoarea
pentru toate elementele setului, ca perechi.
len() – Întoarce lungimea setului (numărul de elemente).
max() - Întoarce cel mai mare element din set.
min() - Întoarce cel mai mic element din set.
sorted() - Întoarce o nouă listă sortată obținută din elementele setului (nu
sortează setul).
sum() - Întoarce suma tuturor elementelor din set.

8.8 Înghețarea seturilor


Setul înghețat (frozen) este o nouă clasă care are caracteristicile unui set, dar elementele sale
nu mai pot fi schimbate odată ce i s-au atribuit.
Observație. Prin analogie cu tuplele care pot fi privite ca liste imutabile, seturile înghețate pot
fi considerate seturi imutabile.
Seturile, fiind mutabile, sunt “non-hashabile”, astfel încât nu pot fi utilizate drept chei de
dicționar. Pe de altă parte, mulțimile înghețate (frozen-set-urile), sunt “hashabile” și pot fi
utilizate drept chei în dicționare (a se vedea noțiunea de tabel de hashing).
Seturile înghețate pot fi create utilizând funcția frozenset().
Acest tip de date permit utilizarea de metode ca: union(), intersection(), difference(),
isdisjoint(), issubset(), issuperset(), symmetric_difference() și copy(). Fiind
imutabile, nu sunt metode care să adauge sau să elimine elemente.
Exemplul 8.13 # date de tip frozenset
A = frozenset([1, 2, 3, 4])
B = frozenset([3, 4, 5, 6])
>>> A.isdisjoint(B)
False
>>> A.difference(B)
frozenset({1, 2})
>>> A | B
150 Seturi

frozenset({1, 2, 3, 4, 5, 6})
>>> A.add(3)
...
AttributeError: 'frozenset' object has no attribute 'add'

8.9 Întrebări, exerciții și probleme


8.9.1 Care va fi ieșirea secvenței următoare:
s = "Goooool!"
v = {i for i in s}
print(len(v))

R8.9.1 Răspuns: 4 (deoarece v este o mulțime/set în care elementele sunt unice)


8.9.2 Care va fi ieșirea secvenței următoare:
x = [1,3,8,6,8,9]
y = set([1,4,7,9,7,4])
print (len(x) + len(y))

R8.9.2 Răspuns: 10
8.9.3 Care opțiune este corectă pentru a crea o mulțime (set) vidă ?
a. set() b. () c. []
R8.9.3 Răspuns: a
8.9.4 Ce este o copie “shalow” a unui set? Exemplificați.
R8.9.4 Ca și în cazul altor obiecte, o copie “shalow” a unui set este o copie distinctă, bit cu
bit, a setului original.
xset = set(["verde","galben"])
yset = xset
xset_copie = xset.copy()
print(xset, " xset, inainte de stergere")
print(yset, " yset, inainte de stergere")
print(xset_copie, " xset_copie, inainte de stergere")
yset.clear()
print(xset, " xset, dupa stergere")
print(yset, " yset, dupa stergere")
print(xset_copie, " xset_copie, dupa stergere")

Output
{'galben', 'verde'} xset, inainte de stergere
{'galben', 'verde'} yset, inainte de stergere
{'galben', 'verde'} xset_copie, inainte de stergere
set() xset, dupa stergere
set() yset, dupa stergere
{'galben', 'verde'} xset_copie, dupa stergere

8.9.5 Să se scrie o secvență de program pentru a verifica dacă un set este un subset al altui
set. Se vor utiliza operatorul “<=” și metoda issubset().
R8.9.5
xset = set(["verde","galben"])
yset = set(["verde","rosu"])
zset = set(["verde"])
151 Seturi

print("x: ", xset); print("y: ", yset); print("z: ", zset)


print("Este x un subset al lui y ? :", (xset <= yset),
(xset.issubset(yset)))
print("Este y un subset al lui x ? :", (yset <= xset),
(yset.issubset(xset)))
print("Este x un subset al lui z ? :", (xset <= zset),
(xset.issubset(zset)))
print("Este z un subset al lui x ? :", (zset <= xset),
(zset.issubset(xset)))

Output
x: {'verde', 'galben'}
y: {'verde', 'rosu'}
z: {'verde'}
Este x un subset al lui y ? : False False
Este y un subset al lui x ? : False False
Este x un subset al lui z ? : False False
Este z un subset al lui x ? : True True

8.9.6 Să se exemplifice operațiile următoare aplicate unor seturi: maximul și minimul în set,
lungimea unui set, prezența sau absența unui element în set.
R8.9.6
xset = set([7,16, 2, 33, 21, 5])
print("Setul creat este: ", xset)
print("Elementul maxim in set este: ", max(xset))
print("Elementul minim in set este: ", min(xset))
print("Lungimea setului este: ", len(xset))
print("Test daca 9 este in set: ", 9 in xset)
print("Test daca 9 este in set: ", 9 in xset)
print("Test daca 9 nu este in set: ", 9 not in xset)
print("Test daca 33 este in set: ", 33 in xset)
print("Test daca 33 nu este in set: ", 33 not in xset)
Output
Setul creat este: {33, 2, 5, 7, 16, 21}
Elementul maxim in set este: 33
Elementul minim in set este: 2
Lungimea setului este: 6
Test daca 9 este in set: False
Test daca 9 este in set: False
Test daca 9 nu este in set: True
Test daca 33 este in set: True
Test daca 33 nu este in set: False
8.9.7 Se poate crea un set de seturi? Cum se poate rezolva?

R8.9.7
Dacă se încearcă crearea unui set de seturi se obține eroare:
>>>{{4,5}, {8,9}}
. . . . . . . .
TypeError: unhashable type: 'set'
>>>set([{4,5}, {8,9}])
. . . . . . . .
TypeError: unhashable type: 'set'
O posibilă rezolvare este utilizarea frozenseturilor:
>>>{frozenset({4, 5}), frozenset({8, 9})}
{frozenset({4, 5}), frozenset({8, 9})}
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 9

Dicționare
9.1 Definiția și crearea dicționarelor
Definiție. Dicționarul este o colecție de date compusă din elemente alcătuite din perechi
formate dintr-o cheie și o valoare corespondentă, scrise sub forma (key: value).
Crearea unui dicționar se face simplu, prin plasarea elementelor între acolade.
În timp ce valoarea poate fi de orice tip de dată, cheia trebuie să fie de un tip imutabil (șir,
număr, tuplu cu elemente imutabile, sau frozenset).
Exemplul 9.1 # dicționarul vid
xdict = {}
print(xdict)
xdict = {1: 'rosu', 2: 'negru'} #dictionarul cu cheie intreaga
xdict = {'nume': 'Alex', 1: [12, 4, 3]} #dictionarul cu chei diverse

# utilizarea funcției built-in dict() de creare dicționar

#varianta 1: argumentele functiei dict() sunt perechi cheie-valoare


xdict = dict({1:'rosu', 2:'negru'})

#varianta 2: argumentul functiei dict() este o lista de perechi


xdict = dict([(1,'tenis'), (2,'baschet')])

Output
{}
{1: 'rosu', 2: 'negru'}
{'nume': 'Alex', 1: [12, 4, 3]}
{1: 'rosu', 2: 'negru'}
{1: 'tenis', 2: 'baschet'}

9.2 Accesarea elementelor unui dicționar


Accesul la elementele unui dicționar nu se face prin metoda indexării, fiind diferit de aceasta.
În locul indexului sunt folosite cheile. Cheile pot fi utilizate fie în interiorul unor paranteze
pătrate [], fie prin intermediul metodei get().
Dacă se utilizează paranteze pătrate [], va apărea o eroare KeyError în cazul în care cheia nu
este găsită în dicționar. Metoda get() întoarce None dacă cheia nu este găsită în dicționar.
Exemplul 9.2 #compararea metodelor de regăsire get() și []
xdict = {'nume': 'Mihai', 'varsta': 25}
print(xdict['nume'])
print(xdict.get('varsta'))
#incercare de a accesa o cheie inexistenta
print(xdict.get('datepers')) # rezultat dat de metoda get(): None

Output
Mihai
25csa 9
None
153 Dicționare

9.3 Modificarea și adăugarea elementelor unui dicționar


Dictionarele sunt mutabile. Se pot adăuga noi elemente sau se pot modifica valorile unor
elemente existente în dicționar utilizând operatorul de atribuire.
Dacă în momentul accesului la dicționar cheia este deja prezentă, atunci valoarea asociată se
actualizează. Dacă cheia nu există, atunci se va adăuga o nouă pereche (key: value) în
dicționar.

Exemplul 9.3 # modificare și adaugare de elemente în dicționar

xdict = {'nume': 'Mihai', 'varsta': 25}


xdict['varsta'] = 42 # valoare actualizata
print(xdict)
xdict['telefon'] = '0740079331' # adaugare element
print(xdict)

Output
{'nume': 'Mihai', 'varsta': 42}
{'nume': 'Mihai', 'varsta': 42, 'telefon': '0740879331'}

9.4 Eliminarea elementelor din dicționar


Un anumit element din dicționar se poate elimina utilizând metoda pop(). Această metodă
elimină elementul având cheia specificată și întoarce valoarea sa.
De asemenea popitem() poate fi utilizată pentru eliminarea și întoarcerea unei perechi
arbitrare (key, value) din dicționar. Toate elementele pot fi eliminate simultan utilizând
metoda clear().
Asemănător, se poate utiliza cuvântul cheie del pentru a elimina elemente individuale sau
dicționarul în totalitate.
Exemplul 9.4 # eliminarea unor elemente din dictionar
patrate = {1: 1, 2: 4, 3: 9, 4: 16, 5: 25} #creare dictionar
print(patrate.pop(4)) #eliminare element precizat și afisarea sa
print(patrate) #afisare dictionar ramas
print(patrate.popitem()) #eliminare element oarecare,cu afisare
print(patrate) #afisare dictionar ramas
patrate.clear() #eliminare toate elementele din dictionar
print(patrate) #afisare dictionar ramas
del patrate #stergerea dictionarului
print(patrate) #afisare eroare (dictionarul nu mai exista)

Output
16
{1: 1, 2: 4, 3: 9, 5: 25}
(5, 25)
{1: 1, 2: 4, 3: 9}
{}
Traceback (most recent call last):
. . . . . . . . . . .
print(patrate) #afisare eroare (dictionarul nu mai exista)
NameError: name 'patrate' is not defined
154 Dicționare

9.5 Metodele obiectului dicționar


Metodele obiectului dicționar sunt următoarele:

Tabelul 9.1

clear() – Eliminarea tuturor elementelor din dicționar.


copy() – Întoarce o copie a dicționarului
fromkeys(seq[, v]) – Întoarce un nou dicționar cu cheile din seq și valori
egale cu v (implicit None).
get(key[,d]) – Întoarce valoarea cheii. Dacă cheia nu există, se întoarce d
(implicit None).
items() – Întoarce un nou obiect al unui element din dicționar în
format (key, value) .
keys() – Întoarce un nou obiect al cheilor din dicționar.
pop(key[,d]) – Elimină elementul cu cheia key și întoarce valoarea sa, sau
întoarce d dacă key nu este găsit. Dacă d nu este prevăzut și cheia key nu
este găsită, se generează KeyError.
popitem() – Elimină și întoarce un element arbitrar (key, value).
Generează KeyError dacă dicționarul este gol.
setdefault(key[,d]) – Întoarce valoarea corespunzătoare dacă cheia este în
dicționar. Dacă nu, inserează cheia key cu valoarea d și întoarce d
(implicit None).
update([arg]) – Actualizează dicționarul cu perechea key/value din arg,
suprascriind cheile existente keys.
values() – Întoarce un nou obiect al valorilor din dicționar.

Exemplul 9.5 # ilustrare metodele fromkeys(), items() și sorted() pentru dicționar

note = {}.fromkeys(['Fizica', 'Chimie', 'Informatica'], 0)


#toate notele au fost inițializate cu 0
print(note)
note['Chimie'] = 9
note['Informatica'] = 8
note['Fizica'] = 10
for k in note.items():
print(k)
# lista sortată a cheilor din dicționar
print(list(sorted(note.keys())))

Output
{'Fizica': 0, 'Chimie': 0, 'Informatica': 0}
('Fizica', 10)
('Chimie', 9)
('Informatica', 8)
['Chimie', 'Fizica', 'Informatica']

9.6 Comprehensiunea dicționarelor


Comprehensiunea dicționarelor este o cale elegantă și concisă de a crea un dicționar nou dintr-
un iterabil în Python.
155 Dicționare

Comprehensiunea dicționarelor constă dintr-o pereche (key: value) urmată de o instrucțiune


for între acolade {}.

Exemplul 9.6 # Comprehensiunea dicționarelor


patrate = {x: x*x for x in range(6)}
print(patrate)

Output
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Secvența de instrucțiuni de mai sus este echivalentă cu:
patrate = {}
for x in range(6):
patrate[x] = x*x
print(patrate)

O comprehensiune de dicționar poate conține opțional mai multe instrucțiuni for sau if .
O instrucțiune if poate filtra suplimentar elementele noului dicționar creat.
Observație. Construcția conținând if se numește comprehensiune cu gardă.
Exemplul 9.7 # Comprehensiune de dicționar utilizând instrucțiunea if (cu gardă)
patrate_impare = {x: x*x for x in range(11) if x % 2 == 1}
print(patrate_impare)

Output
{1: 1, 3: 9, 5: 25, 7: 49, 9: 81}

9.7 Alte operații cu dicționarele


9.7.1 Testul de apartenență la dicționar

Se poate testa dacă o cheie este utilizată într-un dicționar folosind in. Acest lucru este valabil
doar pentru chei, nu și pentru valori.
Exemplul 9.8 # Test de apartenență sau neapartenență la dicționar pentru chei
patrate = {1: 1, 3: 9, 5: 25, 7: 49, 9: 81}
print(1 in patrate) #True
print(2 not in patrate) #True
print(49 in patrate) #test pentru valori => False

Output
True
True
False

9.7.2 Parcurgerea unui dicționar

Un dicționar se poate itera după fiecare cheie utilizând o buclă for .


Exemplul 9.9 # iterarea unui dicționar
patrate = {1: 1, 3: 9, 5: 25, 7: 49, 9: 81}
for i in patrate:
print(patrate[i])
156 Dicționare

Output
1
9
25
49
81

9.7.3 Funcții built-in pentru dicționare

Pentru dicționare se folosesc frecvent funcții built-in: all(), any(), len(), cmp(),
sorted(), etc.

Tabelul 9.2

all() - Întoarce True dacă toate cheile din dicționar sunt True (sau dacă
dicționarul este gol).
any() - Întoarce True dacă o cheie din dictionar este true. Dacă
dicționarul este gol se întoarce False.
len() - Întoarce lungimea dicționarului (numărul de elemente).
cmp() - Compară elementele a două dicționare (indisponibil în Python 3)
sorted() - Întoarce o listă sortată a cheilor din dicționar.

Exemplul 9.10 # utilizare funcții built-in pentru dicționare


patrate = {0: 0, 1: 1, 3: 9, 5: 25, 7: 49, 9: 81}
print(all(patrate)) #nu toate cheile sunt diferite de zero =>False
print(any(patrate))
print(len(patrate))
print(sorted(patrate))

Output
False
True
6
[0, 1, 3, 5, 7, 9]

9.8 Întrebări, exerciții și probleme


9.8.1 Să se arate o metodă de a fuziona 2 dicționare.
R9.8.1 Se execută următoarea secvență de cod:
x = {'a':1, 'b':2}
y = {'c':3, 'd':4}
z = {**x, **y}
print(z)

Output
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
9.8.2 În legătură cu cheile dicționarului, care afirmație nu e adevărată:
a. nu este permisă mai mult de o aceeași cheie (chei multiple)
b. datele (valorile) sunt imutabile
c. cheile trebuie să fie întregi.
R9.8.2 răspuns: c
157 Dicționare

9.8.3 Care din formele de mai jos nu este o declarație de dicționar ?


a. x = {1:'A', 2:'B'} b. x = dict([[1, "A"],[2, "B"]]) c. x = {1,"A", 2"B"}

R9.8.3 c
9.8.4 Să se simuleze procesul vânzării cărților într-o librărie, utilizând un dicționar.
R9.8.4 Se creează un inventar al cărților din magazin, sub forma unui dicționar. Cheia
dicționarului va fi câmpul nume autor, iar valoarea va fi o listă conținând numărul de exemplare
disponibile și prețul cărții.
carti = {"Eminescu Mihai": [5, 70],
"Creanga Ion": [5, 50],
"Minulescu Ion": [6, 45]}
for cheie, valoare in carti.items():
print(cheie, '-', valoare[0], '-', valoare[1])
cost = 0
while True:
carte = input("Autor carte ? (s = stop)=")
if carte == 's':
break
cantit = int(input("nr. cerut?="))
if cantit > carti[carte][1]:
print("Neacceptat - depasire stoc!")
continue
cost += carti[carte][0]*cantit
carti[carte][0] -= cantit

print("Pret:", cost)
for cheie, valoare in carti.items():
print(cheie, 'stoc:', valoare[0], 'pret:', valoare[1])

Output
Eminescu Mihai - 5 - 70
Creanga Ion - 5 - 50
Minulescu Ion - 6 - 45
Autor carte ? (s = stop)=Eminescu Mihai
nr. cerut?=3
Autor carte ? (s = stop)=s
Pret: 15
Eminescu Mihai stoc: 2 pret: 70
Creanga Ion stoc: 5 pret: 50
Minulescu Ion stoc: 6 pret: 45

9.8.5 Fie următoarele cazuri de creare a unui dicționar:


a. dict({'j':111, 'k':122}, m =133)
b. dict([('d',44),('e',55), ('f',66)])
c. dict(a=11, b=22, c=33)
d. dict([('g',77)], h=88, i=99)
Care afirmație este adevărată? :
1. Corect a 2. Corect b 3. Corect c 4. Corect d 5. Corect toate
R9.8.5 raspuns bun = 5
9.8.6 Ce fac metodele keys(), values() și items() aparținând unui obiect dicționar?
Exemplificați utilizarea lor.
158 Dicționare

R9.8.6 keys() întoarce cheile, values() întoarce valorile și items() întoarce termenii
dicționarului (perechile cheie-valoare).
xdict = {'i':5, 'j':8, 'k':34}
print(xdict.keys())
dict_keys(['i', 'j', 'k'])
print(xdict.values())
dict_values([5, 8, 34])
print(xdict.items())
dict_items([('i', 5), ('j', 8), ('k', 34)])
9.8.7 Să se scrie o secvență de iterare pentru dicționarul:
xdict = {'x':10, 'y':20, 'z':30}

R9.8.7
Metoda 1
>>>for cheie, valoare in xdict.items():
... print(cheie, valoare)
x 10
y 20
z 30

Metoda 2
>>>for cheie in xdict:
...print(cheie, xdict[cheie])
x 10
y 20
z 30

Metoda 3
>>>print([cheie for cheie in xdict.items()])
[('x', 10), ('y', 20), ('z', 30)]

9.8.8 Să se obțină un dicționar din următoarele două liste, prima corespunzând cheilor, iar a
doua corespunzând valorilor:
lista1 = ['x', 'y', 'z', 'w']
lista2 = [6, 12, 9, 15]

R9.8.8
Metoda 1
lista1 = ['x', 'y', 'z', 'w']
lista2 = [6, 12, 9, 15]
dict_12 = {}
for k in range(len(lista1)):
dict_12[lista1[k]] = lista2[k]
print(dict_12)

Output
{'x': 6, 'y': 12, 'z': 9, 'w': 15}
Metoda 2
# se utilizeaza functia zip() care produce un obiect iterator tuplu prin
#imperecherea elementelor din alti doi iteratori
lista1 = ['x', 'y', 'z', 'w']
lista2 = [6, 12, 9, 15]
dict_12 = dict(zip(lista1, lista2))
159 Dicționare

print(dict_12)

Output
{'x': 6, 'y': 12, 'z': 9, 'w': 15}

9.8.9 Să se calculeze suma și media aritmetică a tuturor valorilor din următorul dicționar:
Xdict = {'x1': 6, 'x2': 12, 'x3': 9, 'x4': 15}

R9.8.9
xdict = {'x1': 6, 'x2': 12, 'x3': 9, 'x4': 15}
suma = sum(xdict.values())
print("Suma tuturor valorilor din dictionar =", suma)
n = len(xdict)
print("Numarul perechilor din dictionar n=", n)
print("Media aritmetica = suma/n = ", suma/n)

Output
Suma tuturor valorilor din dictionar = 42
Numarul perechilor din dictionar n= 4
Media aritmetica = suma/n = 10.5

9.8.10 Să se combine două dicționare într-unul singur, adunând valorile pentru cheile comune.
Cele două dicționare sunt:
xdict = {"a":23, "b":41, "c":22}
ydict = {"b":99, "c":38, "d":7, "e":59}

R9.8.10 Se utilizează funcția Counter(), disponibilă în modulul collections:


import collections
xdict = {"a":23, "b":41, "c":22}
ydict = {"b":99, "c":38, "d":7, "e":59}
zdict = collections.Counter(xdict) + collections.Counter(ydict)
print(zdict)

Output
Counter({'b': 140, 'c': 60, 'e': 59, 'a': 23, 'd': 7})

9.8.11 Care va fi ieșirea pentru următorul program:


x ={ "nume1": 13, "nume2": -20, 25: 43, "tw": -7}
print(len(x))

R9.8.11 4
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 10

Operații I/O. Fișiere


10.1 Noțiunea de fișier
Definiție. Fișierele sunt nume ale unor colecții de date stocate pe hard-disc sau alte dispozitive
cu proprietăți asemănătoare. Datele de pe disc sunt permanente (non-volatile), spre deosebire
de datele memorate în RAM (Random Access Memory), de unde dispar odată cu decuplarea
tensiunii de alimentare.
Datele memorate în fișiere pot fi ordonate sau neordonate, pot fi în format text (alfanumeric),
sau în format binar.
Pentru a citi/scrie din/în un fișier, este necesară “deschiderea” acestuia (operația open). După
încheierea lucrului cu datele din fișier, este necesară “închiderea” acestuia (operația close),
pentru a elibera resursele care au fost conectate la fișier.
În concluzie, succesiunea operațiilor cu un fișier (în majoritatea limbajelor de programare) este
următoarea:
1. Deschiderea fișierului (open)
2. Prelucrarea datelor prin citire și/sau scriere (read sau write).
3. Închiderea fișierului (close)
Fișierele standard stdin, stdout și stderr
Stdin este fișierul corespunzând intrării standard de date pentru limbajul Python, de regulă
tastatura.
Stdout este fișierul corespunzând ieșirii standard de date, de regulă ecranul.
Stderr este fișierul corespunzând mediului de afișare a erorilor, de regulă ecranul. Este
asemănător fișierului stdout, cu diferența că afișează doar mesajele referitoare la excepții și erori.
Combinația tastatură+ecran este denumită consola sistemului.
Obiectele fișier stdin, stdout și stderr din Python se găsesc în modulul sys. Aceste fișiere
sunt implicit deschise, deși pot fi închise și deschise ca oricare alt fișier.

10.2 Deschiderea fișierelor


Pentru deschiderea unui fișier se utilizează funcția built-in open(). Această funcție întoarce
un nume de fișier, denumit handle, care este utilizat ulterior pentru a citi sau modifica fișierul
după cum se dorește.
În timpul deschiderii fișierului se poate preciza modul cum se face acest lucru: pentru citire
(r), pentru scriere (w), sau pentru adăugare (a) de date, etc. Mai mult, se poate preciza și dacă
deschiderea se face în mod text sau binar.
Implicit, se face citire în mod text. Prin citirea în mod text, din fișier se obțin date structurate
ca șiruri.
Exemplul 10.1 #sintaxa și utilizarea funcției de deschidere open()
161 Operații I/O. Fișiere

>>>f = open("test.txt") #deschidere fisier in directorul de lucru curent


>>>f = open("C:/Python/exemplul_1.txt") #se specifica o cale,
#mod implicit citire text
>>>f = open("ex_2.txt", mode='r', encoding='utf-8') #se specifica un
#mod de deschidere și un tip de codificare

În modul binar, din fișier se obține informația sub formă binară, în octeți. Acest mod este
utilizat pentru preluarea conținutului fișierelor non-text, ca de exemplu fișierele cu imagini, sau
cod executabil.
Modurile posibile de deschidere sunt: r, w, a, x, t, b, +. Semnificația lor este redată
mai jos.
r - Deschiderea unui fișier pentru citire (implicit)
w - Deschiderea unui fișier pentru scriere. Dacă fișierul nu există, se creează unul nou,
iar dacă există, acesta va fi trunchiat (va fi șters conținutul său).
x - Deschidere doar (exclusiv) pentru creare. Dacă fișierul există deja, operația eșuează.
a - Deschide un fișier pentru adăugare de noi date la sfârșitul său. Dacă fișierul nu există,
se creează unul nou.
t - Deschidere în mod text (implicit)
b - Deschidere în mod binar
+ - Deschidere fișier pentru actualizare (citire și scriere)

În operațiile cu fișiere, se întâlnesc frecvent următoarele combinații ale modurilor de


deschidere:

rb - Deschidere pentru citire fișier în modul binar.


r+ - Deschidere pentru citire și scriere text.
rb+ - Deschidere pentru citire și scriere în modul binar.
wb - Deschidere pentru scriere în modul binar.
w+ - Deschidere pentru scriere și citire text.
wb+ - Deschidere pentru scriere și citire în modul binar.
ab - Deschidere pentru adăugare în modul binar.
a+ - Deschidere pentru adăugare și citire text.
ab+ - Deschidere pentru adăugare și citire în modul binar.
xb - Deschidere pentru exclusiv creare în binar.
x+ - Deschidere pentru exclusiv creare, scriere și citire text.
xb+ - Deschidere pentru exclusiv creare, scriere și citire în binar.

Observație. În modurile scriere și adăugare și combinațiile acestora, dacă fișierul nu există, se


va crea unul nou.
Observație. În modul exclusiv creare (x) și combinațiile acestuia, dacă fișierul există se va
obține eroarea FileExistsError. Scopul existenței modului x este preîntâmpinarea unor erori
accidentale de ștergere a unor fișiere existente (posibile în cazul modului w).
Exemplul 10.2 #moduri de deschidere fișiere
f = open("exemplul7.txt") # mod implicit 'r' si 'rt'
f = open("exemplul8.txt",'w') # modul scriere text
f = open("peisaj.bmp",'r+b') # modul citire și scriere în binar

Codarea textului. Un alt parametru necesar pentru o bună utilizare a comenzii open este
encoding. Precizarea tipului de codare pentru modul text de deschidere este foarte
162 Operații I/O. Fișiere

recomandată în vederea asigurării portabilității aplicațiilor deoarece standardul de reprezentare


implicit pentru caractere poate fi diferit de la un sistem de operare la altul. În Linux/Ubuntu,
este implicit utf-8. Windows 11 utilizează de asemenea 'utf-8', dar la unele versiuni
anterioare a fost cp1252.

f = open("exemplul5.txt", mode='r', encoding='utf-8')

Pentru a afla tipul de codare al sistemului de operare sub care se lucrează, se pot utilza
comenzile:
>>>import sys
>>>sys.getdefaultencoding() #Windows 11
'utf-8'

10.3 Închiderea fișierelor


După încheierea operațiunilor cu datele memorate în fișier, este necesară închiderea acestuia.
Prin închiderea fișierului se eliberează resursele care au fost alocate fișierului deschis. Operația
este făcută prin metoda close(), nefiind necesară apelarea directă a mecanismului “garbage
collector” care funcționează în fundal.
Exemplul 10.3 #închiderea unui fișier
f = open("exemplul5.txt", encoding = 'utf-8')
# diverse operații cu datele fișierului
f.close()

Metoda de mai sus nu prezintă suficientă siguranță. Dacă o excepție are loc în timpul
operațiunilor cu fișierul, programul este părăsit și fișierul poate rămâne deschis.
O cale mai sigură este utilizarea blocului try … finally.
Exemplul 10.4 #includerea operațiilor cu fișiere în blocul try … finally
try:
f = open("exemplu.txt", encoding = 'utf-8')
# diverse operații cu datele fișierului
finally:
f.close()
Această cale asigură închiderea fișierului chiar dacă are loc o excepție care ar putea opri rularea
programului.
Observație. Cea mai bună cale de a închide un fișier este utilizarea instrucțiunii with. Aceasta
garantează că fișierul este închis când blocul intern instrucțiunii with este părăsit. În acest caz
nu mai este necesară apelarea explicită a metodei close(), care se face intern.
Exemplul 10.5 #sintaxa și utilizarea funcției with
with open("exemplu.txt", encoding = 'utf-8') as f:
# diverse operații cu datele fișierului

10.4 Scrierea fișierelor


Pentru a scrie într-un fișier, este necesar ca fișierul să se deschidă cu unul din parametrii de
mod: w (write), a (append) sau x (create).
163 Operații I/O. Fișiere

O atenție mai specială trebuie avută în modul w , deoarece se poate suprascrie conținutul
fișierului, dacă acest conținut există deja.
Scrierea unui șir, sau a unei secvențe de octeți (în cazul fișierelor binare) se face cu metoda
write(). Metoda întoarce și numărul de caractere scrise în fișier.

Exemplul 10.6 # scrierea cu metoda write()

with open("exemplu.txt",'w',encoding = 'utf-8') as f:


f.write("Titlul lucrării\n\n")
f.write("Primul rând al textului\n")
f.write("Al doilea rând al textului\n")
f.write(". . . . . . . . . .\n")
f.write("Al n - lea rând al textului\n")

Această secvență de cod va crea un nou fișier cu numele exemplu.txt dacă acesta nu există în
directorul curent de lucru. Dacă există, se va suprascrie.

10. 5 Citirea fișierelor


10.5.1 Utilizarea metodei read(size)

Pentru citire, un fișier trebuie deschis în modul r. Se pot aplica mai multe procedee pentru
aceasta.
Prima variantă utilizează metoda read(size) pentru a citi un număr size de caractere. Dacă
parametrul size nu este specificat, se citește până la sfârșitul fișierului, când întoarce eof (end-
of-file).
Exemplul 10.7 #citirea fișierului exemplu.txt scris în secțiunea anterioară.
>>>f = open("exemplu.txt",'r',encoding = 'utf-8')
>>>f.read(6) # citirea primelor 6 caractere
'Titlul'
>>>f.read(9) # se citesc încă 9 caractere
' lucrării'
>>>f.read() # se citeste restul de caractere pana la eof (end of file)
'\n\nPrimul rând al textului\nAl doilea rând al textului\n. . . . . .
. . . .\nAl n - lea rând al textului\n'
>>>f.read() # mai departe citirea întoarce siruri vide
''
Se poate vedea că metoda read() întoarce o linie nouă marcată cu '\n'. După atingerea eof,
se obțin prin citire numai șiruri goale.
Observație. Se poate schimba poziția cursorului în fișier utilizând metoda seek().
Observație. Asemănător, metoda tell() întoarce poziția curentă a cursorului în fișier
(numărul de caractere/octeți față de început).
Exemplul 10.8 #poziționarea în fișier
>>>f.tell() # obține poziția curentă in fisier
136
>>>f.seek(0) # poziționarea cursorului pe zero
0
>>>print(f.read()) # read the entire file
Titlul lucrării
Primul rând al textului
164 Operații I/O. Fișiere

Al doilea rând al textului


. . . . . . . . . .
Al n - lea rând al textului

10.5.2 Citirea fișierului utilizând o comandă repetitivă

A doua variantă permite citirea rapidă și eficientă un fișier text linie cu linie folosind o buclă
for.

Exemplul 10.9 #citire fișier text utilizând for


>>>f.seek(0)
0
for line in f:
print(line, end = '')
Titlul lucrării
Primul rând al textului
Al doilea rând al textului
. . . . . . . . . .
Al n - lea rând al textului

In fișierul txt, liniile sale includ caracterul linie nouă (newline \n), astfel că în funcția print()
nu a mai fost necesar să se prevadă acest simbol, pentru ca listarea să fie corect aliniată.

10.5.3 Utilizarea metodei readline()

A treia variantă permite citirea individuală a liniilor fișierului prin utilizarea metodei
readline(). Această metodă citește fișierul până la caracterul newline (inclusiv).

Exemplul 10.10 #citire linie cu linie din fișier


>>>f.seek(0)
0
>>>f.readline()
'Titlul lucrării\n'
>>>f.readline()
'\n'
>>>f.readline()
'Primul rând al textului\n'
>>>f.readline()
'Al doilea rând al textului\n'
>>>f.readline()
'. . . . . . . . . .\n'
>>>f.readline()
'Al n - lea rând al textului\n'
>>>f.readline()
''

Observație. Metoda readlines() întoarce toate liniile până la sfârșitul fișierului.


Exemplul 10.11 #citire toate liniile până la eof
>>>f.seek(0)
0
>>>f.readlines()
['Titlul lucrării\n',
'\n',
'Primul rând al textului\n',
165 Operații I/O. Fișiere

'Al doilea rând al textului\n',


'. . . . . . . . . .\n',
'Al n - lea rând al textului\n']

Toate cele trei procedee întorc valori vide când se atinge sfârșitul fișierului (eof).

10.6 Metode pentru fișiere


Pentru lucrul cu fișierele text sunt disponibile atributele și metodele din lista de mai jos obținute
cu comanda dir().

fisier = open("matcon.txt" )
dir(fisier)
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__',
'__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '_checkClosed',
'_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing',
'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno',
'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read',
'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable',
'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']

Tabelul 10.1 Semnificația metodelor aplicabile obiectelor fișier

close() - Închide un fișier deschis. Nu are efect dacă fișierul este deja închis.
detach() - Separă bufferul binar din TextIOBase și îl returnează.
fileno() - Întoarce un număr întreg, descriptorul de fișier
flush() - Eliberează bufferul de scriere din memorie al fluxului fișier (îl trimite în disc)
isatty() - Întoarce True dacă fluxul fișier este interactiv.
read(n) - Citește până la n charactere din fișier. Citește până la sfârșitul fișierului dacă n este
negativ sau None.
readable() - Întoarce True dacă fluxul fișier poate fi citit.
readline(n = -1) - Citește și întoarce o linie din fișier. Citește până la n octeți dacă este
specificat.
readlines(n = -1) - Citește și întoarce o listă de linii din fișier. Citește cel mult n linii dacă este
specificat.
seek(offset, from = SEEK_SET) - Schimbă poziția cursorului în fișier cu offset octeți, în
raport cu from
seekable() - Întoarce True dacă fluxul fișier permite acces aleator.
tell() - Întoarce poziția cursorului în fișier.
truncate(size = None)- Redimensionează fluxul fișier la size octeți. Dacă size nu este
specificat, redimensionează locația curentă.
writable() - Întoarce True dacă fluxul fișier poate fi scris
write(s) - Scrie șirul s în fișier și întoarce numărul de caractere scrise.
writelines(lines) - Scrie o listă de linii în fișier.

10.7 Operații cu directoare și fișiere


10.7.1 Conceptul de director (folder)
166 Operații I/O. Fișiere

Definiție. Un director sau un folder (dosar) este o colecție de fișiere și subdirectoare. Modulul
os conține multe metode utile pentru lucrul cu directoare și fișiere.
Observație. Se poate obține numele directorului de lucru curent utilizând metoda getcwd()
(get current working directory), din modulul os.Numele întors este sub formă de șir. De
asemenea, pentru obținerea numelui ca obiect binar se poate utiliza medoda getcwdb().
Exemplul 10.12
>>> import os
>>> os.getcwd()
'C:\\RoboDK\\Python37'
>>> print(os.getcwd())
C:\RoboDK\Python37

Backslash-ul suplimentar produce o secvență escape, astfel că funcția print() a afișat


corect.

10.7.2 Schimbarea directorului


Se poate schimba directorul curent de lucru cu metoda chdir().
Metoda cere ca argument un șir de caractere care să reprezinte noua cale (adresă). Șirul poate
să conțină simbolul slash direct ( / ) pentru sistemul de operare Linux sau slash invers ( \ ),
backslash, pentru sistemul de operare Windows. Din acest motiv, la scrierea șirurilor se
utilizează o secvență escape la scrierea simbolului slash invers (\\).
Exemplul 10.13 #schimbarea directorului
>>>os.getcwd()
'E:\\A_Python-work'
>>>os.chdir('E:\\RoboDK')
>>>os.getcwd()
'E:\\RoboDK'

10.7.3 Obținerea listei directoarelor și fișierelor


Pentru obținerea listei fișierelor și subdirectoarelor conținute de un director se poate folosi
metoda listdir().
Metoda listdir() acționează pentru locația determinată de calea de acces și întoarce lista
fișierelor și subdirectoarelor conținute. Dacă nu este precizată calea de acces la director, se
întoarce lista subdirectoarelor și fișierelor din directorul curent.
Exemplul 10.14 #lista subdirectoarelor și a fișierelor
>>> print(os.getcwd())
C:\Users\USER
>>> os.listdir()
['(test)',
'.anaconda',
'.android',
'.astropy',
'.cache',
'.conda',
'.condarc',
'.config',
'.dotnet',
167 Operații I/O. Fișiere

'.gitconfig',
'.glue',
'.idea',
'.idlerc',
'.ipynb_checkpoints',
'.ipython',
'.jupyter',
'.keras',
'.kivy',
'.matplotlib',
'.openjfx',
'.pylint.d',
'.spyder-py3',
. . . . . . . . . . . . . .

>>> os.listdir('C:\\')
[['$AV_ASW',
'$Recycle.Bin',
'$WinREAgent',
'AX NF ZZ',
'Blender',
'BMW M3 Challenge',
'Brother',
'Config.Msi',
'Documents and Settings',
'DRIVERS',
'DroidCam',
. . . . . . . . . . . . . . .

10.7.4 Crearea unui nou director


Directoarele noi pot fi create cu metoda mkdir().
Se poate preciza calea spre directorul nou creat, sau dacă acesta nu este dat atunci noul director
este creat în directorul curent de lucru.
Exemplul 10.15 #creare director nou cu mkdir()
>>> os.mkdir('test')
>>> os.listdir()
['test']

10.7.5 Redenumirea unui director sau fișier


Redenumirea unui director sau fișier se poate face cu metoda rename().
Metoda rename()are nevoie de două argumente pentru a redenumi un director sau un fișier:
vechiul nume ca prim argument și noul nume ca al doilea argument.
Exemplul 10.16 #redenumirea unui fișier cu rename()
>>> os.listdir()
['test']
>>> os.rename('test','test2')
>>> os.listdir()
['test2']

10.7.6 Ștergerea unui director sau fișier


168 Operații I/O. Fișiere

Un fișier poate fi șters cu metoda remove().


Asemănător, metoda rmdir() șterge un director gol (care nu mai conține alte subdirectoare sau
fișiere).
Exemplul 10.17 #utilizare remove() și rmdir()
>>> os.listdir()
['t1.txt', 't2.txt']
>>> os.remove('t2.txt')
>>> os.listdir()
['t1.txt']
>>> os.rmdir('test2')
>>> os.listdir()
[]

Observație. Deoarece nu se poate utiliza metoda rmdir() pentru ștergerea directoarelor care
nu sunt vide, se poate face apel la metoda rmtree(). Aceasta poate fi importată din modulul
shutil.
Exemplul 10.18 #ștergere director ne-vid cu rmtree()
>>> os.listdir()
['test']
>>> os.rmdir('test')
Traceback (most recent call last):
...
OSError: [WinError 145] The directory is not empty: 'test'
>>> import shutil
>>> shutil.rmtree('test')
>>> os.listdir()
[]

10.7.7 Redirectarea fișierelor stdin, stdout și stderr


Fișierele stdin, stdout și stderr pot fi redirectate către un alt fișier având un alt
număr_identificator (handler). În cazul stdin, intrarea programului poate fi schimbată de la
consolă la un fișier text, iar în cazul stdout și stderr se poate face înregistrarea mesajelor și
păstrarea lor pentru mai mult timp într-un fișier text.
Exemplul 10.19 #redirectarea ieșirii de la Consolă la fișierul iesire.txt
import sys
import os
f_revenire_stdout = sys.stdout #handler de revenire după redirectare
date_intrare = ["start", "inainte", "dreapta", "stop"]
print("Urmeaza scrierea in stdout")
sys.stdout = open("iesire.txt", "w", encoding = 'utf-8')
for di in date_intrare:
sys.stdout.write(di + "\n")
f_revenire_stdout.write(di + "\n") #memorare
sys.stdout.close
sys.stdout = f_revenire_stdout
print(os.listdir())
#try:
# f = open("iesire.txt", encoding = 'utf-8')
#finally:
# f.close()
print("Afisare continut iesire.txt")
f = open("iesire.txt", "r", encoding = 'utf-8')
print(f.read())
169 Operații I/O. Fișiere

Output
Urmeaza scrierea in stdout
start
inainte
dreapta
stop
[. . . . . . 'iesire.txt' . . . . . . .]
Afisare continut iesire.txt
start
inainte
dreapta
stop

10.8 Întrebări, exerciții și probleme


10.8.1 Să se completeze următorul tabel cu simbolurile v (operație implicată), x (operație
interzisă), s (start) și e (end) pentru evidențierea compatibilităților între operații și modurile de
acces.
Mod r r+ w w+ a a+ x x+
Operație
read
write
creare
ștergere
Poziție (s-start,
e-end)

R10.8.1
Mod r r+ w w+ a a+ x x+
Operație
read        
write        
creare        
ștergere        
Poziție inițială s s   e e s s
(s-start, e-end)

10.8.2 Să se creeze, scrie și afișeze un fișier cu numele “matcon.txt” având un conținut


corespunzând tabelului următor:
Cod Denumirea Lungime Secțiune Preț
BL1 Bară lemn pătrată 4 4x4 25
BL4 Bară lemn pătrată 5 8x8 60
TM5 Țeavă metalică pătrată 2mm 6 4x4 100
TM6 Țeavă metalică pătrată 4mm 6 6x6 250
BL9 Bară lemn dreptunghiulară 8 8x15 150
R10.8.2
f_mc = open("matcon.txt", 'w')
print("Creare si scriere fisier matcon.txt")
f_mc.write("BL1 Bara lemn patrata 4 4x4 25"+"\n")
f_mc.write("BL4 Bara lemn patrata 5 8x8 60"+"\n")
f_mc.write("TM5 Teava metalica patrata 2mm 6 4x4 100"+"\n")
f_mc.write("TM6 Teava metalica patrata 4mm 6 6x6 250"+"\n")
f_mc.write("BL9 Bara lemn dreptunghiulara 8 8x15 150"+"\n")
170 Operații I/O. Fișiere

f_mc.close()

print("Fisierul matcon.txt a fost creat si inchis")


print()
print("Listare metoda 1 - read()")

f_mc = open("matcon.txt", 'r')


print("Cod Denumire Lungime Sectiune Pret")
print(f_mc.read())

print("Listare metoda 2 - for line in")


f_mc.seek(0)
print("Cod Denumire Lungime Sectiune Pret")
for line in f_mc:
print(line, end = '')
print()

print("Listare metoda 3 - readline()")


f_mc.seek(0)
print("Cod Denumire Lungime Sectiune Pret")
#detectarea end_of_file prin handler != None
while(f_mc != None):
f=f_mc.readline()
print(f)
if (f ==''):
break

print("Listare metoda 4 - readlines()")


f_mc.seek(0)
print(f_mc.readlines())

Output
Creare si scriere fisier matcon.txt
Fisierul matcon.txt a fost creat si inchis

Listare metoda 1 - read()


Cod Denumire Lungime Sectiune Pret
BL1 Bara lemn patrata 4 4x4 25
BL4 Bara lemn patrata 5 8x8 60
TM5 Teava metalica patrata 2mm 6 4x4 100
TM6 Teava metalica patrata 4mm 6 6x6 250
BL9 Bara lemn dreptunghiulara 8 8x15 150

Listare metoda 2 - for line in


Cod Denumire Lungime Sectiune Pret
BL1 Bara lemn patrata 4 4x4 25
BL4 Bara lemn patrata 5 8x8 60
TM5 Teava metalica patrata 2mm 6 4x4 100
TM6 Teava metalica patrata 4mm 6 6x6 250
BL9 Bara lemn dreptunghiulara 8 8x15 150

Listare metoda 3 - readline()


Cod Denumire Lungime Sectiune Pret
BL1 Bara lemn patrata 4 4x4 25
BL4 Bara lemn patrata 5 8x8 60
TM5 Teava metalica patrata 2mm 6 4x4 100
TM6 Teava metalica patrata 4mm 6 6x6 250
BL9 Bara lemn dreptunghiulara 8 8x15 150

Listare metoda 4 - readlines()


171 Operații I/O. Fișiere

['BL1\tBara lemn patrata\t 4\t4x4\t25\n', 'BL4\tBara lemn patrata\t


5\t8x8\t60\n', 'TM5\tTeava metalica patrata 2mm\t6\t4x4\t100\n',
'TM6\tTeava metalica patrata 4mm\t6\t6x6\t250\n', 'BL9\tBara lemn
dreptunghiulara \t8\t8x15\t150\n']

10.8.3 Să se calculeze și să se afișeze numărul de linii, cuvinte și caractere în fișierul


matcon.txt creat la 10.8.2

R10.8.3
nume_fis = "matcon.txt"
Tlinii = Tcuvinte = Tcaractere = 0

for i_linie in open(nume_fis):


Tlinii += 1
Tcaractere += len(i_linie)

flag = 'unu'
for caract in i_linie:
if (caract != ' ')|(caract != "\t") and flag == 'unu':
Tcuvinte += 1
flag = 'zero'
elif (caract == ' ')|(caract == "\t"):
flag = 'unu'

print("Total Linii:", Tlinii)


print("Total Cuvinte", Tcuvinte)
print("Total Caractere", Tcaractere)

Output
Total Linii: 5
Total Cuvinte 45
Total Caractere 202

10.8.4 Să se citească fișierul “matcon.txt” sub forma unei liste, eliminând caracterele “\n” și
înlocuind caracterele “\t” cu patru spații.
R10.8.4
t_f = open("matcon.txt")
t_txt = t_f.read()
t_f.close
print("Continut initial")
print(repr(t_txt))
print()
l_txt = t_txt.split("\n")
#print(l_txt)
nl_txt = ' '.join(l_txt)
print()
print("Continut fara nl")
print(repr(nl_txt))

tab_txt = nl_txt.split("\t")
# print(tab_txt)
sp_txt = ' '.join(tab_txt)
print()
print("Cantinut fara nl si tab")
print(repr(sp_txt))

sp_f = open("matcon_filtrat.txt", "w")


sp_f.write(sp_txt)
172 Operații I/O. Fișiere

sp_f.close()

Output
Continut initial
'BL1\tBara lemn patrata\t 4\t4x4\t25\nBL4\tBara lemn patrata\t
5\t8x8\t60\nTM5\tTeava metalica patrata 2mm\t6\t4x4\t100\nTM6\tTeava
metalica patrata 4mm\t6\t6x6\t250\nBL9\tBara lemn dreptunghiulara
\t8\t8x15\t150\n'

Continut fara nl
'BL1\tBara lemn patrata\t 4\t4x4\t25 BL4\tBara lemn patrata\t
5\t8x8\t60 TM5\tTeava metalica patrata 2mm\t6\t4x4\t100 TM6\tTeava
metalica patrata 4mm\t6\t6x6\t250 BL9\tBara lemn dreptunghiulara
\t8\t8x15\t150 '

Cantinut fara nl si tab


'BL1 Bara lemn patrata 4 4x4 25 BL4 Bara lemn
patrata 5 8x8 60 TM5 Teava metalica patrata 2mm 6
4x4 100 TM6 Teava metalica patrata 4mm 6 6x6 250 BL9
Bara lemn dreptunghiulara 8 8x15 150 '
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 11

Programarea Orientată pe Obiecte – prezentare


generală
11.1 Generalități
O abordare de actualitate în domeniul limbajelor de programare este rezolvarea problemelor
de programare prin crearea de obiecte, metoda fiind denumită Programarea Orientată pe
Obiecte, sau POO (Object-Oriented Programming, OOP).
Ideea de bază a conceptului POO este că orice obiect se caracterizează prin două aspecte
principale:
• Atributele care îl descriu.
• Modul de comportament.
Spre deosebire de programarea orientată pe proceduri (programarea procedurală, uneori numită
și programarea structurată), unde interesul este pentru funcții, programarea orientată pe obiecte
(POO) este concentrată pe obiecte.
Lumea în care trăim este o lume orientată spre obiecte.
Exemplul 11.1
Un vehicul este un obiect care poate avea proprietățile următoare:
• Atribute: marca, denumirea, culoarea, puterea motorului, numărul de locuri, etc.
• Comportament: merge înainte, merge înapoi, accelerează, frânează, etc.
La baza apariției conceptului OOP au existat mai multe interese și necesități practice, printre
care nu cea mai puțin importantă a fost reutilizabilitatea codului (dorința ca unele părți sau
secvențe de cod să poată fi refolosite, economisind scrierea lor), în engleză: Don't Repeat
Yourself, DRY.
Python este un limbaj construit pe baza mai multor modele de programare, astfel încât permite
mai multe abordări în construcția programelor, inclusiv POO.
Pentru a utiliza limbajul Python la adevărata sa valoare și putere este necesară înțelegerea
caracteristicilor sale de limbaj pentru programarea orientată pe obiecte. Din acest motiv este
necesară la început o prezentare generală a conceptelor POO, urmând ca studiul concret și
detaliat al caracteristicilor limbajului Python, specifice POO, să fie făcut într-un capitol
distinct, cu exemple concrete.
Definiție. Un obiect este o entitate care se poate distinge dintre alte entități și care are o anumită
semnificație în contextul modelului unei aplicații. Fiecare obiect are o identitate proprie. În
termeni software, un obiect este o structură pentru încorporarea de date și proceduri de lucru
cu aceste date.
Fiecare dintre noi suntem un obiect. Interacționăm cu alte obiecte. De fapt, suntem un obiect
cu date precum înălțimea, greutatea și culoarea părului sau ochilor. De asemenea, avem metode
care funcționează prin noi sau sunt executate de noi, cum ar fi modul de a mânca, intensitatea
vocii, gestica mâinilor, mersul, etc.
174 Programarea Orientată pe Obiecte – prezentare generală

Alte exemple: automobil, contabil, procesul 25, meniul principal, Mihai, etc.
Definiție. O clasă este o descriere a unei mulțimi de obiecte cu proprietăți (atribute) similare,
comportament similar (operații similare) și relații similare față de alte obiecte. O clasă este un
model pentru un tip de obiecte (abstractizează o mulțime de obiecte). Exemple de clasă:
vehicul, funcționar, proces, meniu, persoană, etc.
Observație. Nu trebuie să se confunde clasa cu obiectul. Dacă există o clasă persoană, atunci
George, Vasile sunt obiecte din aceeași clasă, denumite instanțe ale clasei persoană. Obiectul
moștenește toate caracteristicile clasei din care face parte.

11.2 Clasele
O clasă poate fi gândită ca un șablon după care se construiește un obiect (o descriere, sau o
declarație). Pentru clase nu se alocă memorie.
Exemplul 11.2 #clasa automobil
Ca ilustrare a noțiunii de clasă se poate oferi modelul sau schița unui automobil, compusă din
etichete care conțin detalii despre nume, culoare, dimensiuni, etc. Pe baza acestei descrieri, se
poate cunoaște ce este și cum funcționează obiectul automobil.
Sintaxa clasei automobil este:
class automobil:
pass

Pentru definirea unei clase vide s-a utilizat cuvântul cheie class .
Dintr-o clasă se poate crea un obiect specific denumit instanță. Altfel spus, implementările
particularizate ale claselor sunt denumite instanțe. În consecință, un obiect este o instanță
a unei clase.

11.3 Obiectele
Definiție. Un obiect (instanța) este o instanțiere a unei clase. Spre deosebire de clase, pentru
obiecte se alocă memorie.
Exemplul 11.3 #obiect instanțiat din clasa automobil
obj = automobil()

Prin instrucțiunea de mai sus, obj este un obiect creat plecând de la clasa automobil.
Dacă se dispune de amănuntele descriptive și comportamentale ale noțiunii de automobil
(atributele), se pot construi clasa și obiectele de tip automobil.
Exemplul 11.4 #Crearea claselor și obiectelor automobil
class automobil:
# atributul
categoria = "vehicul"

#functia de initializare
def __init__(self, marca, nume, an):
self.marca = marca
self.nume = nume
self.an = an
175 Programarea Orientată pe Obiecte – prezentare generală

# instantierea clasei automobil se face de catre funcția constructor


#cu numele __new__() si care se lanseaza automat la
#momentul crearii obiectului
Focus = automobil("Ford", "Focus", 2013)
Duster = automobil("Dacia", "Duster", 2016)

# access la atributul clasei - este acelasi pentru toate obiectele


print("Focus este un {}".format(Focus.__class__.categoria))
print("Si Duster este un {}".format(Duster.__class__.categoria))

#acces la atributele obiectelor – sunt specifice acestora


print("{} an de fabricatie {}".format( Focus.nume, Focus.an))
print("{} an de fabricatie {}".format( Duster.nume, Duster.an))

Output
Focus este un vehicul
Si Duster este un vehicul
Focus an de fabricatie 2013
Duster an de fabricatie 2016

De notat că în programul de mai sus sunt două feluri de atribute ale clasei.
Atributul categoria este specific clasei și are aceeași valoare pentru toate instanțele clasei. De
aceea este denumit atribut al clasei și poate fi apelat și înainte de instanțierea clasei (înainte
de crearea obiectelor).
Atributele marca, nume, an vor lua valori specifice instanței, ele fiind inițializate în momentul
în care se creează instanța (obiectul) în prin metoda __init__ , care se execută prima. Acestea
sunt atribute specifice obiectelor.
Ford și Dacia sunt referințe la obiectele noi create.
Toate obiectele noi vor include aceeași valoare pentru atributul categoria care nu poate fi
modificat la crearea unui nou obiect, dar atributele marca, nume, an vor putea fi modificate
de fiecare dată când se creează un nou obiect.

11.4 Metodele
Funcțiile definite în corpul unei clase sunt denumite metode. Scopul lor este impunerea unui
anumit comportament obiectului.
Exemplul 11.5 #Crearea clasei și obiectelor automobil
class automobil:
# atributele
categoria = "vehicule"
# constructorul
def __init__(self, marca, nume, an):
self.marca = marca
self.nume = nume
self.an = an
#metodele
def cumpara(self, pret):
return "{} cumparare {} lei".format(self.nume, pret)
def vinde(self, pret):
return "{} vinzare {} lei".format(self.nume, pret)
#instantierea obiectului
auto1 = automobil("Dacia", "Duster", 2013)
176 Programarea Orientată pe Obiecte – prezentare generală

auto2 = automobil("Ford", "Focus", 2016)


#apeleaza metoda instantei
print(auto1.cumpara(10000))
print(auto2.vinde(7000))

Output
Duster cumparare 10000 lei
Focus vinzare 7000 lei

În programul de mai sus s-au definit metodele cumpara() și vinde(). Ele au fost apelate numai
după ce au fost create obiectele auto1 și auto2 (instanțe ale clasei automobil). De aceea se
numesc și metode ale unor obiecte (ale unor instanțe).

11.5 Programarea orientată pe obiecte


Lumea reală poate fi privită ca o lume formată din obiecte aflate în interacțiune.
Programarea orientată pe obiecte este o abordare a dezvoltării de software în care structura
softwarelui se bazează pe obiecte care interacționează între ele pentru a îndeplini o sarcină.
Această interacțiune apare sub forma mesajelor care se schimbă între obiecte. Ca răspuns la un
mesaj, un obiect poate efectua o acțiune (execută o metodă).
Exemplul 11.6 #Obiectul vehicul
In general se interacționează cu un vehicul pentru utilizarea funcțiunii sale de a transporta. Un
obiect vehicul este format însă din alte obiecte: caroserie, roți, motor, etc., care interacționează
între ele pentru îndeplinirea funcțiunii de transport. Pentru pornirea vehiculului se utilizează
un obiect cheie prin a cărei rotire se transmite un mesaj (prin intermediul unui semnal electric)
către demaror, care acționează la rândul său motorul, care prin arborele motor mișcă roțile, etc.
De remarcat că obiectul șofer este izolat de obiectul vehicul, care răspunde tot printr-un mesaj.
Conceptele POO au început să apară pentru prima data la mijlocul anilor ’60 în limbajul de
programare numit Simula și au evoluat în continuare în anii 70 odată cu apariția limbajului
Smalltalk. Deși dezvoltatorii de software nu au adoptat imediat aceste principii stabilite
timpuriu în limbaje de tip POO, metodologiile orientate pe obiecte au continuat să evolueze.
Abia la mijlocul anilor 80, datorită interesului crescut pentru aceste metodologii orientate pe
obiecte, au apărut limbajele POO, dintre care se distinge în mod deosebit C ++, care a devenit
un standard universal recunoscut printre programatorii de calculatoare. POO a continuat să
crească în popularitate în anii 90, mai ales cu apariția Java care a avut o influență uriașă.

11.6 Utilitatea POO


POO s-a dezvoltat în principal ca răspuns la cererea de a proiecta aplicații mari și foarte mari.
Pe parcursul anilor 70 și 80, au fost utilizate pe scară largă pentru a dezvolta sisteme software
limbaje de programare orientate procedural, cum sunt Fortran, Pascal și C.
Limbajele procedurale organizează programul într-un mod liniar, instrucțiunile programului se
rulează secvențial de sus în jos (“top-down“), ca o serie de pași care se execută unul după altul
în cadrul programului. Acest tip de programare a funcționat bine pentru programele mici,
alcătuite din câteva sute linii de cod (instrucțiuni), dar pe măsură ce programele au devenit mai
mari, au devenit mult mai greu de întreținut și depanat.
177 Programarea Orientată pe Obiecte – prezentare generală

În încercarea de a gestiona dimensiunea din ce în ce mai mare a programelor, programarea


structurată a fost la început o soluție. În cadrul ei, codul era descompus în segmente mai ușor
administrabile numite funcții sau proceduri. Aceasta a fost o îmbunătățire față de “programarea
spaghetti”, inițială, care abunda în utilizări ale instrucțiunilor de salt de tip “goto”, dar pe
măsură ce programele realizau funcționalități mai complexe și interacționau cu alte sisteme, au
început să se evidențieze următoarele neajunsuri ale metodologiei de programare structurată:
- Pe măsură ce dimensiunea programelor creștea, deveneau tot mai greu de întreținut.
- Funcționalitatea existentă era greu de modificat fără a afecta negativ funcționalitatea
sistemului în ansamblu.
- Noile programe trebuiau să fie construite în esență de la zero. În consecință, rentabilitatea
investițiilor în programare era redusă deoarece eforturilor anterioare nu erau valorificate.
- Programarea nu permitea dezvoltarea pe echipe. Programatorii trebuiau să cunoască fiecare
aspect al modului în care funcționa un program, deoarece acesta nu putea fi descompus în
module, asfel ca acestea să fie distribuite membrilor unei echipe pentru a fi realizate separat
sau independent.
- Transpunerea modelelor aplicațiilor în modele de programare era dificilă.
- Programele funcționau bine izolat, dar nu se integrau bine cu alte sisteme.
În plus față de aceste neajunsuri, unele evoluții ale sistemelor de calcul au provocat mărirea
cerințelor la crearea programelor structurate, cum ar fi:
- Utilizatorii (neprogramatori) au solicitat și au primit acces direct la programe prin
intermediul încorporării interfețelor grafice (GUI).
- Utilizatorii au solicitat o abordare mai intuitivă și mai simplă pentru a interacționa cu
programele.
- Sistemele informatice au evoluat într-un model distribuit în care logica aplicațiilor,
interfețele utilizator și bazele de date au fost cuplate mai slab și accesate prin intermediul
internetului și al intraneturilor.
În consecință, mulți dezvoltatori de software aplicativ de tip economic sau industrial au apelat
la metodologii orientate pe obiecte și limbaje de programare pentru rezolvarea acestor
probleme. Avantajele noii abordări includ următoarele:
- O tranziție mai intuitivă de la modelele de aplicații la modelele de implementare software.
- Capacitatea de a actualiza și implementa modificările programelor mai eficient și mai rapid.
- Capacitatea de a crea mai eficient sisteme software folosind echipe de specialiști, permițând
acestora să lucreze la anumite părți ale sistemului.
- Posibilitatea de a reutiliza componentele de cod în alte programe și de a cumpăra
componente scrise de dezvoltatori terți pentru a crește funcționalitatea sistemului nou cu mai
puțin efort.
- O integrare mai bună cu sisteme de calcul distribuite, slab cuplate.
- O integrare îmbunătățită cu sistemele de operare moderne.
- Posibilitatea de a crea o interfață grafică mai intuitivă pentru utilizatori.

11.7 Caracteristicile structurale ale modelului obiectelor utilizate


în POO
Printre conceptele și termenii fundamentali comuni tuturor limbajelor de tip POO sunt
abstractizarea, încapsularea, polimorfismul și moștenirea.
178 Programarea Orientată pe Obiecte – prezentare generală

11.7.1 Abstractizarea
Definiție. Abstractizarea este o caracteristică care evidențiază aspectele esențiale ale unei
entități, eventual neglijând (filtrând) unele proprietăți mai puțin importante sau care nu
interesează în contextul aplicației. În cazul unui obiect se precizează ce este și ce trebuie să
facă acel obiect.
Fără a abstractiza sau filtra proprietățile în exces ale obiectelor, ar fi greu să se proceseze
multitudinea de informații existente și să se reușească focalizarea pe sarcina de îndeplinit.
Ca urmare a abstractizării, când două persoane diferite interacționează cu același obiect, se
ocupă adesea fiecare cu un subset diferit de atribute. De exemplu, când conduce automobilul,
șoferul trebuie să știe viteza mașinii și direcția pe care merge. Deoarece mașina are un sistem
automat de comandă, el nu trebuie să cunoască turația sau cuplul motorului, așa că va filtra
aceste informații. Pe de altă parte, aceste informații ar fi esențiale pentru un șofer pilot de curse,
care nu le-ar filtra.
Atunci când se construiesc obiecte în aplicațiile OOP, este important să se utilizeze conceptul
de abstractizare. De exemplu, în cazul unei aplicații de transport, se poate proiecta un produs
(obiect) cu atribute precum mărimea și greutatea. Culoarea produsului ar fi o informație
excedentară și va fi eliminată. Pe de altă parte, la proiectarea unei aplicații de introducere date
referitoare la comenzi, culoarea ar putea fi importantă și va fi inclusă ca atribut al produsului
obiect.
11.7.2 Moştenirea
Majoritatea obiectelor sunt clasificate pe baza ierarhiilor.
Ierarhiile se pot constitui pe baza proprietăților sau funcțiunilor. De exemplu, vehiculele se pot
clasifica în grupuri ca având anumite caracteristici comune, cum ar fi numărul de roți și
sistemul de propulsie. Grupurile se clasifică în continuare în subgrupuri cu atribute comune,
cum ar fi culoarea și dimensiunea, etc. De asemenea, o altă clasificare a vehiculelor se poate
face în raport de funcția lor. De exemplu, există vehicule comerciale de mărfuri (camioane,
autobasculante, etc.) și vehicule de pasageri (autoturisme, autobuze, etc.).
Definiție. Moștenirea este transmiterea atributelor și operațiilor într-o ierarhie de clase, din
clasă în clasă.
Moștenirea simplifică programarea deoarece se permite atribuirea caracteristicilor generale
unui obiect părinte și moștenirea acestor caracteristici în obiectul ierarhic descendent (copil).
Exemplul 11.7 #ilustrarea moștenirii
Se poate defini un obiect “salariat” care definește toate caracteristicile generale ale salariților
dintr-o firmă. Se poate defini apoi un obiect “șef_formație” care moștenește caracteristicile
obiectului salariat, dar adaugă și caracteristici unice pentru șefii de formații din firmă. Obiectul
șef_formație va reflecta automat orice modificare în implementarea obiectului salariat.
Moștenirea este o cale de creare a unei noi clase utilizând o clasă existentă deja. Noua clasă
beneficiază de toate detaliile proprii vechii clase, care nu se modifică. Se spune că noua clasă
(copil, sau child) este derivată din vechea clasă (clasa părinte sau clasa de bază).
Exemplul 11.8 #utilizarea moștenirii
# clasa parinte (de baza)
class Vehicul:
def __init__(self):
print("vehiculele transportă ceva")
179 Programarea Orientată pe Obiecte – prezentare generală

def identif(self):
print("Vehicul")
def miscare(self):
print("Vehiculul se mișcă")
# clasa copil (derivata)
class Avion(Vehicul): #considerand avionul un vehicul aerian
def __init__(self):
# apelează functia super()
super().__init__()
print("Avionul zboară")
def identif(self):
print("Avion")
def zboara(self):
print("zborul este mai rapid")
Tarom_237 = Avion()
Tarom_237.identif()
Tarom_237.miscare()
Tarom_237.zboara()

Output
vehiculele transportă ceva
Avionul zboară
Avion
Vehiculul se mișcă
zborul este mai rapid

În programul de mai sus s-au creat două clase: Vehicul (clasa părinte sau de bază) și Avion
(clasa copil sau derivată). Clasa copil moștenește funcțiile clasei părinte. Acest lucru este
dovedit prin metoda miscare().
Clasa copil modifică comportamentul clasei părinte, acest lucru fiind dovedit de metoda
identif(). De asemenea, clasa copil extinde funcțiunile clasei părinte prin crearea metodei
zboara().

Observație. Pentru a da posibilitatea execuției metodei __init__() a clasei părinte în


interiorul clasei copil, s-a utilizat funcția super() în interiorul metodei __init__().
11.7.3 Încapsularea
O altă caracteristică importantă a OOP este încapsularea (ascunderea informației).
Definiție. Prin încapsulare se înțelege separarea aspectelor externe (publice) de cele interne
(private) ale obiectului.
Încapsularea este un concept întâlnit în viața de zi cu zi tot timpul. Fie exemplul unui obiect
“department de resurse umane”. Acesta încapsulează (ascunde) informațiile despre angajați
care nu sunt publice. El determină modul în care aceste date pot fi folosite și manipulate. Orice
cerere pentru datele angajaților sau solicitarea de actualizare a datelor trebuie să fie transmisă
prin departament.
Un alt exemplu este obiectul “securitatea rețelei”. Orice solicitare pentru informațiile de
securitate sau modificarea unei politici de securitate trebuie făcută printr-un administrator de
securitate al rețelei. Datele de securitate sunt încapsulate față de utilizatorii rețelei. Încapsularea
contribuie la îmbunătățirea siguranței și fiabilității datelor unui sistem. Sistemul știe cum se
accesează datele și ce operațiuni se efectuează pe date. Acest lucru face ca întreținerea
programelor să fie mult mai ușoară și, de asemenea, simplifică foarte mult procesul de
depanare.
180 Programarea Orientată pe Obiecte – prezentare generală

Unul dintre conceptele de bază ale POO este restricționarea accesului la metode și variabile,
cu scopul evitării alterării lor nedorite. Pentru a pune în practică acest concept este utilizată
metoda încapsulării variabilelor și metodelor.
Definiție. A încapsula o metodă sau variabilă înseamnă de fapt a restricționa accesul la acestea
(denumite private), spre deosebire de cazul celor publice.
Observație. Atributele private sunt diferențiate față de cele publice prin utilizarea in Python a
prefixelor underscore simplu ( _ ) și dublu ( __ ).
Exemplul 11.9 # încapsularea datelor in Python
class minge_tenis:
def __init__(self):
self.__pretmax = 5
def vinde(self):
print("Pret de vanzare: {}".format(self.__pretmax))
def pune_pretmax(self, pret):
self.__pretmax = pret
c = minge_tenis()
c.vinde()
# incercare de schimbare de pret, care
# va esua deoarece nu se poate schimba variabila privata
c.__pretmax = 4
c.vinde()
# utilizează funcția de stabilire a pretului, va functiona
c.pune_pretmax(4)
c.vinde()

Output
Pret de vanzare: 5
Pret de vanzare: 5
Pret de vanzare: 4

În programul de mai sus s-a definit clasa minge_tenis.


S-a utilizat metoda __init__() pentru a stabili prețul maxim de vânzare pentru o minge de
tenis. Apoi s-a încercat să se modifice prețul în două moduri. Prima dată s-a eșuat deorece
variabila __pretmax a fost tratată de Python ca variabilă privată (deci nemodificabilă din
exterior).
A doua oară s-a reușit deoarece s-a folosit o funcție (metodă) denumită pune_pretmax() care
primește noul preț ca parametru.
11.7.4 Polimorfismul
În general, “polimorfism” înseamnă existența mai multor forme ale aceleiași entități.
Definiție. În programare, polimorfismul este capacitatea a două obiecte diferite de a răspunde
la același mesaj în mod specific.
Exemplul 11.10 #ilustrare polimorfism
Comanda “imprimă” poate avea efecte diferite (font, culoare, etc.) dacă este trimisă la obiectul
de imprimare pe ecran sau la printerul cu laser. Un alt exemplu este comanda “a muta” care
înseamnă lucruri deferite în funcție de obiectul asupra căruia se aplică.
Un caz special este implementarea unui tip de polimorfism printr-un proces numit
supraîncărcare. Se pot implementa diferite metode care au același nume, pentru un obiect, iar
181 Programarea Orientată pe Obiecte – prezentare generală

obiectul poate spune apoi care este metoda de implementat în funcție de contextul mesajului
(cu alte cuvinte, funcție de numărul și tipul de argumente).
Definiție. În programare, polimorfism înseamnă că o funcție poate fi folosită cu argumente
care la momentul apelării pot fi de fiecare dată de tipuri diferite, eventual în număr diferit.
Observație. Din perspectiva POO, polimorfismul înseamnă utilizarea aceleiași interfețe cu
tipuri diferite de date (forme).
Un exemplu clasic de polimorfism în programare este cel al unei metode/funcții de colorare
prin care se pot colora cu diferite culori diferite forme (cerc, patrat, triunghi, etc.).
Exemplul 11.11 # o funcție polimorfică “built-in”
# len() se poate utiliza pentru un sir
print(len("dimensiune"))
# len() se poate utiliza pentru o lista
print(len([10, 20, 30]))

Output
10
3

Un alt exemplu de polimorfism este utilizarea metodelor cu același nume pentru clase diferite.
Mai jos se prezintă un program în care apar două clase utilizate în același mod. Din fiecare
clasă se construiește cîte un obiect. O buclă for parcurge cele două obiecte. Apoi se apelează
metodele care există cu același nume în fiecare clasă.
Exemplul 11.12 # metode cu același nume pentru clase diferite
class Fac_Inginerie():
def specializari(self):
print("Electronica, Mecanica, Automatica")
def durata(self):
print("Licenta 4 ani, master 2 ani")
def exam_admitere(self):
print("Examen de admitere la matematica, fizica")
class Fac_Medicina():
def specializari(self):
print("MGenerala, Stomatologie, Pediatrie")
def durata(self):
print("Durata studii 6 ani")
def exam_admitere(self):
print("Examen de admitere la anatomie, chimie")
Ing = Fac_Inginerie()
Med = Fac_Medicina()
for facultate in (Ing, Med):
facultate.specializari()
facultate.durata()
facultate.exam_admitere()

Output
Electronica, Mecanica, Automatica
Licenta 4 ani, master 2 ani
Examen de admitere la matematica, fizica
MGenerala, Stomatologie, Pediatrie
Durata studii 6 ani
Examen de admitere la anatomie, chimie
182 Programarea Orientată pe Obiecte – prezentare generală

Suprascrierea metodelor
Polimorfismul poate fi și în strânsă legătură cu conceptul de moștenire. Polimorfismul oferă
posibilitatea definirii de metode în clasa copil, la care se atribuie același nume ca în clasa
părinte.
Prin moștenire, clasa copil moștenește metodele din clasa părinte. Totuși, este posibil să se
modifice o metodă din clasa copil care este moștenită din clasa părinte. Acest lucru este dorit
când metoda moștenită nu corespunde integral necesităților din clasa copil.
Se spune că, în acest caz, se re-implementează metoda în cadrul clasei copil. Procesul de re-
implementare a unei metode în clasa copil se numește “suprascrierea metodei” (method
overrriding).
Exemplul 11.13 #Suprascrierea unei metode
class Vehicul:
def general(self):
print("Cele mai multe vehicule se misca pe roti")
def miscare(self):
print("Miscarea rotilor deplaseaza vehiculul")
class Elicopter(Vehicul):
def miscare(self):
print("Elicopterul zboara datorita rotirii elicei")
class Vapor(Vehicul):
def miscare(self):
print("Vaporul se deplaseaza plutind pe apa")
ob_vehicul = Vehicul()
ob_elicopt = Elicopter()
ob_vapor = Vapor()
ob_vehicul.general()
ob_vehicul.miscare()
ob_elicopt.general()
ob_elicopt.miscare()
ob_vapor.general()
ob_vapor.miscare()

Output
Cele mai multe vehicule se misca pe roti
Miscarea rotilor deplaseaza vehiculul
Cele mai multe vehicule se misca pe roti
Elicopterul zboara datorita rotirii elicei
Cele mai multe vehicule se misca pe roti
Vaporul se deplaseaza plutind pe apa

Observație. POO face ca programele să fie mai ușor de înțeles și mai eficiente. Prin
abstracțiunea datelor, acestea devin mai sigure și mai protejate. Polimorfismul permite aceeași
interfață pentru diferite obiecte, astfel că programatorii pot să scrie cod mai eficient. Codul
poate fi reutilizat.

11.8 Întrebări, exerciții și probleme


11.8.1 POO utilizează termenul “data hiding” (ascunderea datelor). Care este principalul mijloc
prin care se realizează acest lucru și care este principalul avantaj ? Ce se poate spune în acest
sens despre programarea procedurală (PP) ?
R11.8.1Ascunderea datelor se face prin intermediul mecanismelor de restricționare a accesului
la datele protejate și datele private (mai precis accesul la aceste date poate fi făcut doar cu
183 Programarea Orientată pe Obiecte – prezentare generală

îndeplinirea anumitor condiții). Principalul avantaj este securitatea datelor. Programarea


procedurală nu oferă nici o cale de protecție a datelor în cadrul limbajului (Pascal, C, etc.),
fiind mai puțin sigură.
11.8.2 Termenii “date abstracte” și “încapsulare” sunt utilizați frecvent ca sinonime
(interschimbabili). În ce măsură este adevărat acest lucru ?
R11.8.2 Abstracțiunea datelor este utilizată pentru a ascunde detaliile interne ale datelor și a
arăta doar funcționalitatea lor. Acest proces are loc prin intermediul încapsulării care ascunde
datele și permite accesul la acestea doar prin mijlocirea unor metode (funcții), singurele care
le pot oferi o funcționalitate.
11.8.3 Puteți oferi câteva diferențe între POO și programarea procedurală (PP)?
R11.8.3 În tabelul următor sunt prezentate câteva diferențe între POO și PP.

POO PP
1 Calculul se face prin intermediul Calculul se face pe baza unei liste de
obiectelor. instrucțiuni, pas cu pas.
2 Dezvoltarea și întreținereaÎn PP întreținerea programelor devine dificilă
programelor este mai ușoară și costisitoare dacă programele se măresc
3 POO simulează entități din lumea Nu se simulează lumea reală, rezolvarea
reală. Rezolvarea problemelor apare problemelor se face prin algoritmi abstracți.
mai concretă și mai simplă Aceștia sunt implementați pas cu pas, în
secvențe denumite funcții.
4 POO mărește securitatea datelor În PP datele pot fi accesate cu ușurință de
(date protejate și private) oriunde.
5 POO încurajează reutizabilitatea și PP nu are echivalent pentru conceptul de
scurtarea codului prin mecanisme ca moștenire
moștenirea
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 12

Clase și obiecte în Python


12.1 Definirea și crearea claselor și obiectelor
Python este un limbaj de programare orientat către obiecte.
Un obiect este o simplă colecție de date (variabile) și metode (funcții) aplicate datelor colecției.
Clasa este un prototip sau un șablon pentru obiecte. Pe baza clasei se pot crea obiectele prin
procesul denumit instanțiere.
Definirea unei clase începe cu cuvântul cheie class.
Funcțiile constructor, destructor și de inițiere ale unei clase
Constructorul este în Python o funcție specială cu denumirea __new__() care este automat
executată atunci când un obiect al clasei este instanțiat (creat).
După crearea noului obiect cu funcția constructor __new__(), este apelată metoda specială cu
denumirea __init__() pentru inițializarea datelor obiectului nou creat.
La terminarea programului se apelează de asemenea automat funcția specială pentru
distrugerea obiectului și eliberarea resurselor denumită __del__(). Destructorul poate fi
invocat și manual, dar de regulă acest lucru nu este necesar deoarece Python are un sistem
“garbage collector” foarte eficient, care funcționează în fundal.
Funcția cea mai solicitată și cea mai vizibilă este __init__(). Aceasta are întotdeauna drept
prim argument parametrul self, care este o referință la obiectul însuși.
Observație. Spre deosebire de alte limbaje (C++) se face distincție între funcția de creare a
noului obiect și ințializarea datelor. În C++, constructorul îndeplinește ambele sarcini.
Observație importantă: Definirea unei clase înseamnă și crearea unui obiect cu același nume.
Acest obiect clasă permite atât accesul la anumite atribute ale obiectului clasă, cît și instanțierea
unor noi obiecte pornind de la această clasă.
O clasă creează un spațiu de nume local (namespace) în care pot fi definite atribute care pot fi
date sau funcții.
Atributele care încep cu dublu underscore ( __ ) au o semnificație specială (unele sunt
date/variabile, unele sunt metode).
De exemplu, __doc__ este un atribut (variabilă) care întoarce docstring-ul clasei, iar
__sizeof__() este metoda care obține dimensiunea de memorie ocupată de clasă.

Exemplul 12.1 #funcția __init__() inițializează datele clasei


class Test:
def __init__(self, num):
print("Se va crea/construi/instantia un obiect cu numele Test")
self.num = num
print("Valoarea variabilei este :", num)
S = Test(100)

Output
Se va crea/construi/instantia un obiect cu numele Test
185 Clase și obiecte în Python

Valoarea variabilei este : 100

Exemplul 12.2 #funcția __init__() nu apare explicit


class Persoana:
"Clasa contine datele unei persoane"
nume = "Mihai"
varsta = 26
def salut(self):
print('Buna ziua Mihai!')
Pers1 = Persoana()
type(Pers1)
print(Persoana.nume) #atribut al obiectului clasa
print(Persoana.__doc__) #atribut al obiectului clasa
print(Persoana.salut) #atribut al obiectului clasa
print(Pers1.salut()) #atribut al obiectului instantiat Pers1

Output
<class '__main__.Persoana'>
Mihai
Clasa contine datele unei persoane
<function Persoana.salut at 0x00000258D1342678>
Buna ziua Mihai!

În exemplul de mai sus, după crearea obiectului clasă, s-a arătat că acesta poate fi utilizat pentru
accesarea diferitelor sale atribute.
De asemenea, obiectul clasă poate fi utilizat pentru crearea de noi obiecte (instanțe) ale clasei.
În program, această acțiune este foarte asemănătoare cu apelul funcțiilor.
>>> Pers2 = Persoana() #Un nou obiect
>>> print(Pers2.salut())
În secvența de mai sus s-a instanțiat un obiect denumit Pers2. Apoi s-a accesat un atribut al
acestuia folosindu-i numele ca prefix.
Atributele pot fi date sau metode. Metodele obiectului instanțiat corespund funcțiilor clasei
respective.
Observație. În clase se vorbește de “funcții”, în obiectele care sunt instanțe ale clasei
corespondente denumirea este de “metode”.
Atributele definite în cadrul clasei sunt denumite “atributele clasei”, iar atributele definite în
cadrul funcțiilor sunt denumite “atributele instanței”. Atributele clasei pot fi accesate de clasa
însăși (nume_clasă.nume_atribut), ca și de instanțele clasei (instanță.nume_atribut). Astfel,
instanțele au acces la ambele atribute: ale clasei și ale instanței.
Un atribut al clasei poate fi suprascris într-o instanță, dar nu este o metodă bună (de a utiliza
încapsularea).
Parametrul self din definiția funcției din interiorul clasei este asemănător pointerului THIS
din limbajul C++ și trebuie să fie prezent obligatoriu (def salut(self)).
Totuși, la apelul metodei obiectului, parametrul self se poate omite și se poate scrie
Pers1.salut(). Acest lucru este posibil deoarece la apelul metodei, această referire la însuși
obiectul este făcută implicit, astfel încât Pers1.salut() se transformă în
Persoana.salut(self).
186 Clase și obiecte în Python

În general, la apelul unei metode cu o listă de n argumente, acesta se transformă într-un apel
echivalent la o funcție cu o listă de n+1 argumente prin inserția unui argument (self) referind
obiectul. Acest lucru nu se mai întâmplă dacă primul argument are denumirea self.
În concluzie, primul argument al funcției în cadrul clasei trebuie să fie însuși obiectul. Acesta
este denumit convențional self.
Observație. Se poate utiliza o denumire diferită de self, dar se recomandă cu insistență
păstrarea convenției de denumire self.
Observație. Atributele (înțelegând atât atributele propriu-zise, cât și funcțiile/metodele unei
clase/obiect) se pot obține în Python cu comanda dir(nume_clasă/obiect). Explicitarea
faptului că un nume din lista obținută este atribut se face cu comanda
hasattr(nume_clasă/object,"atribut/method").

De asemenea lista de atribute a unui modul se poate face tot cu comanda dir(nume_modul),
iar specificarea faptului ca un modul are un anumit atribut se face cu comanda
hasattr(module_name, "attr_name").

Exemplul 12.3 #Afișarea atributelor și metodelor unei clase/obiect


>>>import math
>>>dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos',
'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb',
'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp',
'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma',
'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt',
'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan',
'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin',
'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'ulp']

>>>hasattr(math, "log")
True

>>>b=3+7j #acesta este un numar complex


>>>type(b)
<class 'complex'>

>>>dir(b) #pentru dir(3+7j) se va obtine acelasi rezultat


['__abs__', '__add__', '__bool__', '__class__', '__delattr__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__le__', '__lt__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__',
'__pow__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmul__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__',
'__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__',
'conjugate', 'imag', 'real']

>>>b.imag #imag este un atribut (variabilă)


7.0
>>>b.__mul__ #__mul__ este o metoda
<method-wrapper '__mul__' of complex object at 0x0000022F0F8FD290>

>>>b.__mul__(5) #__mul__() este o metoda


(15+35j)

Dacă se face referire la clasa/obiectul Persoana, definită în Exemplul 12.2, se obține:

>>>dir(Pers1)
187 Clase și obiecte în Python

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',


'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'nume', 'salut',
'varsta']
>>>Pers1.__sizeof__()
32
>>>Pers1.varsta #atribut de tip data/variabila
26
>>>Pers1.salut
<bound method Persoana.salut of <__main__.Persoana object at
0x0000022F0F8FBBE0>>
>>>Pers1.salut() #atribut de tip metoda
Buna ziua!

Observație. Se observă că între atributele definite de utilizator nu se poate face o diferențiere


printr-un marcaj special între atributele propriu-zise (datele) și metode. Datalii despre metodele
unui obiect se mai pot obține utilizând comanda help(object.method). Făcând referire la
aceeași clasă Persoana, se obține:
>>>help(p.salut)
Help on method salut in module __main__:
salut() method of __main__.Persoana instance
>>>help(p.nume)
No Python documentation found for 'Mihai'.

Se constată că s-a obținut o precizare utilă doar în cazul metodei (că atributul este o metodă).
Autodocumentarea claselor. Primul șir în interiorul unei clase este denumit docstring și
reprezintă o scurtă descriere a clasei. Deși nu este obligatoriu, este recomandat ca prezența
docstring să devină o regulă.
Exemplul 12.4 #clasa și docstringul
class Xclasa:
'''Acesta este un docstring. Rolul sau
este sa descrie Xclasa'''
pass

12.2 Ștergerea obiectelor și atributelor


Un obiect se poate șterge cu instrucțiunea del.
Exemplul 12.5 #Ștergerea unui obiect cu del
>>> c1 = complex(2,51)
>>> c1
(2+51j)
>>> del c1
>>> c1
Traceback (most recent call last):
. . . . . . . . . . .
NameError: name 'c1' is not defined

Instrucțiunea del poate fi utilizată și pentru ștergerea atributelor unui obiect, dar nu este
întotdeauna posibil, unele atribute fiind read-only (pot fi doar citite).
Exemplul 12.6 #nu este posibilă ștergerea atributului
>>> c1 = complex(3,1)
188 Clase și obiecte în Python

>>> print(c1.imag)
1.0
del(c1.imag)
Traceback (most recent call last):
. . . . . . . . . . . .
del(c1.imag)
AttributeError: readonly attribute

Exemplul 12.7 #este posibilă ștergerea atributului


class A:
def __init__(self, x, y):
self.x = x
self.y = y
A1 = A(2, 3)
print(A1.x, A1.y)
print('inainte de stergerea atributului obiectului A1')
rezultat = hasattr(A1, 'x')
print(f'A1 contine x? {rezultat}')
rezultat = hasattr(A1, 'y')
print(f'A1 contine y? {rezultat}')
del(A1.x) #stergerea atributului
print(A1.x, A1.y)

Output
2 3
inainte de stergerea atributului obiectului A1
A1 contine x? True
A1 contine y? True
Traceback (most recent call last):
. . . . . . . . . . . .
print(A1.x, A1.y)
AttributeError: 'A' object has no attribute 'x'

De asemenea, atributele unor obiecte se mai pot șterge prin funcția built-in delattr().

12.3 Încapsularea datelor


Diverse limbaje orientate pe obiecte cum ar fi C++, Java, Python, etc. controlează și
restricționează accesul la variabilele și metodele clasei.
Majoritatea limbajelor de programare au trei forme de modificatori de acces într-o clasă, aceștia
fiind Public, Protected și Private.
Python folosește simbolul „_” pentru a determina controlul accesului la un anumit membru de
date, sau o funcție membră a unei clase. Specificatorii de acces în Python au un rol important
în securizarea datelor, împotriva accesului neautorizat și în prevenirea exploatării nedorite a
acestora.
Observație. Deoarece Python nu are nici un mecanism (parte a limbajului, așa cum există în
C++, cuvintele cheie public, protected, sau private) care să restricționeze efectiv accesul la o
variabilă sau metodă, utilizarea prefixului underscore simplu “_” (membri de tip protected)
sau dublu underscore ”__” (membri de tip private) este o convenție pentru a emula
comportamentul specificatorilor de acces.
Cele trei tipuri de modificatori de acces utilizați într-o clasă Python sunt:
189 Clase și obiecte în Python

A. Modificator de acces public


Membrii unei clase care sunt declarați publici sunt ușor accesibili din orice parte a programului.
Toți membrii de date și funcțiile membre ale unei clase sunt publici în mod implicit.
Exemplul 12.8 #access public la date și metode intr-o clasă
class persoana:
def __init__(self, nume, varsta):
# date publice
self.persNume = nume
self.persVarsta = varsta
def afisare_varsta(self): # functie publica
print("Varsta: ", self.persVarsta) # accesare date publice
ob_pers = persoana("Tudor", 20) #creare obiect/instantiere clasa
print("Nume: ", ob_pers.persNume) #accesare date publice
ob_pers.afisare_varsta() #apelarea unei functii publice a clasei

Output
Nume: Tudor
Varsta: 20

În programul de mai sus, persNume și persVarsta sunt membri publici ai datelor, iar metoda
afisare_varsta() este o funcție membru public al clasei persoana. Acești membri date ai
clasei persoana pot fi accesați de oriunde în program.
B. Modificator de acces protejat
Membrii unei clase care sunt declarați protejați sunt accesibili numai unei clase derivate din
aceasta. Membrii date ai unei clase sunt declarați protejați prin adăugarea unui singur simbol
de subliniere (underscore) “_” înaintea numelor.
Exemplul 12.9 #access protejat la date și metode intr-o clasă
class Salariat: # clasa parinte
_nume = None # data protejata
_marca = None # data protejata
_sectia = None # data protejata
def __init__(self, nume, marca, sectia):
self._nume = nume
self._marca = marca
self._sectia = sectia
def _afisare_marca_sectia(self): # functie protejata
# accessare date protejate
print("Marca: ", self._marca)
print("Sectia: ", self._sectia)

class persoana(Salariat): # clasa derivata


def __init__(self, nume, marca, sectia):
Salariat.__init__(self, nume, marca, sectia)

def afisare_detalii(self): # functie publica


# accesare date protejate ale clasei parinte
print("Nume: ", self._nume)
# accesare functie protejata a clasei parinte
self._afisare_marca_sectia()
# creare obiect al clasei derivate/copil
ob_pers = persoana("Tudor", 12345, "Sectia motoare")
ob_pers.afisare_detalii() # apel public al functiei

Output
190 Clase și obiecte în Python

Nume: Tudor
Marca: 12345
Sectia: Sectia motoare
În programul de mai sus, _nume, _marca și _sectia sunt membri date protejați, iar metoda
__afisare_marca_sectia() este o metodă protejată a super-clasei Salariat. Metoda
afisare_detalii() este o funcție publică membră a clasei persoana care este derivată din
clasa Salariat, metoda afisare_detalii din clasa persoana accesează membrii de date
protejați ai clasei Salariat.
C. Modificator de acces privat
Membrii unei clase care sunt declarați privați sunt accesibili numai în cadrul clasei.
Modificatorul de acces privat este cel mai sigur modificator de acces. Membrii date ai unei
clase sunt declarați privați prin adăugarea unui simbol dublu de subliniere (underscore) “__”
înaintea numelui datei din acea clasă.
Exemplul 12.10 #access privat la date și metode intr-o clasă
class persoana:
__nume = None #date private
__marca = None #date private
__sectia = None #date private
def __init__(self, nume, marca, sectia):
self.__nume = nume
self.__marca = marca
self.__sectia = sectia
def __afisare_detalii(self): #functie privata
# acces la date private
print("Nume: ", self.__nume)
print("Marca: ", self.__marca)
print("Sectia: ", self.__sectia)
def func_acces_privat(self): #functie publica
self.__afisare_detalii() #acces la functie privata
# creare obiect
ob_pers = persoana("Tudor", 12345, "Sectia motoare")
# apelul functiei publice
ob_pers.func_acces_privat()

Output
Nume: Tudor
Marca: 12345
Sectia: Sectia motoare

În programul de mai sus, __nume, __marca și __sectia sunt membri date privați, metoda
__ afisare_detalii este o funcție membru privat (acestea pot fi accesate numai în cadrul
clasei) și metoda func_acces_privat() este o funcție publică membru a clasei persoana care
poate fi accesată de oriunde în cadrul programului. Metoda func_acces_privat accesează
membrii privați ai clasei persoana.

12.4 Moștenirea
Definiție. Moștenirea se referă la crearea unei noi clase pornind de la o clasă existentă care nu
se modifică. Clasa nouă se numește clasă copil (sau derivată), iar clasa de la care moștenește
se numește clasă părinte (sau de bază).
Sintaxa
191 Clase și obiecte în Python

class clasaBaza:
Corpul clasei de baza/părinte
class clasaDerivata(clasaBaza):
Corpul clasei derivate
Clasa derivată moștenește caracteristicile clasei de bază, iar alte noi caracteristici sunt adăugate
la cele moștenite, în noua clasă. Practic, apare avantajul reutilizării codului deja scris.
Exemplul 12.8 #moștenirea figurilor geometrice
Se definește poligonul ca fiind o figură geometrică formată din 3 sau mai multe segmente de
dreaptă (laturi) care sunt unite și formează un contur închis.
Clasa denumită Poligon este prezentată în programul de mai jos. Atributele clasei sunt
numărul de laturi n, și o listă cu dimensiunile acestora, denumită laturi.
Metoda input_laturi() citește mărimea fiecărei laturi și metoda afiseaza_latura(self)
imprimă lungimea fiecărei laturi.
Se creează o clasă denumită Triunghi (poligon cu trei laturi) care moștenește clasa Poligon.
În consecință, toate atributele clasei Poligon devin disponibile clasei Triunghi, prezentată de
asemenea în program.
Noua metodă calcAria() a clasei Triunghi calculează și imprimă aria triunghiului.
#datele introduse trebuie sa satisfaca conditia matematica
#necesara pentru a fi laturile unui triunghi (!)
class Poligon:
def __init__(self, nr_laturi):
self.n = nr_laturi
self.laturi = [0 for i in range(nr_laturi)]
def input_laturi(self):
self.laturi = [float(input("Introdu latura "+str(i+1)+" : ")) for i
in range(self.n)]
def afiseaza_latura(self):
for i in range(self.n):
print("Latura",i+1," : ",self.laturi[i])
class Triunghi(Poligon):
def __init__(self):
Poligon.__init__(self,3)
def calcAria(self):
a, b, c = self.laturi
# calculează semi-perimetrul
s = (a + b + c) / 2
aria = (s*(s-a)*(s-b)*(s-c)) ** 0.5
print('Aria triunghiului este %0.2f' %aria)
t = Triunghi()
t.input_laturi()
t.afiseaza_latura()
t.calcAria()

Output
Introdu latura 1 : 6
Introdu latura 2 : 2
Introdu latura 3 : 5
Latura 1 : 6.0
Latura 2 : 2.0
Latura 3 : 5.0
Aria triunghiului este 4.68
192 Clase și obiecte în Python

Se observă că deși nu s-au definit metode ca input_laturi()sau afiseaza_latura() pentru


clasa Triunghi,se pot totuși utiliza pentru obiectul instanțiat din această clasă, deoarece dacă
un atribut nu este găsit în clasa derivată, se continuă căutarea în clasa de bază. Căutarea poate
avea loc pe mai multe niveluri succesive de declarare a moștenirii părinte-copil a claselor.

12.5 Suprascrierea metodelor


În exemplul anterior, metoda __init__() a fost definită în ambele clase, Triunghi() și
Poligon(). În acest caz (metodele au același nume) metoda din clasa derivată suprascrie
metoda din clasa de bază. Altfel spus, __init__() din clasa Triunghi are prioritate asupra
metodei din clasa Poligon.
În general, când se suprascrie o metodă din clasa de bază, se tinde să se mai adauge ceva la
clasa de bază, aceasta extinzându-se în clasa derivată și devenind diferită de cea de bază. Dacă
totuși se dorește să se apeleze metoda din clasa de bază, acest lucru este posibil prin calificare:
se apelează Poligon.__init__() în loc de __init__() în clasa Triunghi.
Observație. O opțiune mai bună
este utilizarea funcției super(). Astfel,
super().__init__(3) este echivalentă cu Poligon.__init__(self,3), fiind varianta
recomandată.
Pentru verificarea moștenirii sunt utilizate două funcții de tip in-built: isinstance() și
issubclass().

Funcția isinstance() întoarce True dacă obiectul este o instanță a clasei sau altă clasă
derivată din ea. Toate clasele din Python moștenesc clasa de bază object.
Exemplul 12.9 #Verificarea calității de instanță a clasei, sau de clasă derivată
# t este o instanță a claei triunghi, creată în exemplul anterior
>>> isinstance(t,Triunghi)
True
>>> isinstance(t,Poligon)
True
>>> isinstance(t,int)
False
>>> isinstance(t,object)#object este clasa de baza de prim nivel in Python
True

Asemănător, issubclass() este utilizată pentru verificarea moștenirii unei clase.


Exemplul 12.10 #Verificarea calității de clasă derivată (moștenitoare a unei clase)
>>> issubclass(Poligon,Triunghi)
False
>>> issubclass(Triunghi,Poligon)
True
>>> issubclass(bool,int)
True

12.6 Moștenirea multiplă


Definiție. O clasă poate fi derivată din mai multe clase. Aceasta se numește moștenire multiplă.
În cazul moștenirii multiple, clasa derivată moștenește caracteristici din mai multe clase.
Exemplul 12.11 # clasă derivată din două clase
class Baza_A:
193 Clase și obiecte în Python

pass
class Baza_B:
pass
class Multi_Derivata(Baza_A, Baza_B):
pass

Clasa Multi_Derivata este derivată din clasele Baza_A și Baza_B și moștenește atribute din
ambele clase.

12.7 Moștenirea multinivel


Definiție. Moștenirea se poate face și dintr-o clasă derivată. În acest caz, caracteristicile clasei
de bază și clasei derivate sunt moștenite de o nouă clasă derivată. Acest tip de moștenire este
denumită moștenirea multinivel și poate avea loc pe oricâte niveluri în Python.
Exemplul 12.12 # clase derivate pe mai multe niveluri
class Baza:
pass
class Derivata1(Baza):
pass
class Derivata2(Derivata1):
pass

Aici, clasa Derivata1 moștenește clasa Baza, iar clasa Derivata2 moștenește clasa Derivata1.

12.8 Ordinea apelării metodelor (MRO)


Toate clasele din Python sunt derivate din clasa object. Aceasta este clasa de bază de prim
nivel în Python. În consecință, toate clasele, fie de tip built-in, sau definite de utilizator, sunt
clase derivate și toate acestea sunt instanțe derivate ale clasei object.
Exemplul 12.13 #verificarea calității de subclasă a clasei object
print(issubclass(list,object))
print(isinstance(5.5,object))
print(isinstance("Salut!",object))

Output
True
True
True

În cazul unei secvențe de cod care presupune moștenire multiplă, atributele se caută mai întâi
în clasa curentă. Dacă acestea nu sunt găsite, se continuă căutarea în clasele părinte de același
nivel, în ordine de la stânga la dreapta, evitând căutarea repetată în aceeași clasă.
În exemplul anterior referitor la clasa Multi_Derivata, ordinea căutării este:
Multi_Derivata, Baza_A, Baza_B, object. Această ordonare este denumită liniarizarea
clasei Multi_Derivata, iar setul de reguli utilizate pentru a o determina se numește Ordinea
Apelării (Rezolvării) Metodei, Method Resolution Order (MRO).
MRO trebuie să garanteze că o clasă se situează întotdeauna înaintea părinților în ce privește
căutarea metodelor în vederea apelării. În cazul părinților multipli, ordinea este aceeași ca în
tuplul claselor de bază.
194 Clase și obiecte în Python

MRO al unei clase poate fi vizualizat ca un atribut cu numele __mro__ , sau ca metoda mro().
Atributul întoarce un tuplu, iar metoda întoarce o listă.
Exemplul 12.14 #afișare ordine de apelare folosind atributul __mro__ , sau metoda mro()
>>> Multi_Derivata.__mro__
(__main__.Multi_Derivata, __main__.Baza_A, __main__.Baza_B, object)

>>> MultiDerivata.mro()
[__main__.Multi_Derivata, __main__.Baza_A, __main__.Baza_B, object]

12.9 Supraîncărcarea operatorilor


12.9.1 Conceptul de supraîncărcare a unui operator

În POO se poate schimba semnificația unui operator în funcție de modul de utilizare.


De regulă, operatorii lucrează în Python pentru clasele/obiectele preconstruite (built-in),
reamintindu-se cu această ocazie că în Python totul este obiect. Dar unii operatori se comportă
diferit cu tipuri diferite. De exemplu, operatorul + poate să adune două numere (2 + 3), sau să
concateneze două șiruri ('abc' + 'def').
Definiție. Prin supraîncărcarea operatorilor (operator overloading) se înțelege capacitatea
operatorilor de a avea o semnificație diferită, în funcție de contextul în care sunt utilizați.
Un aspect ce trebuie analizat este acela al utilizării unui operator cu clase definite de utilizator.
Pentru analiză se ia in considerație un exemplu în care operanzii cărora li se aplică un operator
sunt puncte intr-un plan 2-D, în sistemul de coordonate cartezian.
Exemplul 12.15 #clasa Punct căreia i se va aplica un operator supraîncărcat
class Punct:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
p1 = Punct(1, 2)
p2 = Punct(2, 3)
print(p1+p2)

Output
Traceback (most recent call last):
. . .
print(p1+p2)
TypeError: unsupported operand type(s) for +: 'Punct' and 'Punct'

Rezultatul rulării secvenței de program de mai sus este ridicarea excepției TypeError,
deoarece, în acest moment, Python nu știe să adune sau să facă alte operații cu obiecte de tip
Punct. Totuși este posibil ca Python să poată îndeplini acest lucru prin supraîncărcarea
operatorilor.
În vederea dezvoltării acestui subiect, trebuie să se cunoască unele aspecte despre funcțiile
speciale.
12.9.2 Supraîncărcarea utilizând funcții speciale
Funcțiile (din clase) care încep cu dublu underscore ( __ ) sunt denumite funcții speciale în
Python, deoarece diferă de funcțiile tipice. De exemplu, funcția__init__() este una dintre
195 Clase și obiecte în Python

funcțiile speciale. O caracteristică ce o face specială este faptul că este apelată de fiecare dată
când se creează un nou obiect din acea clasă.
Există multe alte funcții speciale: __del__, __str__, __format__, __hash__, __bool__,
etc.

Prin utilizarea funcțiilor speciale, se poate face clasa definită anterior (Punct) compatibilă cu
funcțiile built-in.
Dacă se dorește să se imprime cu funcția print() coordonatele unui obiect Punct, se obține
un eșec, după cum se vede în exemplul următor.
Exemplul 12.16
>>>p1 = Punct(2,3)
>>>print(p1)

Output
<__main__.Punct object at 0x000001DBC16F1

Pentru rezolvarea problemei, se poate defini o metodă __str__() în clasa Punct pentru
controlul modului de printare. După cum urmează, dacă acum se reîncearcă execuția funcției
print(), se obține ce se dorește.

Exemplul 12.17 #suprascrierea metodei __str__()pentru clasa Punct


class Punct:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "({0}, {1})".format(self.x, self.y)
p1 = Punct(2, 3)
print(p1)

Output
(2, 3)
Răspunsul este cel așteptat. Se poate incerca acum invocarea funcțiilor str() sau format().
În acest caz, utilizând str(p1) sau format(p1), se apelează intern metoda p1.__str__().
Exemplul 12.18 #apelarea internă a metodei __str__()
>>>str(p1)
'(2, 3)'
>>>format(p1)
'(2, 3)'

Pentru a obține rezultatul operației Punct(p1 + p2), mai trebuie făcut ceva, după cum se va
vedea în secțiunea următoare.

12.9.3 Supraîncărcarea operatorului + și a altor operatori


Pentru a supraîncărca operatorul +, este necesar să se implementeze funcția __add__() în
cadrul clasei. În interiorul funcției se poate pune un return care intoarce suma pentru fiecare
coordonată în parte.
Exemplul 12.19 #supraîncărcarea operatorului “sumă” (+)
class Punct:
def __init__(self, x=0, y=0):
196 Clase și obiecte în Python

self.x = x
self.y = y
def __str__(self):
return "({0},{1})".format(self.x, self.y)
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Point(x, y)
p1 = Punct(4, 5)
p2 = Punct(3, 6)
print(p1+p2)

Output
(7,11)

Când argumentul funcției este p1 + p2, se face apelul p1.__add__(p2) care la rândul său
este transformat în Punct.__add_(p1,p2). După aceasta, operația de adunare contiunuă în
modul specificat.
Ca exemplu suplimentar se prezintă în continuare supraîncărcarea unui operator de comparație
( < ), cu ilustrare pentru cazul clasei Punct.
Exemplul 12.20 # supraincarcarea operatorului “mai mic” (<)
class Punct:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "({0},{1})".format(self.x, self.y)
def __lt__(self, alt):
self_modul = (self.x ** 2) + (self.y ** 2)
alt_modul = (alt.x ** 2) + (alt.y ** 2)
return self_modul < alt_modul
p1 = Punct(1,1)
p2 = Punct(-2,-3)
p3 = Punct(1,-1)
# utilizarea operatorului supraincarcat (<)
print(p1<p2)
print(p2<p3)
print(p1<p3)

Output
True
False
False

Supraîncărcarea operatorilor poate fi făcută și pentru alte categorii de operatori, nu numai


pentru cei de adunare sau comparație. Și alți operatori se supraîncarcă în același mod. Tabelul
cu funcțiile speciale care trebuie implementate este prezentat mai jos.
Tabelul 12.1 Funcții speciale utilizate la supraîncărcarea operatorilor
Operator Expresia Metoda internă
Adunare p1 + p2 p1.__add__(p2)
Scădere p1 - p2 p1.__sub__(p2)
Înmulțire p1 * p2 p1.__mul__(p2)
Putere p1 ** p2 p1.__pow__(p2)
197 Clase și obiecte în Python

Împărțire p1 / p2 p1.__truediv__(p2)
Împ. cu rot. infer. p1 // p2 p1.__floordiv__(p2)
Rest (modulo) p1 % p2 p1.__mod__(p2)
Bitwise Left Shift p1 << p2 p1.__lshift__(p2)
Bitwise Right Shift p1 >> p2 p1.__rshift__(p2)
Bitwise AND p1 & p2 p1.__and__(p2)
Bitwise OR p1 | p2 p1.__or__(p2)
Bitwise XOR p1 ^ p2 p1.__xor__(p2)
Bitwise NOT ~p1 p1.__invert__()
Mai mic p1 < p2 p1.__lt__(p2)
Mai mic sau egal p1 <= p2 p1.__le__(p2)
Egal p1 == p2 p1.__eq__(p2)
Not egal p1 != p2 p1.__ne__(p2)
Mai mare p1 > p2 p1.__gt__(p2)
Mai mare sau egal p1 >= p2 p1.__ge__(p2)

12.10 Întrebări, exerciții și probleme


12.10.1 Care dintre următoarele variante este utilizată pentru a crea un obiect?
A. O clasă B. O funcție C. O metodă D. Un constructor
R12.10.1 Varianta D
12.10.2 Care este diferența dintre construcția, distrugerea și inițializarea unui obiect în Python?
R12.10.2 În Python, constructorul este o funcție specială automat executată atunci când un
obiect este instanțiat. Denumirea funcției constructor este __new__(). Constructorul nu
utilizează parametrul self deoarece la apelul său obiectul încă nu există.
Destructorul este o funcție specială cu denumirea __del__() care este apelată când un obiect
nu mai este necesar și resursele sale trebuie să fie eliberate. În Python, de regulă nu este nevoie
să se apeleze manual destructorii (ca în C++), deoarece funcția de “garbage collection” face
automat acest lucru, foarte eficient. Destructorul mai este apelat automat la terminarea
programului.
Funcțiile speciale constructor și destructor sunt apelate foarte rar în Python.
Puthon dispune de o metodă specială denumită __init__() prin care se inițializează datele la
momentul creerii obiectelor (În C++ atât crearea cât și inițializarea sunt făcute prin intermediul
constructorului).
Atât funcția destructor cît și funcția de inițializare invocă parametrul self.
Observație. În foarte multe lucrări și aplicații Python, funcția de inițializare __init__() este
denumită abuziv ”constructor”.
12.10.3 Care este diferența dintre atributele unei clase și cele ale unei instanțe? Oferiți un
exemplu.
R12.10.3 Se reamintește că instanțele unei clase sunt denumite “obiecte”. Pe de altaă parte, în
Python, clasa însăși este un obiect.
Diferențele dintre atributele unei clase și cele ale unei instanțe sunt cele specificate în următorul
tabel:
198 Clase și obiecte în Python

Atributul clasei Atributul instanței


1 Este definit direct în interiorul Este definit în interior prin intermediul
clasei unui constructor prin utilizarea
parametrului self
2 Este preluat de toate instanțele Este specific instanței
clasei
3 Se accesează utilizând numele Se accesează utilizând notația cu punct a
clasei sau numele instanței cu obiectelor. De exemplu:
notația “punct”. De exemplu, obiect.instanța_atribut
clasa_nume.clasa_atribut
sau obiect.clasa_nume
4 Schimbarea valorii prin Schimbarea valorii unui atribut al unei
clasa_nume.clasa_atribut = instanțe nu se refllectă în toate obiectele
valoare_noua se referă la toate
obiectele.

Exemplu. Fie clasa “salariat”.


class Salariat:
marca = 0
def __init__ (self):
Salariat.marca += 1

În exemplul de mai sus , marca este un atribut al clasei Salariat. Când se creează un nou
obiect Salariat, valoarea corespunzătoare atributului marca se mărește cu 1.
Dacă se accesează atributul marca după crearea obiectului, se obține:
>>> electrician = Salariat()
>>> Salariat.marca
1
instalator = Salariat()
>>> Salariat.marca
2

În continuare se prezintă cazul atributelor instanțelor. Se redefinește clasa anterioară:


class Salariat:
marca = 0
def __init__ (self, nume, varsta):
salariat.marca += 1
self.nume = nume
self.varsta = varsta

La crearea instanțelor, se specifică valorile atributelor instanțelor prin intermediul


constructorului.
>>>electrician = Salariat('Horia', 22)
>>>instalator = Salariat('Petre', 41)
>>>electrician.nume
'Horia'
>>>electrician.varsta
22
>>>electrician.nume = 'Vasile'
>>>electrician.varsta = 32
>>>electrician.nume
'Vasile'
199 Clase și obiecte în Python

>>>electrician.varsta
32

Modificările afectează doar atributul “electrician”. Valorile atributelor nume și vârsta


rămân nemodificate în obiectul instalator.
Se observă că modificarea făcută atributului Salariat.marca afectează conținutul ambelor
obiecte (făcând abstracție de forțarea exemplului):
Salariat.marca =7
electrician.marca
7
instalator.marca
7

12.10.4 Care sunt atributele built-in ale unei clase ? Oferiți un exemplu de utilizare.
R12.10.4 Orice clasă din Python are următoarele atribute built-in care pot fi accesate utilizând
operatorul punct, asemănător oricărui alt atribut:
__dict__ = dicționar conținând spațiul numelor de clasă
__doc__ = șir de documentare, dacă există
__name__ = numele clasei
__module__= numele modulului în care este definită clasa. În modul interactiv, acest atribut
este __main__
__bases__ = lista claselor de bază, ordonate, sau nimic

class Salariat:
'clasa de baza'
marcaSalariat = 0 #numarul de salariati
def __init__(self, nume, salariu):
self.nume = nume
self.salariu = salariu
Salariat.marcaSalariat +=1
def afisareMarca(self):
print("Nr. total de salariati: %d" % Salariat.marcaSalariat)
def afisareSalariat(self):
print("Nume:", self.nume, "Salariu:", self.salariu)
muncitor = Salariat("Tudor", 5000)
Salariat.afisareSalariat(muncitor)
print()
print("Salariat.__doc__:", Salariat.__doc__)
print("Salariat.__name__:", Salariat.__name__)
print("Salariat.__module__:", Salariat.__module__)
print("Salariat.__bases__:", Salariat.__bases__)
print("Salariat.__dict__:", Salariat.__dict__)
print()
print("muncitor.__doc__:", muncitor.__doc__)
#print("muncitor.__name__:", muncitor.__name__) #eroare no attribute
print("muncitor.__module__:", muncitor.__module__)
#print("muncitor.__bases__:", muncitor.__bases__)#eroare no attribute
print("muncitor.__dict__:", muncitor.__dict__)

Output
Nume: Tudor Salariu: 5000
Salariat.__doc__: clasa de baza
Salariat.__name__: Salariat
Salariat.__module__: __main__
Salariat.__bases__: (<class 'object'>,)
200 Clase și obiecte în Python

Salariat.__dict__: {'__module__': '__main__', '__doc__': 'clasa de baza',


'marcaSalariat': 1, '__init__': <function Salariat.__init__ at
0x000002AADFF325F0>, 'afisareMarca': <function Salariat.afisareMarca at
0x000002AADFF32680>, 'afisareSalariat': <function Salariat.afisareSalariat
at 0x000002AADFF32710>, '__dict__': <attribute '__dict__' of 'Salariat'
objects>, '__weakref__': <attribute '__weakref__' of 'Salariat' objects>}
muncitor.__doc__: clasa de baza
muncitor.__module__: __main__
muncitor.__dict__: {'nume': 'Tudor', 'salariu': 5000}

12.10.5 Să se scrie un program de creare și actualizare simplă a unei liste de articole


(adaugare, stergere, afisare) folosind tipul clasă.
R12.10.5
class act_lista: #clasa de creare si actualizare simpla a unei liste
def __init__ (self): #creare lista de articole vida
self.art =[]
def adauga(self, a1): #metoda adaugare in lista
return self.art.append(a1)
def sterge(self, a2): #metoda stergere articol din lista
return self.art.remove(a2)
def afiseaza(self): #metoda afiseaza articol din lista
return (self.art)
ob = act_lista() #instantiere clasa-creare obiect

optiune = 1
while optiune != 0:
print("0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista")
print()
optiune = int(input("Optiune="))

if optiune == 1:
a1 = input("Adaugati un articol:")
ob.adauga(a1)
print("Continut lista:", ob.afiseaza())
elif optiune == 2:
a2 = input("Stergeti un articol:")
ob.sterge(a2)
print("Continut lista:", ob.afiseaza())
elif optiune == 3:
print("Continut lista:", ob.afiseaza())
elif optiune == 0:
print("Terminat")
else:
print("Optiune gresita")

print("Stop program")

Output
0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista

Optiune=1
Adaugati un articol:mere
Continut lista: ['mere']
0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista

Optiune=1
Adaugati un articol:pere
Continut lista: ['mere', 'pere']
201 Clase și obiecte în Python

0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista

Optiune=1
Adaugati un articol:cirese
Continut lista: ['mere', 'pere', 'cirese']
0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista

Optiune=3
Continut lista: ['mere', 'pere', 'cirese']
0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista

Optiune=2
Stergeti un articol:pere
Continut lista: ['mere', 'cirese']
0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista

Optiune=3
Continut lista: ['mere', 'cirese']
0.Terminare 1.Adaugare 2.Stergere 3.Afisare lista

Optiune=0
Terminat
Stop program

12.10.6
Practic, ce se întîmplă în cazul moștenirii fără polimorfism?
R12.10.6 Dacă nu se utilizează polimorfismul, va fi necesară o verificare suplimentară a tipului
obiectului pentru a determina metoda corectă ce va fi apelată. Acest lucru înseamnă un cod mai
mare de scris.
12.10.7 Să se scrie un program care să inverseze ordinea cuvintelor unui șir (alcătuit din mai
multe cuvinte).
R12.10.7 Se utilizează succesiv funcțiile split(), reversed() și join().
class inversare_sir:
def inversare_cuvant(self, s):
return ' '.join(reversed(s.split()))

print(inversare_sir().inversare_cuvant("Curtea de Arges"))

Output
Arges de Curtea

12.10.8 Ce sunt modificatorii de acces în Python? Oferiți un exemplu.


R12.10.8 Modificatorii de acces în Python sunt utilizați pentru a modifica domeniul de
vizibilitate a unei variabile. Practic, aceștia sunt public, protected, private și default (fără
cuvânt cheie). Modificatorii public și default sunt echivalenți. Deoarece Python nu are nici un
mecanism care să restricționeze efectiv accesul la o variabilă sau metodă (cuvinte cheie
specifice de tipul protected, sau private, etc.), se utilizează o convenție constând dintr-un prefix
simplu sau dublu underscore pentru a emula comportamentul specificatorilor de acces
protected și private. Următorul program ilustrează modul de lucru cu modificatorii de acces în
Python într-o ierarhie de clase.
#clasa de baza
class Baza:
#date membre publice
202 Clase și obiecte în Python

date_publice = None
#date membre protejate
_date_protejate = None
#date membre private
__date_private = None
#initializare date la creare obiect
def __init__(self, varpub, varprot, varpriv):
self.date_publice = varpub
self._date_protejate = varprot
self.__date_private = varpriv
#functie membra publica
def afisareMembriPublici(self): #acces la date publice
print("Date publice: ", self.date_publice)
#functie membra protejata
def _afisareMembriProtejati(self): #acces la date protejate
print("Date protejate: ", self._date_protejate)
#functie membra privata
def __afisareMembriPrivati(self): #acces la date private
print("Date private: ", self.__date_private)

#functie publica – acces la membri privati


def accesMembriPrivati(self):
#acces la date private dintr-o functie publica prin
#apelul functiei private (din aceeasi clasa)
self.__afisareMembriPrivati()

#clasa derivata
class derivata(Baza):
#initializare date la creare obiect
def __init__(self, varpub, varprot, varpriv):
Baza.__init__(self, varpub, varprot, varpriv)
#functie publica
def accesMembriProtejati(self):
#acces la functia protejata a clasei de baza
self._afisareMembriProtejati()

#crearea unui obiect al clasei derivate


obj = derivata("Robot Lego", 1600, "Mainstorms")

#obiectul derivat poate apela o functie publica a clasei Baza


#si afisa datele publice
obj.afisareMembriPublici()

obj.accesMembriProtejati()
#obiectul derivat apeleaza o functie publica proprie membra
#si poate accesa date protejate in clasa Baza
print("Objectul acceseaza date protejate:", obj._date_protejate)

obj.accesMembriPrivati()
# objectul apeleaza o functie publica a clasei Baza, dar nu poate accesa
#datele private, rezulta eroare de Atribut
#print(obj.__date_private)

Output
Date publice: Robot Lego
Date protejate: 1600
Date private: Mainstorms
Objectul acceseaza date protejate: 1600

12.10.9 Ce sunt metodele magice ? Exemplificați.


203 Clase și obiecte în Python

R12.10.9 Metodele magice (magic methods) sunt funcții de tip dunder: __init__(sel, alte
arg.), __abs__(self), __round__(self, n), etc. Sunt apelabile indirect (transparent), de
exemplu apelarea operatorului + conduce la apelul metodei sale __add__(self, alte arg).
Denumirea dunder provine de la “double underscore”.
>>>var_nr = 20
>>>var_nr + 10
30
>>>var_nr.__add__(10)
30

12.10.10 Să se scrie un program pentru realizarea unui calculator “științific” care să utilizeze
moștenirea multiplă. Clasele părinte sunt o clasă pentru calcule aritmetice și o clasă pentru
funcții matematice (de exemplu, funcția putere).
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 13

Iteratori
13.1 Definiția și utilizarea iteratorilor
Concept. Iterabilii sunt colecții, în timp ce un iterator gestionează doar iterația (adică păstrează
poziția), dar de regulă nu este o colecție în sine.
Definiție. Iteratorii sunt obiecte pe baza cărora se poate parcurge un număr de pași într-o buclă
de procesare iterativă. Iteratorul în Python este pur și simplu un obiect care va returna date,
câte un element, pe rând.
Iteratorii sunt frecvent întâlniți într-un limbaj de programare. Ei sunt elegant implementați în
cadrul buclei for, comprehensiuni, generatoare, etc., uneori nefiind clar vizibili.
Diferența dintre un iterabil și un iterator este importantă, de exemplu, atunci când cineva
dorește să parcurgă un iterabil (printr-o o secvență de iterații) de mai multe ori. Dacă un iterator
a ajuns la finalul iterabilului, atunci a doua parcurgere nu va mai funcționa, deoarece iteratorul
a fost deja folosit și a ridicat imediat excepția StopIteration .
Observație. Iteratorii în Python nu pot fi resetați. Excepția StopIteration nu poate fi anulată
pentru un iterator, odată ce a apărut. Soluția este construcția unui nou iterator (sau generator).
Din punct de vedere tehnic, în Python, un iterator trebuie să implementeze două metode
speciale, __iter__() și __next__(), ambele făcând parte din protocolul iterator.
Un obiect este iterabil dacă se poate găsi un iterator pentru el. Multe containere built-in din
Python ca: liste, tuple, șiruri, etc., sunt iterabile. Pentru ele, funcția iter() (care la rândul său
apelează metoda specială __iter__()) întoarce un iterator.
În modul pas cu pas (“manual”), pentru utilizarea unui iterator cu un iterabil, se va utiliza
funcția next()pentru a itera prin toți termenii iterabilului. Când se va atinge sfârșitul și nu vor
mai fi date de întors, se va ridica excepția StopIteration.
Există obiecte care sunt atât iterabile (conțin metoda __iter__()) cât și iteratori (conțin
metoda __next__()) . Astfel de obiecte (fișiere, dicționare, etc.) suportă o singură iterație la
un moment dat, fiind propriul lor iterator. Listele, în schimb, sunt doar iterabile, permițand
multiple iterații simultan, cu obiecte iterator diferite.
Exemplul 13.1 #iterarea unei liste
lista = [21, 8, 9, 33] #se definește iterabilul: o listă
iterator = iter(lista) #se obține un iterator cu functia iter()
print(next(iterator)) # se iterează “manual” aplicând next()
print(next(iterator))
print(iterator.__next__()) # next(obj) echivalent cu obj.__next__()
print(iterator.__next__())
next(iterator) # Eroare, nu mai sunt termeni

Output
21
8
9
33
Traceback (most recent call last):
205 Iteratori

. . .
next(iterator) # Eroare, nu mai sunt termeni
StopIteration

O cale mai elegantă, pentru iterarea “automată”, este utilizarea buclei for. Cu ajutorul acesteia,
se poate itera orice obiect care întoarce un iterator (liste, șiruri, fișiere, etc.).
Exemplul 13.2 #iterarea unei liste utilizând for
>>> for termen in lista:
... print(termen)
...

Output
21
8
9
33
Altfel spus, un iterator nu este altceva decât un obiect container care implementează protocolul
iterator (care este bazat pe cele două metode: __next__, care întoarce următorul element al
containerului și __iter__, care întoarce iteratorul însuși).
Exemplul 13.3 #iterator creat pentru un șir utilizând funcția built-in iter
>>> i = iter('abc')
>>> next(i)
'a'
>>> next(i)
'b'
>>> next(i)
'c'
>>> next(i)
Traceback (most recent call last):
. . . . . . .
next(i)
StopIteration

Când secvența a fost parcursă, se ridică excepția StopIteration.

13.2 Cum funcționează bucla for cu iteratorii


Practic, bucla for se poate utiliza cu toți iterabilii (liste, șiruri, fișiere, etc.). Forma standard
este următoarea:
for ob_iterator in iterabil:
# se face ceva cu componenta iterata din iterabil

Bucla for este în realitate implementată ca o buclă while infinită, prin care trec toate
excepțiile, în afară de StopIteration. În interiorul buclei for se creează un obiect iterator,
ob_iterator, prin apelarea funcției iter() pe iterabil. Următorul termen al iterabilului se
obține apelând next() și apoi se execută corpul buclei for cu această valoare. După ce toți
termenii iterabilului se epuizează se ridică excepția StopIteration care este prinsă intern și
bucla se închide.
Implementarea se face după următorul model:
Exemplul 13.4 #funcționarea reală - implementarea buclei for printr-un while
206 Iteratori

# Se creează un obiect iterator din iterabil


ob_iterator = iter(iterabil)
# Bucla infinita while
while True:
try:
# se obține următoarea componenta
componenta = next(ob_iterator)
# se face ceva cu componenta din iterabil
except StopIteration:
# dacă de ridica StopIteration, se opreste bucla
break

13.3 Construcția iteratorilor de tip utilizator


Construcția unui iterator de către utilizator este destul de simplă în Python, reducându-se
practic doar la implementarea metodelor __iter__() și __next__().
Metoda __iter__() întoarce obiectul iterator însuși, uneori mai fiind necesară și o inițializare.
Metoda __next__() întoarce următoarea componentă a secvenței. La final, se ridică excepția
StopIteration.

Exemplul 13.5 # puterea lui 2 pentru o secvență de numere naturale


class P2:
"""Clasa pentru implementarea unui iterator
pentru ridicarea la puterea a doua a unor numere naturale"""
def __init__(self, max=0):
self.max = max
def __iter__(self):
self.n = 0
return self
def __next__(self):
if self.n <= self.max:
rezultat = 2 ** self.n
self.n += 1
return rezultat
else:
raise Exception('StopIteration')
# crearea unui obiect
numere = P2(4)
# crearea unui iterabil din obiect
i = iter(numere)
# utilizarea functiei next pentru obținerea următorului termen iterator
print(next(i))
print(next(i))
print(next(i))
print(next(i))
print(next(i))

Output
1
2
4
8
16

De asemenea, se poate utiliza o buclă for pentru iterarea clasei iterator.


207 Iteratori

>>> for i in P2(5):


... print(i)
...
1
2
4
8
16
32

13.4 Iteratori infiniți


Nu este necesar ca termenii unui obiect iterator să fie în număr finit. Pot exista și iteratori
infiniți (cu număr nelimitat de termeni). Utilizarea unui astfel de iterator trebuie să se facă cu
o atenție sporită, punând condiții de oprire..
Funcția built-in iter() poate fi apelată cu două argumente, unde primul argument trebuie să
fie un obiect apelabil (funcție), iar al doilea este santinela. Iteratorul apelează funcția până ce
valoarea întoarsă este egală cu santinela.
>>> int()
0
>>> inf = iter(int,1)
>>> next(inf)
0
>>> next(inf)
0
Se observă că funcția int() întoarce întotdeauna 0. Folosind-o pentru iterare ca iter(int,1),
ea va întoarce un iterator care va apela int() până ce valoarea întoarsă va fi egală cu 1. Dar
acest lucru nu se va întâmpla niciodată și astfel se obține un iterator infinit.
Pe baza acestei idei se poate realiza o secvență care poate executa iterativ, infinit, unele
activități.
Exemplul 13.6 #iterator infinit
def fx():
def fy():
print("functia fy face ceva")
b = fy()
return 0
a = iter(fx,1)
next(a)
next(a)
next(a)
#se poate continua la infinit cu apelul next(a)

Output
functia fy face ceva
functia fy face ceva
functia fy face ceva
Un iterator de tip infinit se poate implementa și în cadrul unei clase.
Exemplul 13.7 #iterator infinit implementat printr-o clasă
class it_infinit:
""" iterator care va întoarce
toate numerele impare """
208 Iteratori

def __iter__(self):
self.num = 1
return self
def __next__(self):
num = self.num
self.num += 2
return num

Output
>>> a = iter(it_infinit())
>>> next(a)
1
>>> next(a)
3
>>> next(a)
5
>>> next(a)
7
Se poate continua la infinit cu apelul next(a).

Observație. La utilizarea unui iterator infinit, întotdeauna trebuie acordată atenție includerii
unei condiții de terminare.
Concluzie. Construcția și utilizarea iteratorilor prezintă avantajul economisirii de resurse. De
exemplu, în exemplul de mai sus, se pot obține numerele impare fără a se stoca în memorie
toate numerele posibile. Teoretic, se poate avea într-o memorie finită un număr infinit de
numere.

13.5 Modulul itertools


Modulul itertools conține o colecție de funcții pentru realizarea eficientă de iteratori
reprezentând secvențe de parcurgere în buclă a unor iterabili, cu utilizarea eficientă a memoriei.
Iteratorii generați pot funcționa fie individual, fie în combinații. Împreună, ei constituie o
“algebră a iteratorilor”, devenind un instrument folositor pentru dezvoltarea aplicațiilor în
Python.
Modulul itertools conține următoarele funcții iterator:
- Iteratori infiniți: count(), cycle(), repeat().
- Iteratori finiți: accumulate(), chain(), chain.from_iterable(), compress(),
dropwhile(),filterfalse(), groupby(), islice(), pairwise(), starmap(),
takewhile(), tee(), zip_longest().
- Iteratori combinaționali: product(), permutations(), combinations(),
combination_with_replacement().

13.6 Întrebări, exerciții și probleme


13.6.1 Atunci când trebuie să se alăture mai multe obiecte iterabile, precum listele, pentru a
obține o singură listă, se poate utiliza funcția zip(). Funcția zip() returnează un obiect zip,
care este un iterator de tupluri în care elementele sale sunt asocieri de elemente din fiecare
iterator, corespunzând ca poziție de ordine (împerecheri corespondente ca indice în fiecare
tuplu), etc. Dacă iteratorii asociați au lungimi diferite, iteratorul cu cele mai puține elemente
decide lungimea noului iterator. Să se exemplifice utilizarea funcției zip().
209 Iteratori

R13.6.1
Sintaxa: *zip(iterator1, iterator2, iterator3 ...) *
an = (1998, 2004, 2012, 2019)
luna = ("mar", "iun", "ian", "dec")
zi = (11, 21, 13, 5)
print(tuple(zip(an, luna, zi)))

Output
[(1998, 'mar', 11), (2004, 'iun', 21), (2012, 'ian', 13), (2019, 'dec', 5)]

13.6.2 Să se scrie un program Python prin care se creează un nou iterator din mai mulți iterabili
și să se afișeze tipul acestuia.

R13.6.2 Se utilizează funcția chain() din biblioteca itertools.

from itertools import chain


def chain_func(l1,l2,l3):
return chain(l1,l2,l3)
#Generarea iteratorului din trei liste
noul_iterator = chain_func([1,2,3], ['a1','a2','a3','a4'], [4,5,6,7,8,9])
print("\n Tipul noului iterator:")
print(type(noul_iterator))
print("Elementele noului iterator:")
for k in noul_iterator:
print(k, ' ', end = '')

#Generarea iteratorului din trei tupluri


noul_iterator = chain_func((11,22,33,44), ('A','B','C','D'),
(55,66,77,88,99))
print("\n Tipul noului iterator:")
print(type(noul_iterator))
print("Elementele noului iterator:")
for k in noul_iterator:
print(k, '', end = '')

Output
Tipul noului iterator:
<class 'itertools.chain'>
Elementele noului iterator:
1 2 3 a1 a2 a3 a4 4 5 6 7 8 9
Tipul noului iterator:
<class 'itertools.chain'>
Elementele noului iterator:
11 22 33 44 A B C D 55 66 77 88 99

13.6.3 Să se scrie o secvență de program pentru generarea unui ciclu infinit de elemente
dintr-un iterabil.
R13.6.3 Se utilizează funcția cycle() din modulul bibliotecă itertools.
Soluția 1
import itertools as it
def cicl_infinit(itr):
return it.cycle(itr)
# rulează la infinit
#Elemente din Lista
210 Iteratori

noul_iterator = cicl_infinit(['A','B','C','D'])
print("Număr infinit de elemente generate:")
for i in noul_iterator:
print(i, ' ', end = '')

Output
Număr infinit de elemente generate:
A B C D A B C D A B C D A B C D A B C D A B C D A
B C D A B C D A B C D A B C D A B C D . . . . . . . . .

Soluția 2
import itertools as it
def cicl_infinit(itr):
return it.cycle(itr)
# rulează la infinit
#Elemente dintr-un sir
noul_iterator = cicl_infinit('Limbajul Python')
print("Număr infinit de elemente generate:")
for i in noul_iterator:
print(i, sep=' ', end = '')

Output
Număr infinit de elemente generate:
Limbajul PythonLimbajul PythonLimbajul PythonLimbajul PythonLimbajul
PythonLimbajul PythonLimbajul PythonLimbajul PythonLimbajul . . . . . . . .
. . . . . . . . . . . . . . . . . .

13.6.4 Să se scrie un program pentru a genera toate permutările posibile între n obiecte.
R13.6.4 Se utilizează funcția permutation() din modulul bibliotecă itertools.
import itertools as it
def permutari(elem):
for elemente in it.permutations(elem):
print(elemente)
permutari([1])
print("\n")
permutari([1,2])
print("\n")
permutari([1,2,3])
print("\n")
permutari(['a','b','c'])

Output
(1,)

(1, 2)
(2, 1)

(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)

('a', 'b', 'c')


('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
211 Iteratori

('c', 'a', 'b')


('c', 'b', 'a')

13.6.5 Să se scrie o secvență de program Python care să găsească lungimea maximă a unui
subșir care face parte dintr-un șir și care este format din caractere identice. Se vor utiliza funcții
din modulul itertools.
R13.6.5 Se utilizează funcția groupby().
import itertools as it
def max_subsir(sir):
return max(len(list(x)) for _, x in it.groupby(sir))
sir = "TRVHHHHHmg?dgfIIIfnfnvvvvvvppdAFt?gmmmm"
print("Sirul initial:",sir)
print("Lungimea maximă a subsirului format\
din caractere identice:", max_subsir(sir))

Output
Sirul initial: TRVHHHHHmg?dgfIIIfnfnvvvvvvppdAFt?gmmmm
Lungimea maximă a subsirului formatdin caractere identice: 6
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 14

Generatoare
14.1 Definiția și crearea generatoarelor
Necesitatea funcțiilor generatoare. În practica programării poate apărea necesitatea
prelucrării unui volum mare de date aflate în structuri de tipul: liste, dicționare, tablouri, baze
de date, etc. Prelucrarea prin metode clasice poate întâmpina dificultăți datorită consumului
mare de memorie deoarece prin stocarea atât a articolelor cât și a indecșilor aferenți acestora
în memoria calculatorului, aceasta se poate ocupa rapid și poate deveni astfel insuficientă. O
utilizare mai eficientă a memoriei se poate face dacă se substituie memorarea, de exemplu, a
unei întregi liste cu indicii aferenți, ce ar urma să fie prelucrată ca lot, cu memorarea și
prelucrarea individuală a unui singur articol la un moment dat. În acest caz, trebuie să se rezolve
problema producerii “întârziate” a indecșilor (iteratorului).
Metoda clasică de a implementa un iterator constă în utilizarea metodelor __iter__() și
__next__() cu urmărirea stărilor interne ale structurii (secvență de program, clasă) create și
la final ridicarea excepției StopIteration în momentul în care s-au epuizat valorile. Cantitatea
de cod ce trebuie scrisă este însă suficient de mare ca să descurajeze programarea în acest stil,
în plus ceea ce se poate realiza nu este ceva chiar foarte elegant și intuitiv.
O soluție de ieșire din acest impas este utilizarea funcțiilor generatoare, sau mai scurt spus,
utilizarea generatoarelor. Prin intermediul lor se pot crea iteratori în mod simplu și rapid. O
funcție generatoare conține și administrează tot ceea ce corespunde metodei clasice a unui
iterator.
Definiție. O generatoare este o funcție care întoarce un obiect (o valoare unică a unui iterator)
care poate fi utilizat pentru iterare (câte o valoare la un moment dat).
Crearea unei generatoare este foarte simplă, aceasta definindu-se ca o funcție obișnuită, dar
care utilizează cuvântul cheie yield în locul înstrucțiunii return.
Observație. Pentru a defini o generatoare, funcția trebuie să conțină cel puțin o declarație
yield, dar funcția poate să mai conțină și alte declarații yield și chiar return.

Observație. Modul de funcționare al instrucțiunilor return și yield este diferit prin aceea că
în timp ce return termină definitiv funcția, cu posibilitatea întoarcerii unei valori,
instrucțiunea yield produce o pauză în execuția funcției, salvează toate stările acesteia și poate
continua din acel punct, la un nou apel al funcției care poate apărea în continuare.
Mai jos sunt prezentate diferențele între o funcție normală și o funcție generatoare.
1. O funcție generatoare conține una sau mai multe instrucțiuni yield.
2. După apel, funcția generatoare întoarce un obiect (un element al iteratorului), dar nu
reîncepe execuția imediat (ci doar după ce se face activarea cu metoda next()).
3. Funcția generatoare implementează automat metodele __iter__() și __next__(). În
consecință se vor itera termenii iterabilului cu funcția next().
4. Odată ce funcția generatoare produce (“yields”) un obiect, ea este pusă pe “pauză“, iar
controlul este transferat apelantului.
5. Între apelurile succesive ale generatoarei, variabilele locale și stările lor sunt memorate (în
același spațiu limitat de memorie).
213 Generatoare

6. După terminarea la final a execuției funcției generatoare, dacă mai urmează alte apeluri
ale acesteia, se ridică excepția StopIteration.
Exemplul 14.1 # O funcție generatoare (cea mai simplă), cu mai multe instrucțiuni yield
>>>def fgen():
... n = 1
... print('Pasul 1')
... yield n
... n += 1
... print('Pasul 2')
... yield n
... n += 1
... print('Pasul 3')
... yield n
...
>>>j = fgen()
>>>next(j)
Pasul 1
1
>>>next(j)
Pasul 2
2
>>>next(j)
Pasul 3
3
>>>next(j)
Traceback (most recent call last):
. . .
next(j)
StopIteration

Observații în legătură cu exemplul de mai sus:


- se constată că la rularea funcției generator se generează un obiect, dar execuția nu începe
imediat, ci doar după ce se face activarea (“întârziat”) cu funcția next;
- de asemenea, se observă că valoarea variabilei n este rememorată între apeluri, spre
deosebire de comportamentul funcțiilor obișnuite (în care variabilele locale sunt distruse
între apeluri);
- important de reținut, obiectul generat se poate utiliza o singură dată, la o singură iterație;
- pentru a restarta procesul de generare cu aceeași funcție generatoare se utilizează o
comandă de forma a = generator().

14.2 Generatoare cu bucle for


Observație. Funcțiile generatoare se pot utiliza direct cu bucle for. Acest lucru este posibil
pentru că bucla for utilizează un iterator prin care avansează automat cu funcția next() până
la ridicarea automată a excepției StopIteration.

Exemplul 14.2 # utilizarea generatoarei cu bucla for


def fgen():
n = 1
print('Pasul 1')
yield n
214 Generatoare

n += 1
print('Pasul 2')
yield n
n += 1
print('Pasul 3')
yield n
for fiecare_termen in fgen():
print(fiecare_termen)

Output
Pasul 1
1
Pasul 2
2
Pasul 3
3
Exemplul de mai sus este prezentat doar pentru ilustrarea a ceea ce se întâmplă pe fond, dar
lucrurile sunt ceva mai complexe.
În mod normal, o funcție generatoare se implementează cu o buclă for având și o condiție de
terminare a iterațiilor.
Exemplul 14.3 #inversarea unui șir cu generatoare cu for și condiție de terminare
#indexul buclei for se obtine cu functia range()
def inversare_sir(sir_A):
lungime = len(sir_A)
for i in range(lungime - 1, -1, -1):
yield sir_A[i]
for litera in inversare_sir("abcdef"):
print(litera)

Output
f
e
d
c
b
a
Observație. Funcția generatoare de mai sus poate lucra și cu alte tipuri de iterabile (liste,
tupluri, etc.)

Exemplul 14.4 #funcție generatoare cu buclă for pentru obținere numere pare

def nr_pare(x):
while(x!=0):
if x%2==0:
yield x
x-=1

for i in nr_pare(8):
print(i)

Output
8
6
4
2
215 Generatoare

14.3 Expresii generatoare


O modalitate simplă și rapidă de a crea (funcții) generatoare este utilizarea expresiilor
generatoare.
Definiție. Spre deosebire de o expresie clasică, expresia generatoare are o execuție întârziată
(“lazy”), adică produce elemente doar când i se cere.
Creare. Sintaxa pentru crearea expresiile generatoare este asemănătoare cu a listelor
comprehensive, doar se înlocuiesc parantezele pătrate cu cele rotunde.
Diferența fundamentală între o listă comprehensivă și o expresie generatoare este că în timp ce
o listă comprehensivă produce o întreagă listă, expresia generatoare produce un singur termen
al listei la un moment dat. Această particularitate face ca expresia generatoare să fie mult mai
eficientă din punct de vedere al memoriei comparativ cu lista comprehensivă echivalentă.
Observație. Asemănător cu funcțiile lambda care creează funcții anonime, expresiile
generatoare creează funcții generatoare anonime.
Exemplul 14.5 #crearea unei expresii generatoare; comparație cu lista comprehensivă
# lista inițiala
lista = [5, 3, 7, 14]
# lista derivată cu termenii patratici obtinuti ca lista comprehensiva
lista_c = [x**2 for x in lista] #paranteze patrate !
# expresia generatoare (utilizeaza paranteze rotunde)
ex_gen = (x**2 for x in lista) #paranteze rotunde !
print(lista_c)
print(ex_gen)

Output
[25, 9, 49, 196]
<generator object <genexpr> at 0x00000256DF4276C8>

Rezultatul execuției arată că expresia generatoare produce un obiect generator care nu oferă
rezultatul dorit imediat, ci numai după ce i se cere (“la cerere”), asa cum se poate vedea în
exemplul care urmează.

Exemplul 14.6 #utilizarea expresiei generatoare

# expresia generatoare utilizeaza o lista


lista = [2, 4, 6, 8]
#obținerea obiectului generator din expresia generatoare
ob_g = (x**3 for x in lista)
print(next(ob_g))
print(next(ob_g))
print(next(ob_g))
print(next(ob_g))
next(ob_g) #semnalizare depasire, detectare StopIteration

Output
8
64
216
512
Traceback (most recent call last):
. . .
next(ob_g)
216 Generatoare

StopIteration

Observație. Expresiile generatoare pot fi folosite ca argumente ale funcțiilor. În acest caz, se
renunță la parantezele rotunde.
Exemplul 14.7 #argumentul funcției este o expresie generatoare
>>> sum(x**3 for x in lista)
800
>>> max(x**5 for x in lista)
32768

14.4 Utilizarea generatoarelor


Funcțiile generatoare pot fi soluții de implementare puternice.
Unul din motivele pentru care generatoarele sunt recomandate se datorează simplității
realizării, prin intermediul lor, a iteratorilor (cod mai scurt și mai clar), comparativ cu soluția
obișnuită a clasei iterator (cod mai lung și confuz).
14.4.1 Comparație intre un iterator și o generatoare
Deși au același scop, crearea de iteratori, mecanismele iteratorilor și generatoarelor prezintă o
serie de diferențe descrise în continuare.
1. Pentru obținerea unui iterator sunt suficiente utilizările directe ale funcțiilor iter() și
next(), în timp ce pentru crearea unei generatoare trebuie să se utilizeze o funcție creată în
acest scop.
2. Funcția generatoare utilizează cuvântul cheie ‘yield’, dar iteratorul nu depinde de o
declarație anume.
3. Generatoarea salvează stările variabilelor locale la fiecare ciclu al buclei for care se
termină cu ‘yield’ și face o pauză. Spre deosebire de acest comportament, iteratorul nu
utilizează variabile locale, el are nevoie doar de un iterabil pe care să-l itereze.
4. O generatoare poate avea orice număr de declarații ‘yield’.
5. Iteratorul de tip utilizator se poate implementa prin intermediul unei clase, în timp ce
generatoarea nu are nevoie de o clasă pentru implementare.
6. O generatoare se poate implementa utilizând o funcție sau o listă comprehensivă; iteratorul
folosește doar funcțiile iter() și next().
7. Scrierea unei funcții generatoare se face cu mai puține instrucțiuni (înseamnă nu numai cod
mai compact, ci și mai simplu și rapid) comparativ cu un iterator.
8. Iteratorul este mai eficient din punctul de vedere al utilizării memoriei, ca resursă pentru
sine însuși.
Exemplul 14.8 #memoria proprie utilizată de o generatoare și un iterator
def func():
i=1
while i>0:
yield i
i-=1
for i in func():
print(i)
func().__sizeof__()
217 Generatoare

Output
1
32

Deci, pentru generator s-au găsit utilizați 32 octeți de memorie.


În cazul implementării aceleiași activități printru-un iterator, se vor găsi mai puțini (16).
>>> iter([1,2]).sizeof__()
16
Observație importantă. Eficiența utilizării memoriei de către iteratori și generatoare este încă
o chestiune discutabilă. Pentru volume mici de date se pare că iteratorii sunt mai eficienți (ca
în exemplul de mai sus), dar pentru volume mari de date implementările cu generatoare
sunt mai avantajoase și sunt preferate doarece se produce doar un element la un moment dat.
În plus, generatoarele, indiferent de eficiența lor, sunt mai prietenoase din punct de vedere al
programării și clarității.
9. O generatoare întoarce un generator, dar un iterator întoarce un obiect. Acest lucru se poate
verifica testând funcția de la exemplul anterior, func().
Exemplul 14.9
>>> f=func()
>>> type(f)
<class ‘generator’>

>>> i=iter({1,3,2})
>>> type(i)
<class ‘set_iterator’>

10. Un generator este un iterator. Generatoarele sunt o subclasă a Iteratoarelor. Acest lucru se
poate verifica folosind funcția issubclass().

Exemplul 14.10

>>> import collections,types


>>> issubclass(types.GeneratorType, collections.Iterator)
True
>>> issubclass(collections.Generator,collections.Iterator)
True
>>> issubclass(collections.Iterator,types.GeneratorType)
False

Observație. Deși, așa cum s-a arătat mai sus, o generatoare este un iterator, reciproca nu este
întotdeauna adevărată (nu orice iterator este o generatoare).
11. Iteratorul este el însuși un iterabil. Iteratorul este o subclasă a clasei iterabilelor ( Iterable).
>>> issubclass(collections.Iterator,collections.Iterable)
True
12. Implementarea utilizând funcția generatoare poate fi mai scurtă și mai clară deoarece
funcția generatoare poate automatiza și ascunde detaliile procesului iterativ.
13. Generatoarele sunt soluții potrivite pentru reprezentarea fluxurilor infinite de date.
Fluxurile infinite de date nu pot fi reprezentate în memorie și din acest motiv, generatoarele
care pot produce doar un element la un moment de timp, pot reprezenta un flux infinit de date.
218 Generatoare

14. Generatoarele pot fi utilizate pentru asamblarea operațiilor după modelul “benzii rulante”
(pipeline), model întâlnit la calculul unor serii de numere (de exemplu, seria Fibonacci).

14.5 Întrebări, exerciții și probleme


14.5.1 Pe scurt, cum se creează o funcție generatoare în Python ? Dați un exemplu la calculul
indecșilor multipli de 7 pentru un interval precizat.
R14.5.1 O generatoare este similară unei funcții normale definită cu cuvântul cheie def și cu
utilizarea instrucțiunii yield în loc de return. Altfel spus, dacă corpul unei funcții conține
yield, ea devine automat generatoare.
def gen_index_m7():
for i in range (50):
if(i%7 ==0):
yield i

for i in gen_index_m7():
print(i, end=" ")

Output
0 7 14 21 28 35 42 49
14.5.2 Pot fi utilizate mai multe instrucțiuni yield într-o funcție generatoare?
R14.5.2 Da, pot fi utilizate mai multe instrucțiuni yield, spre deosebire de return.
O funcție normală poate conține mai multe instrucțiuni return, dar după executarea primului
return se iese din funcție, funcția este terminată.

În cazul funcției generatoare, se pot executa mai multe instrucțiuni yield, după care se poate
încheia funcția generatoare.
def multi_yield():
sir1 = "aaaaa"
yield sir1
sir2 = "bbbbb"
yield sir2
sir3 = "ccccc"
yield sir3
ob_gen = multi_yield()
print(next(ob_gen))
print(next(ob_gen))
print(next(ob_gen))

Output
aaaaa
bbbbb
ccccc

14.5.3 Se pot folosi generatoare direct în interiorul unei funcții (în locul listei de argumente)
pentru a scrie un cod mai scurt și mai curat. Să se exemplifice la calculul unei sume folosind
un generator direct ca argument pentru funcția de calcul.
R14.5.3
>>> sum(i for i in range(10))
45
219 Generatoare

14.5.4 Să se genereze o comparație între metoda clasei iterator, respectiv metoda funcției
generatoare prin scrierea a două secvențe de program pentru calculul pătratului unui număr.
R14.5.4
Metoda clasei iterator
class P2:
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
r = 2 ** self.n
self.n += 1
return r
a=P2(3)
print(P2().__sizeof__()) #afisare memoria alocata clasei iterator
print(a.__sizeof__()) #afisare memoria alocata obiectului iterator
print(a) # indica obiectul
i = iter(a)
# apel manual (explicit)
print(i) #inainte de next(), indica obiectul
print(next(i))
print(next(i))
print(next(i))
print(next(i))
# apel automat (buclă)
b = P2(3)
print(b) # indica obiectul
j = iter(b)
while (1000): # bucla infinită
print(next(j))

Output
32
32
<__main__.P2 object at 0x00000256DF7FE7C8>
<__main__.P2 object at 0x00000256DF7FE7C8>
1
2
4
8
<__main__.P2 object at 0x00000256E1088E08>
1
2
4
8
Traceback (most recent call last):
. . .
print(next(j))
. . .
raise StopIteration
StopIteration

Metoda funcției generatoare


def P2(max=0): #functia generatoare
220 Generatoare

n = 0
while n < max:
yield 2 ** n
n += 1
a=P2(3)
print(P2().__sizeof__()) #afisare memoria alocata funcției generatoare
print(a.__sizeof__())
print(a)
i = iter(a)
# apel manual (explicit)
print(i) #inainte de next(), indica obiectul
print(next(i))
print(next(i))
print(next(i))
# apel automat (bucla)
b = P2(3)
print(b)
j = iter(b)
while (1000): # bucla infinită
print(next(j))

Output
96
96
<generator object P2 at 0x00000256E10031C8>
<generator object P2 at 0x00000256E10031C8>
1
2
4
<generator object P2 at 0x00000256E0A391C8>
1
2
4
Traceback (most recent call last):
. . .
print(next(j))
StopIteration

14.5.5 Să se calculeze suma patratelor numerelor din seria Fibonacci folosind doua generatoare.
R14.5.5
#generatorul (I) de numere din seria Fibonacci
def nr_fibonacci(nr):
x, y = 0, 1
for _ in range(nr):
x, y = y, x+y
print(x, end = " ")
yield x
#generatorul (II) de numere ridicate la puterea a doua
def patrat(nr):
for num in nr:
yield num**2
print("\nSuma patratelor: ", sum(patrat(nr_fibonacci(8))))
print("\nSuma patratelor: ", sum(patrat(nr_fibonacci(14))))
Output
1 1 2 3 5 8 13 21
Suma patratelor: 714
1 1 2 3 5 8 13 21 34 55 89 144 233 377
Suma patratelor: 229970
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 15

Închideri
15.1 Concept
În filozofia POO, protecția prin ascunderea datelor ocupă un loc important. De regulă, acest
lucru se face în cadrul și prin construcția claselor și obiectelor. Există totuși aplicații în care
definirea de noi clase fie complică codul, fie nu se justifică din alte diverse motive, dar rămâne
totuși interesul pentru accesul protejat și controlat la anumite date.
Un exemplu de date sensibile sunt variabilele globale. Acestea pot fi alterate relativ ușor,
nedorit, în cadrul unui program. O cale de a evita utilizarea acestor variabile vulnerabile este
introducerea lor în clase și obiecte, dar astfel fie s-ar complica inutil programul, fie ar
presupune un efort prea mare. Soluția pentru astfel de cazuri este utilizarea inchiderilor
(clojure), adică închiderea într-o funcție.
Noțiunea de închidere are legătură cu noțiunea de variabilă non-locală din funcțiile încuibate
(nested). Se reamintește că o funcție încuibată în Python este o funcție declarată în cadrul altei
funcții, denumită funcție de “închidere” (enclosing function). Funcția încuibată are acces total
la variabilele definite în domeniul său de vizibilitate și doar acces parțial, numai pentru citire
(read-only), la variabilele din domeniul de vizibilitate al funcției care o închide/conține. Pentru
a avea acces total este necesară folosirea cuvântului cheie nonlocal în fața variabilei, în cadrul
funcției încuibate.
Exemplul 15.1 # acces la variabile din funcția închisă fara declaratia nonlocal
def print_msg(msg):
# functia de inchidere externa
def printeaza():
# functia incuibata (inchisa)
msg = "Salut!"
print(msg)
print(msg)
printeaza()
print(msg)
print_msg("Buna ziua!")

Output
Buna ziua!
Salut!
Buna ziua!

Exemplul 15.2 #acces la variabile din funcția închisă cu declaratia nonlocal


def print_msg(msg):
# functia de inchidere externa
def printeaza():
# functia incuibata (inchisa)
nonlocal msg #declaratia nonlocal
msg = "Salut!"
print(msg)
print(msg)
printeaza()
print(msg)
print_msg("Buna ziua!")
222 Închideri

Output
Buna ziua!
Salut!
Salut!

15.2 Definirea unei închideri


Sunt unele aplicații în care se dorește păstrarea conținutului unor variabile locale dezvoltate în
cadrul unor funcții incuibate (închise). De regulă, după terminarea execuției funcției încuibate,
variabilele locale aparținând acestei funcții sunt șterse în totalitate.
Definiție. Tehnica prin care se face posibil ca unele date aparținând doar funcției încuibate
(închise), aflate în domeniul său de vizibilitate (care în mod normal dispar după terminarea
execuției acestei funcții, fiind date locale) să rămână încă disponibile în program după
terminarea funcției, se numește închidere (clojure).
O soluție practică pentru exemplul de mai sus este înlocuirea apelului din ultima linie a funcției
print_msg()cu returnarea funcției printeaza().

Exemplul 15.3
def print_msg(msg):
# functia de inchidere externa
def imprima():
# functia încuibata
print(msg)
return imprima # intoarce functia incuibata
test_cloj = print_msg("Salut!")
test_cloj()

Output
Salut!

Rezultatul de mai sus pare puțin ciudat. Funcția print_msg()a fost apelată cu argumentul șir
"Salut!", iar funcția returnată a fost legată la numele test_cloj. Apoi, apelând
test_cloj(), se imprimă mesajul "Salut!", deși funcția print_msg() își terminase
deja execuția.
Această tehnică prin care date locale (“Salut!” în acest caz) rămân atașate de o variabilă este
ceea ce se numește “închidere (clojure)” în Python.
Valoarea din funcția de inchidere este memorată în afara domeniului său de vizibilitate. Acest
lucru se întâmplă chiar dacă funcția de inchidere este ștearsă din spațiul de nume curent, după
cum se poate vedea continuînd rularea exemplului de mai sus în consola Python.
Exemplul 15.4 (continuare)
>>> del print_msg
>>> test_cloj()
Salut!
>>> print_msg("Salut!")
Traceback (most recent call last):
. . .
print_msg("Salut!")
NameError: name 'print_msg' is not defined

Concluzia la exemplul de mai sus este: funcția întoarsă de return încă funcționează, deși funcția
originală a fost ștearsă.
223 Închideri

Observație. O închidere există numai dacă o funcție încuibată face referire la o valoare situată
în spațiul său de închidere. Criteriile asociate definiției unei închideri sunt următoarele:
- Trebuie să existe o funcție încuibată.
- Funcția încuibată trebuie să se refere la o variabilă locală.
- Funcția de închidere trebuie să întoarcă (prin return) funcția încuibată.

15.3 Utilitatea unei închideri


A. Închiderile oferă posibilitate programatorului să evite utilizarea variabilelor globale și să
obțină o formă acceptabilă de ascundere a datelor. Această politică este în conformitate cu
abordarea POO a problemelor de proiectare în domeniul programării.
B. În situațiile când algoritmul aplicației impune crearea unui număr restrâns de metode (de
obicei, una singură), închiderile oferă o alternativă mai simplă și mai elegantă la implementarea
prin intermediul unei clase. Atunci când numărul de metode este mare, implementarea printr-
o clasă apare însă ca fiind mai bună și în consecință mai recomandată. De asemenea, la alegerea
soluției mai intervin și preferințele programatorului.
C. Închiderile își găsesc un domeniu larg de aplicație la funcțiile decoratoare.
Exemplul 15.5 #utilizarea închiderii la multiplicarea cu un număr fixat
def multiplicator(n):
def multiplu(x):
return x * n
return multiplu
ori4 = multiplicator(4) #multiplicare cu 4
ori7 = multiplicator(7) #multiplicare cu 7
print(ori4(9))
print(ori7(3))
print(ori7(ori4(4)))

Output
36
21
112

În exemplul de mai sus, ori4() și ori7() sunt închideri (funcții de închidere).


Observație importantă. Toate obiectele de tip funcție au atributul __closure__ care întoarce
un tuplu de obiecte celulă dacă aceasta este o funcție de închidere.
Exemplul 15.6 (continuarea în consolă a ex.15.5) #atributul __closure__
>>> multiplicator.__closure__
>>> ori4.__closure__
(<cell at 0x000001CA58308A60: int object at 0x000001CA55C96990>,)

Obiectul celulă are atributul cell_contents care memorează valorile închise:


>>> ori4.__closure__[0].cell_contents
4
>>> ori7.__closure__[0].cell_contents
7
224 Închideri

15.4 Întrebări, exerciții și probleme


15.4.1 Presupunând că într-un program Python se dorește ca variabila ”cheia” să nu apară ca
variabilă globală, din motive de protecție a datelor, să se scrie o secvență de cod care să
utilizeze o închidere în acest scop.
R15.4.1
def obtine_cheia(cheia):
cheia = 1234
def inchide():
print(cheia)
return inchide
afiseaza = obtine_cheia("SESAM")
del obtine_cheia
print(afiseaza)
afiseaza()
afiseaza()
afiseaza()

Output
<function obtine_cheia.<locals>.inchide at 0x000001CB1C6B25F0>
1234
1234
1234

15.4.2 Să se implementeze o secvență de numărare prin care o variabilă ”contor” să fie protejată
prin închidere. Ce soluție permite incrementarea variabilei contor la fiecare apel al funcției de
închidere?
R15.4.2 Pentru ca funcția încuibată să poată modifica variabila contor, este necesar ca aceasta
să fie declarată ca fiind nonlocală. În acest fel se transformă dintr-o variabilă imutabilă într-
o variabilă mutabilă.
def contor(ctr):
ctr = 0
def inchis_contor():
nonlocal ctr
ctr += 1
print(ctr)
return inchis_contor
numarator = contor(0)
del contor
print(numarator)
numarator()
numarator()
numarator()

Output
<function contor.<locals>.inchis_contor at 0x000001E211A125F0>
1
2
3

15.4.3 Să se arate ambele variante de implementare, prin clase și prin închideri, ale problemei
de ascundere a volorii variabilei reprezentând suma unor încasări într-o aplicatie financiară.
R15.4.3
Implementarea cu clasă:
225 Închideri

class Total_Incasat():
def __init__(self):
self.lei = []
def __call__(self, val):
self.lei.append(val)
_lei = sum(self.lei)
return _lei
total = Total_Incasat()
s = total(int(input('Suma=')))
print('Total=',s)
s = total(int(input('Suma=')))
print('Total=',s)
s = total(int(input('Suma=')))
print('Total=',s)
s = total(int(input('Suma=')))
print('Total=',s)

Output
Suma=56
Total= 56
Suma=14
Total= 70
Suma=25
Total= 95
Suma=56
Total= 151

Implementarea cu închidere (clojure):


def Total_Incasat():
lei = []
def finchisa(val):
lei.append(val)
_lei = sum(lei)
return _lei
return finchisa
total = Total_Incasat()
s = total(int(input('Suma=')))
print('Total=',s)
s = total(int(input('Suma=')))
print('Total=',s)
s = total(int(input('Suma=')))
print('Total=',s)
s = total(int(input('Suma=')))
print('Total=',s)

Output
Suma=89
Total= 89
Suma=45
Total= 134
Suma=56
Total= 190
Suma=45
Total= 235
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 16

Funcții decoratoare
16.1 Concept și definiție
Decorarea funcțiilor reprezintă un aspect al domeniului programării calculatoarelor denumit
metaprogramare. În principiu, prin metaprogramare se înțelege modificarea pe care o face o
parte a programului unei alte părți a programului, la momentul compilării (înainte de execuție).
Definiție (1). O funcție decoratoare se aplică unei alte funcții astfel încât funcția țintă să capete
una sau mai multe funcționalități suplimentare față de cele de la momentul creerii sale. Metoda
decorării unei funcții reprezintă o formă de metaprogramare.
Pentru a înțelege mai bine modul cum funcționează decoratorii, este necesară reamintirea
câtorva cunoștințe de bază din Python.
A. S-a spus de la începutul lucrării că totul, în Python, este obiect (inclusiv clasele), iar numele
sunt simple legături către obiectele existente.
În acest context și funcțiile sunt, de asemenea, obiecte, care au propriile atribute. De un același
obiect funcție se pot lega diferite nume.
Exemplul 16.1 #Atribuirea mai multor nume la un obiect funcție
def numef1(msg):
print(msg)
numef1("Salut!")
numef2 = numef1
numef2("Salut!")

Output
Salut!
Salut!

Se observă că ambele funcții numef1 și numef2 se referă la același obiect funcție și dau în
urma execuției același rezultat.
B. Un alt aspect ce trebuie să fie reamintit este că, în Python, o funcție poate fi argumentul unei
alte funcții. O astfel de funcție care poate avea drept argument o altă funcție este denumită
funcție de ordin superior (higher order function). Exemple bine cunoscute de astfel de
funcții sunt map, filter și reduce.
Exemplul 16.2 #Utilizarea unei funcții ca argument
#Se compilează batch și se apelează în consolă.
def inc(x):
return x + 1
def dec(x):
return x - 1
def operatie(func, x):
rezultat = func(x)
return rezultat

Output
>>> operatie(inc,3)
227 Decoratoare

4
>>> operatie(dec,3)
2
C. De asemenea, în Python, o funcție poate returna o altă funcție.În continuare se prezintă cazul
unei funcții incuibate, fn_returnata(), care este definită și returnată la fiecare apel al funcției
fn_apelata().

Exemplul 16.3 #Returnarea unei funcții de către o altă funcție


#Se ilustrează intoarcerea unei functii de catre o alta functie, prin return
def fn_apelata():
def fn_returnata():
print("Salut!")
return fn_returnata
fn_noua = fn_apelata()
fn_noua()

Output
Salut!

D. Pentru studiul și înțelegerea decoratorilor se recomandă cunoașterea închiderilor în Python.

16.2 Construcția decoratorilor


Funcțiile și metodele sunt entități apelabile deoarece ele pot fi apelate, cu sau fără argumente.
De fapt, orice obiect care implementează metoda specială __call__() poate fi denumit
“apelator”. În esență, un decorator este un apelator care întoarce un apelabil.
Definiție (2). Formulat în cel mai simplu mod, un decorator preia o funcție, îi adaugă ceva
funcționalitate și apoi o returnează.
Exemplul 16.4 #Funcția decorată nu este afectată în nici un fel de decorare
def f_decoreaza(func):
def f_incuibata():
print("Aceasta funcție este decorata!")
func()
return f_incuibata
def f_oarecare():
print("Aceasta functie este nedecorata!")

f_oarecare() #afiseaza textul: "Aceasta functie este nedecorata! "


f_decorata = f_decoreaza(f_oarecare)
f_decorata() #se va arăta ca functia f_oarecare a fost decorata,
#dar si ca functionalitatea ei initiala a ramas
#neafectata, afisand textele:
#"Aceasta funcție este decorata! "
#"Aceasta functie este nedecorata!"

Output
Aceasta functie este nedecorata!
Aceasta funcție este decorata!
Aceasta functie este nedecorata!

În exemplul de mai sus, f_decoreaza() este un decorator. Prin atribuire se obține


f_decorata, astfel:
228 Decoratoare

f_decorata = f_decoreaza(f_oarecare)

Funcția f_oarecare a fost decorată, iar funcției întoarse i s-a dat numele f_decorata.
Se poate observa că funcția decoratoare a adăugat ceva funcționalitate funcției originale. Ea a
acționat asemănător aplicării unui ambalaj. Obiectul care a fost decorat nu a fost afectat în nici
un fel. Dar, în urma decorării, fie are un aspect mai bun, fie are funcțiuni adăugate.
Utilizarea simbolului @
În general, decorarea unei funcții se face aplicându-i o funcție de decorare și apoi auto-
reatribuind-o, astfel:
f_oarecare = f_decoreaza(f_oarecare)

Deoarece această construcție este tipică, Python dispune de o sintaxă pentru a o simplifica, prin
utilizarea simbolului @. Simbolul @ este aplicat înaintea numelui funcției decoratoare care se
plasează chiar deasupra definiției funcției ce trebuie decorată.
Secvența care urmează:
@f_decoreaza
def f_oarecare():
print("Aceasta functie este nedecorata!")

este echivalentă cu următoarea secvență:


def f_oarecare():
print("Aceasta functie este nedecorata!")
f_oarecare = f_decoreaza(f_oarecare)

16.3 Decorarea funcțiilor care au listă de parametri


Exemplul de mai sus se referă la o funcție simplă, fără parametri. În cazul general, funcțiile au
parametri.
Pentru a analiza cazul general, se propune o funcție cu doi parametri, a și b care să calculeze
rezultatul împărțirii lui a prin b. Evident, dacă b va fi egal cu zero, se va obține eroare.
Exemplul 16.5 #o funcție simplă cu doi parametri
def impartire(a, b):
return a/b
print(impartire(6,5))

Output
1.2

Dacă se apelează în shell (consolă) funcția cu b = 0, se va obține o eroare deoarece nu este


permisă împărțirea cu zero.

>>> impartire(6,0)
Traceback (most recent call last):
. . .
impartire(6,0)
. . .
ZeroDivisionError: division by zero
Utilizarea funcției poate fi îmbunătățită prin aplicarea unui decorator care să verifice condițiile
de corectitudine ale calculului.
229 Decoratoare

Exemplul 16.6 #Îmbunătățirea cu un decorator care verifică corectitudinea apelării


def impartire_verif(func):
def f_interna(a, b):
print("Se împarte valoarea:", a, "la valoarea:", b)
if b == 0:
print("Eroare! Impartire la zero!")
return
return func(a, b)
return f_interna

@impartire_verif
def impartire(a, b):
print(a/b)

Output #se execută în shell


>>>impartire(5,2)
Se împarte valoarea: 5 la valoarea: 2
2.5
>>>impartire(5,0)
Se împarte valoarea: 5 la valoarea: 0
Eroare! Impartire la zero!

Observație. Parametrii funcției f_interna din interiorul funcției decorator sunt aceiași cu
parametrii funcției pe care o decorează. Această constatare se poate generaliza, obținându-se o
regulă pentru decoratorii cu orice număr de parametri.
Soluția este utilizarea formei generale de definiție a unei funcții: function(*args,
**kwargs). Primul argument, args, reprezintă un tuplu de argumente poziționale, iar al doilea
reprezintă un dicționar de argumente.
Exemplul 16.7 #formă generală pentru un decorator
def deco_toate_fcn(func):
def f_interior(*args, **kwargs):
print("Decorare pentru orice funcție")
return func(*args, **kwargs)
return f_interior

16.4 Înlănțuirea decoratorilor


Decoratorii pot fi înlănțuiți în Python, mai exact se pot aplica succesiv mai mulți decoratori
aceleiași funcții. Sintactic, acest lucru poate fi exprimat prin plasarea decoratorilor deasupra
funcției dorite a fi decorate.

Exemplul 16.8 #utilizarea decoratorilor înlănțuiți, prin suprapunerea lor


def simbol_1(func):
def f_interior(*args, **kwargs):
print("*" * 40)
func(*args, **kwargs)
print("*" * 40)
return f_interior
def simbol_2(func):
def f_interior(*args, **kwargs):
print("=" * 40)
func(*args, **kwargs)
print("=" * 40)
230 Decoratoare

return f_interior
@simbol_1
@simbol_2
def afisare(msg):
print(msg)
afisare("|| Tehnologia Informatiei ||")

Output
****************************************
========================================
|| Tehnologia Informatiei ||
========================================
****************************************

Sintaxa:
@simbol_1
@simbol_2
def afisare(msg):
print(msg)

este echivalentă cu:


def afisare(msg):
print(msg)
afisare = simbol_1(simbol_2(afisare))

Ordinea în care sunt înlănțuiți decoratorii are importanță; dacă se inversează ordinea în
exemplul de mai sus, se obține:
@simbol_2
@simbol_1
def afisare(msg):
print(msg)

Output
========================================
****************************************
|| Tehnologia Informatiei ||
****************************************
========================================

16.5 Întrebări, exerciții și probleme


16.5.1 Utilizând opțiunea debbuger a mediului de dezvoltare a IDLE Python pentru a executa
pas cu pas programul care urmează (reprezentând definiția și modul de lucru cu o funcție
decoratoare), să se completeze numerele de ordine corespunzătoare secvenței de execuție a
instrucțiunilor programului.
#definitia functiei decoratoare
def func_decoratoare(func_apelata_pt_a_fi_decorata): #Pas…
#interiorul functiei decoratoare
#care are ca argument o functie ce va fi decorata
#in interior este o functie incuibata care acceseaza
#functia externa ce va fi decorată si apoi va fi returnata

def interior_A(): #Pas…


print("Mesaj: inainte de executia functiei decorate!") #Pas…
func_apelata_pt_a_fi_decorata() #Pas…
print("Mesaj: dupa executia functiei decorate!") #Pas…
231 Decoratoare

return interior_A #Pas…

#definirea functiei ce urmeaza a fi decorata


def functia_decorata(): #Pas…
# aceasta functie urmeaza a fi decorata
print("Mesaj: interiorul functiei ce va primi decorarea!") #Pas…

#trecerea functie ce urmeaza a fi decorata ca argument


#al functiei decoratoare pentru a-i fi modificata comportarea
functia_decorata = func_decoratoare(functia_decorata) #Pas…

# apelarea funciei ce a fost decorata


functia_decorata() #Pas…

16.5.2 Scrieți un program care decorează o funcție calcul ce realizează operații între doi
parametri. ”Decorarea” constă în introducerea parametrilor de la consolă, lucru care nu se face
în cadrul funcției calcul.
R16.5.2 Funcția decoratoare stabilește modul cum se introduc și se transmit funcției calcul
valorile parametrilor. O singură mențiune: declararea parametrilor trebuie făcută în programul
principal.
def functia_decoratoare(func):
# def interior(*args, **kwargs): #cazul general
def interior(a, b): #cazul aplicatiei
print("Msg: urmeaza decorarea!")
a = int(input("a="))
b = int(input("b="))
print("Msg: inainte de executia funct ce se decoreaza")
# obtinerea valorii returnate
# valoarea_returnata = func(*args, **kwargs) #cazul general
valoarea_returnata = func(a, b) #cazul
aplicatiei
print("Msg: dupa executie")

# intoarcerea valorii la cadrul original


return valoarea_returnata
return interior

# adaugarea functiei decoratoare


@functia_decoratoare
def calcul(a, b):
print("Msg: interiorul functiei calcul")
return a + b

a, b = 0, 0

# obtinerea rezultatului
print("Rezultatul calculului=", calcul(a, b))

Output
Msg: urmeaza decorarea!
a=6
b=9
Msg: inainte de executia funct ce se decoreaza
Msg: interiorul functiei calcul
Msg: dupa executie
Rezultatul calculului= 15
232 Decoratoare

16.5.3 Să se scrie un program în care cei doi parametri ai unei expresii de calcul sunt adăugați
consecutiv, prin două funcții decoratoare diferite.
R16.5.3 Rezolvarea constă în utilizarea metodei parametrilor înlănțuiți. Programul este
implementarea expresiei: decorator2(decorator1(calcul))).
def decorator2(func):
def intern(x, y):
x = int(input("Decorator2: x = "))
return func(x, y)
return intern

def decorator1(func):
def intern(x, y):
y = int(input("Decorator1: y = "))
return func(x, y)
return intern

@decorator2
@decorator1
def calcul(x, y):
return x * x + 10 * x + 7 + 3 * y + y * y

x = y = 0
print(calcul(x, y))

Output
Decorator2: x = 9
Decorator1: y = 5
218
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 17

Erori și excepții built-in


17.1 Tipuri de erori
În timpul execuției unui program Python pot apărea diverse erori datorită unei programări
defectuoase. În mod normal, un program care produce chiar și numai o singură eroare ar trebui
să se termine imediat.
Erorile se clasifică în general în mai multe categorii categorii:
- Erori de sintaxă.
- Erori de sistem, impredictibile sau dificil de prezis, generate de sistemul de operare (ca
urmare a unei intrări invalide, lipsa unei resurse, programare slabă).
- Excepții (în realitate sunt erori logice, predictibile de către programator).
Eroarea indică o problemă severă pe care o aplicație obișnuită nu ar trebui să încerce să o
trateze.
Excepția indică o problemă mai simplă pe care o aplicație obișnuită ar trebui să o trateze (la
runtime).

17.1.1 Erorile de sintaxă și de sistem

Erorile generate ca urmare a nerespectării regululor de sintaxă ale limbajului de programare se


numesc erori de sintaxă, sau erori de analiză de limbaj generate de analizorul limbajului
(parser).

Exemplul 17.1 #eroare de sintaxă


>>> if a < 3
File "<interactive input>", line 1
if a < 3
^
SyntaxError: invalid syntax
În exemplul de mai sus eroarea este dată de lipsa simbolului “ : ”, iar o săgeată indică locul
unde analizorul sintactic a găsit eroarea.
Erorile de sistem necontrolabile apar deoarece ori nu există posibilitatea de a prevedea apariția
lor, sau costurile metodelor de sesizare a apariției lor sunt foarte mari. Eroarea de sistem arată
o problemă apărută datorită lipsei uneia sau mai multor resurse în sistemul de calcul.

17.1.2 Excepțiile (erorile logice)

După corectarea erorilor de sintaxă, încă mai pot apărea erori la momentul execuției. Acestea
sunt denumite excepții sau erori logice.
Exemple de cazuri când se produc erori:
- Încercarea de deschidere a unui fișier inexistent, pentru citire ( FileNotFoundError)
- încercarea de a divide un număr prin zero (ZeroDivisionError)
- încercarea de a importa un modul inexistent (ImportError).
234 Erori și excepții

- etc.
Ori de câte ori apare la execuție o astfel de eroare, Python creează un obiect de tip excepție.
Dacă acesta nu este procesat corespunzător, atunci programul se întrerupe și se afișează o serie
de mesaje indicând cauza și locul producerii erorii în program sau memorie, posibil și alte
detalii, după caz.
Exemplul 17.2 #cazuri de apariție a erorilor la execuția instrucțiunilor
>>> 1/0
Traceback (most recent call last):
. . . . . . . . .
1/0
ZeroDivisionError: division by zero

>>> open("inexistent.txt")
Traceback (most recent call last):
. . . . . . . . .
open("inexistent.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'inexistent.txt'

17.2 Excepțiile de tip built-in


Definiție. O excepție este un semnal software declanșat de un eveniment în timpul execuției
unui program (de exemplu, apariția unei erori), care poate perturba fluxul normal al
instrucțiunilor acestuia. Excepția este “ridicată” (termen utilizat de programatori) de regulă
când se efectuează operații ilegale. Ea poate fi folosită pentru a evita oprirea nedorită a
programului.
Excepțiile sunt de două tipuri:
- excepții definite în cadrul Python (built-in) care pot fi ridicate automat la apariția erorilor.
- excepții definite de utilizatorul însuși.
Excepțiile built-in pot fi vizualizate cu ajutorul unei construcții utilizând funcția local(), care
poate produce lista tuturor entităților built-in (atribute, funcții și excepții) din care se pot
extrage doar exceptiile, prezentate parțial în lista care urmează.
>>>print(dir(locals()['__builtins__']))
Tabelul 17.1 Tabel parțial cu excepțiile built-in
Numele excepției Motivul ridicării excepției
AssertionError eșec instrucțiune assert
AttributeError eșec la atribuirea unei referințe sau atribut
EOFError dată de funcția input() la condiția end-of-file
FloatingPointError eșec operație în virgulă flotantă
GeneratorExit eșec operație metoda close()
ImportError modulul importat nu este găsit
IndexError indexul unei secvențe are depășire
KeyError cheie negăsită în dicționar
KeyboardInterrupt apăsare taste de întrerupere (Ctrl+C or Delete)
MemoryError eroare depășire memorie
NameError variabilă negăsită în domeniile de vizibilitate local sau global
NotImplementedError eroare dată de metodele abstracte
235 Erori și excepții

OSError eroare dată de sistemul de operare


OverflowError eroare de depășire capacitate de reprezentare
ReferenceError eroare în funcționarea mecanismului de garbage colection
RuntimeError eroare de execuție cu cauză neidentificată
StopIteration eroare dată de next() care nu găsește elementul următor
SyntaxError excepție ridicată de parser la o eroare de sintaxă
IndentationError indentare incorectă
TabError eroare de indentare produsă de spații și tab-uri
SystemError eroare internă detectată de interpretor
SystemExit excepție ridicată de duncția sys.exit()
TypeError operație sau funcție aplicată unui obiect greșit
UnboundLocalError referință la o variabilă locală, dar legătura negăsită
UnicodeError eroare de codare sau decodare în legătură cu Unicode
UnicodeEncodeError eroare de codare în legătură cu Unicode
UnicodeDecodeError eroare de decodare în legătură cu Unicode
UnicodeTranslateError eroare de translatare în legătură cu Unicode
ValueError argument de tip bun, dar valoare nepotrivită pentru o funcție
ZeroDivisionError al doilea operand este zero în cazul împărțirii

17.3 Tratarea excepțiilor cu try, except și finally


17.3.1 Excepțiile în Python

Așa cum s-a precizat mai sus, Python dispune de multe excepții de tip built-in, sau definite de
utilizator, care sunt ridicate când un program întâmpină o eroare (ceva greșit în program).
Când se ridică o excepție (se semnalizează existența sau apariția erorii), interpretorul Python
oprește execuția procesului curent și transferă controlul procesului apelat prin excepție. Dacă
tratarea excepției eșuează, atunci programul se oprește definitiv, altfel se revine la procesul
curent și execuția continuă.
De exemplu, fie o funcție A care apelează o funcție B, care apelează o funcție C. Dacă o
excepție are loc în funcția C, dar nu este procesată în C, atunci excepția trece în B și apoi în A.
Dacă nu este tratată în vreun fel, se afișează un mesaj de eroare și programul se întrerupe cu
eroare neprevăzută.

17.3.2 Capturarea excepțiilor


În Python, excepțiile pot fi tratate cu instrucțiunea/clauza try.
Operația susceptibilă să ridice o excepție se plasează într-o clauză try.
Secvența de program care tratează excepția se scrie în clauza except. Aici se pot alege și
plasa operațiile ce se vor executa din momentul în care se capturează (“se prinde”) excepția.
Definiție. Activarea clauzei except și începerea tratării reprezintă “capturarea”, sau
“prinderea” excepției (catching an exception). Tratarea excepției poate fi, sau nu, reușită.
Exemplul 17.3 #utilizarea clauzelor try și except
import sys #modulul sys conține tipul de exceptie
xlista = ['s', 0, 7]
for element in xlista:
236 Erori și excepții

try:
print("Elementul este:", element)
r = 1/int(element)
break
except:
print("Atentie!", sys.exc_info()[0], "a avut loc.")
print("Următorul element.")
print()
print("Inversul lui:", element, "este", r)

Output
Elementul este: s
Atentie! <class 'ValueError'> a avut loc.
Următorul element.

Elementul este: 0
Atentie! <class 'ZeroDivisionError'> a avut loc.
Următorul element.

Elementul este: 7
Inversul lui: 7 este 0.14285714285714285

Această secvență de program conține o buclă for pentru elementele din lista xlista. Sursa
posibilei excepții se află în interiorul blocului try.
Dacă nu se produce nici o excepție, blocul except este sărit (ultimul caz), iar fluxul normal
continuă până la ultima valoare. Dar dacă se produce o excepție (primul și al doilea caz),
aceasta este prinsă de blocul except.
Se poate afișa numele excepției prin funcția exc_info() din modulul sys. Se poate observa
că elementul s produce ValueError, iar 0 produce ZeroDivisionError.

17.3.3 Capturarea excepțiilor specifice


În exemplul de mai sus, nu s-a menționat nimic specific despre excepție (ce caz este) în clauza
except.

Nu este o practică bună de programare să se prindă toate excepțiile și să se trateze fiecare caz
în același mod.
Se poate specifica ce excepții ar trebui să prindă o clauză except.
O clauză try poate să aibă orice număr de clauze except pentru a trata diferite excepții, totuși
doar numai una va fi executată în cazul ridicării unei excepții.
Se poate utiliza un tuplu de valori pentru a specifica mai multe excepții într-o singură clauză
except.

Exemplul 17.4 #specificarea excepțiilor în clauza except

try:
# se face ceva
pass

except ValueError:
# se tratează excepția ValueError
pass
237 Erori și excepții

except (TypeError, ZeroDivisionError):


# se tratează excepțiile multiple TypeError și ZeroDivisionError
pass

except:
# tratează alte excepții
pass

17.3.4 Ridicarea excepțiilor în Python


În Python, exceptiile sunt generate/ridicate automat la momentul execuției programului
(runtime).
O excepție poate fi ridicată și manual folosind cuvântul cheie raise.
Opțional se pot transfera valori (argumente) excepției, pentru a indica mai clar ce excepție a
fost ridicată.
Exemplul 17.5 #ridicarea manuală a excepțiilor și utilizarea argumentelor
>>> raise KeyboardInterrupt
Traceback (most recent call last):
...
KeyboardInterrupt

>>> raise MemoryError("Atentie la parametri !!!")


Traceback (most recent call last):
...
MemoryError: Atentie la parametri !!!

>>> try:
... a = int(input("Introduceti un pozitiv intreg: "))
... if a <= 0:
... raise ValueError("Acesta nu este un numar pozitiv!")
... except ValueError as val_eroare:
... print(val_eroare)
...
Introduceti un pozitiv intreg: -2
Acesta nu este un numar pozitiv!

17.3.5 Instrucțiunea try cu clauza else


În unele situații se poate dori să se ruleze o anumită secvență de cod dacă secvența de cod din
clauza try se execută fără nici o eroare. Pentru astfel de cazuri se poate utiliza opțional
cuvântul cheie else cu instrucțiunea try.
Observație: În clauza else pot apărea excepții. Aceste excepții nu sunt tratate de clauzele
precedente.
Exemplul 17.6 #utilizarea clauzei else
# calculul inversului unui numar par
try:
nr = int(input("Introduceti un numar: "))
assert nr % 2 == 0
except:
print("Nu este un numar par!")
else:
inversul = 1/nr
print(inversul)
238 Erori și excepții

Output1 Dacă se introduce un număr impar:


Introduceti un numar: 1
Nu este un număr par!

Output2 Dacă se introduce un număr par, se calculează și se afișează inversul numărului.


Introduceti un numar: 4
0.25

Output3 Totuși, dacă se introduce 0, se produce eroarea ZeroDivisionError în


interiorul lui else, care nu este tratată de precedentul except.
Introduceti un numar: 0
Traceback (most recent call last):
. . .
inversul = 1/nr
ZeroDivisionError: division by zero

17.3.6 Instrucțiunea try ... finally


Instrucțiunea try poate avea clauza opțională finally. Această clauză este executată în orice
variantă, fiind utilizată în general pentru eliberarea resurselor.
Exemplul 17.7 #Clauza finally
try:
f = open("test.txt",encoding = 'utf-8')
# operații executate cu fișierul
finally:
f.close()

Prin acest tip de construcție, dacă apare o eroare la deschiderea fișierului sau la desfășurarea
operațiilor cu acesta, producând o excepție la execuția programului, se închide fișierul și se
eliberează resursele implicate în deschiderea acestuia.

Fig. 17.1 Structura generală a tratării unei excepții de tip built-in


239 Erori și excepții

17.4 Excepții definite de utilizator


17.4.1 Crearea excepțiilor de tip uilizator

A crea o excepție în Python înseamnă a crea o clasă nouă. Clasa excepție definită de utilizator
trebuie să fie derivată direct sau indirect din clasa built-in Exception. Majoritatea excepțiilor
built-in sunt de asemenea derivate din aceasta.

Exemplul 17.8 #crearea unei clase excepție

>>> class Test_eroare(Exception):


... pass
...

>>> raise Test_eroare


Traceback (most recent call last):
...
__main__. Test_eroare

>>> raise Test_eroare("A aparut o eroare")


Traceback (most recent call last):
...
__main__. Test_eroare: A aparut o eroare

Mai sus s-a creat excepția definită de utilizator denumită Test_eroare care moștenește clasa
Exception. Această excepție nouă, ca și alte excepții, poate fi ridicată folosind instrucțiunea
raise cu un mesaj opțional de eroare.

Se recomandă să se păstreze definițiile tuturor excepțiilor definite de utilizator într-un fișier


separat, dacă numărul lor mare o justifică. Acesta poate lua numele exceptions.py sau
errors.py (sau alt nume).

O clasă definită de utilizator poate fi implementată ca orice altă clasă făcând orice face o clasă
normală. În general, totuși, această clasă trebuie să fie simplă și concisă. Frecvent, în multe
aplicații, se declară o clasă excepție de bază utilizator, iar alte excepții sunt derivate din această
clasă.
Exemplul 17.9 Excepție definită de utilizator
# Se cere introducerea unui numar pana ce acesta este dat corect. Se oferă
#o indicatie ajutatoare precizând daca numarul este mai mare sau mai mic
#decat cel asteptat, corect.
class Eroare(Exception):
"""Clasa de baza pentru alte exceptii"""
pass
class Eroare_Val_Mica(Eroare):
"""Exceptie ridicata cand valoarea introdusa este mai mica"""
pass
class Eroare_Val_Mare(Eroare):
""" Exceptie ridicata cand valoarea introdusa este mai mare"""
pass
# Numarul de ghicit este:
numar = 10
# se fac incercari pana se ghiceste
while True:
try:
240 Erori și excepții

nx = int(input("Introdu un numar: "))


if nx < numar:
raise Eroare_Val_Mica
elif nx > numar:
raise Eroare_Val_Mare
break
except Eroare_Val_Mica:
print("Valoarea este prea mica, mai incearca!")
print()
except Eroare_Val_Mare:
print("Valoarea este prea mare, mai incearca!")
print()
print("Corect! Felicitari! ")

In exemplul de mai sus s-a definit clasa de baza Eroare. Celelalte două excepții care sunt
ridicate în secvența de program: Eroare_Val_Mica , Eroare_Val_Mare, sunt derivate din
această clasă.
Observație. Mai sunt și alte metode de a defini clase utilizator.
17.4.2 Excepția AssertionError
Un caz special în rularea unui program este cel în care se așteaptă îndeplinirea unei condiții
date (assert) pentru a continua rularea, neîndeplinirea acesteia oprind programul. Altfel spus,
dacă condiția este True, programul continuă, iar dacă este False, se ridică o excepție
AssertionError.

Exemplul 17.10 #verificarea sistemului de operare pe care se va rula programul


#Programul este destinat rulării pe sistemul de operare Windows
#programul a fost verificat pe Linux-Ubuntu
import sys
assert(("Windows" in sys.platform),"Programul ruleaza doar pe Windows")

Output #rulare pe Linux-Ubuntu


Traceback (most recent call last):
...
assert 'Windows' in sys.platform,"Programul ruleaza doar pe Windows"
AssertionError: Programul ruleaza doar pe Windows

17.5 Suprascrierea (supraîncărcarea) claselor excepție


Clasele excepție se pot suprascrie (supraîncărca) pentru a primi și alte argumente, conform
necesităților. Suprascrierea claselor excepție se poate studia în cadrul programării orientate
spre obiecte (POO).

Exemplul 17.11

class SalariulNuEsteInInterval(Exception):
"""Excepție ridicată pentru erori la introducerea valorii salariului.
Semnificatia parametrilor:
salariul – salariul introdus (poate genera eroare)
mesaj – explicatia erorii
"""
def __init__(self, salariul, mesaj="Salariul nu este in intervalul
(5000, 15000)"):
self.salariul = salariul
241 Erori și excepții

self.mesaj = mesaj
super().__init__(self.mesaj)
salariul = int(input("Introduceti valoarea salariului:"))
if not 5000 < salariul< 15000:
raise SalariulNuEsteInInterval(salariul)

Output
Introduceti valoarea salariului: 2000
Traceback (most recent call last):
. . .
raise SalariulNuEsteInInterval(salariul)
SalariulNuEsteInInterval: Salariul nu este in intervalul (5000, 15000)

În exemplul de mai sus, s-a suprascris funcția de inițializare a clasei Exception pentru a
accepta argumentele utilizator salariul și mesaj. Apoi, constructorul clasei părinte
Exception este apelat manual cu argumentul self.mesaj utilizând super().

În exemplul care urmează, metoda moștenită __str__ a clasei Exception este utilizată pentru
a afișa mesajul corespunzător când excepția SalariulNuEsteInInterval este ridicată. Pentru
adaptare, metoda __str__ este suprascrisă.
Exemplul 17.12
class SalariulNuEsteInInterval(Exception):
"""Excepție ridicată pentru erori la introducerea valorii salariului.
Semnificatie:
salariul – salariul introdus (poate genera eroare)
mesaj – explicatia erorii
"""
def __init__(self, salariul, mesaj="Salariul nu este in intervalul
(5000, 15000)"):
self.salariul = salariul
self.mesaj = mesaj
super().__init__(self.mesaj)

def __str__(self):
return f'{self.salariul} -> {self.mesaj}'

salariul = int(input("Introduceti valoarea salariului: "))


if not 5000 < salariul< 15000:
raise SalariulNuEsteInInterval(salariul)

Output
Introduceti valoarea salariului: 2000
Traceback (most recent call last):
. . .
raise SalariulNuEsteInInterval(salariul)
SalariulNuEsteInInterval: 2000 -> Salariul nu este in intervalul (5000,
15000)

17.6 Întrebări, exerciții și probleme


17.6.1 Care este diferența dintre erori și excepții ?
R17.6.1 Eroarea este un eveniment care duce la oprirea programului, consecință pe care
programatorul nu o poate evita sub nici o formă și pentru care nu se recomandă să încerce
scrierea unei rutine de tratare. În general eroarea nu poate fi controlată sau anticipată. Exemple
de erori:
242 Erori și excepții

- OutOfMemoryError,
- StackOverflowError, etc.
Excepția este un eveniment care poate produce oprirea programului, dar pe care programatorul
îl poate testa și îl poate ocoli, astfel încât să se evite oprirea programului. Excepția poate fi
controlată și anticipată. Exemple de excepții:
- FileNotFoundException,
- ParseException,
- ZeroDivisionError, etc.
17.6.2 O instrucțiune try poate avea mai multe instrucțiuni except?
R17.6.2 Da, acest lucru este folosit când blocul try conține instrucțiuni care pot genera mai
multe tipuri de excepții.
17.6.3 Se poate scrie o clauză except generică, ce poate trata orice excepție?
R17.6.3 Da. In clauza except nu se specifică nimic, se va activa pentru oricare excepție
determinată de blocul din clauza try.
17.6.4 După o clauză except se poate include o clauză else. Această clauza else se poate
combina cu if sau cu elif?
R17.6.4 Codul din blocul else se execută dacă blocul try nu ridică nici o excepție. În acest
bloc se poate plasa partea de cod care nu are nevoie de protejarea prin clauza try. Nu este
posibil în nici un fel combinarea acestui else cu if sau elif. Singura posibilitate este
adăugarea instrucțiunii if după clauza else, după modelul următor:
try:
. . .
except
. . .
else:
. . .
if …:
. . .
elif . . .:
. . .
else:
. . .

17.6.5 Se poate scrie o clauză except pentru mai multe excepții (excepții multiple) ?
R17.6.5 Da, după cum se poate constata din modelul următor:
try
. . .
Operatii
. . .
except(Exceptie1 [, Exceptie2 [, . . . ExceptieN]]])
else
Bloc executabil daca nu exista nici o exceptie

17.6.6 În câte moduri se poate ridica o excepție de către programator?


R17.6.6 Excepțiile se pot ridica în mai multe feluri prin instrucțiunea raise. Sintaxa:
raise [Exceptie, [, args [, traceback]]]

Exceptie – este tipul de excepție (de exemplu NameError)


243 Erori și excepții

args – una sau mai multe valori. Dacă lipsește (este opțional), valoarea sa este None.
traceback – un obiect traceback (rar folosit în practică)
17.6.7 Să se arate că orice excepție moștenește clasa de bază Exception. Se poate utiliza codul
de la exercițiul 17.3 (cu utilizarea clauzelor try și except, cu moștenirea clasei de bază)
R17.6.7
import sys #modulul sys conține tipul de excepție
xlista = ['s', 0, 7]
for element in xlista:
try:
print("Elementul este:", element)
r = 1/int(element)
break
except:
print("Atentie!", element.__class__, Exception.__init__, "a avut
loc.")
print("Următorul element.")
print()
print("Inversul lui:", element, "este", r)

Output
Elementul este: s
Atentie! <class 'str'> <slot wrapper '__init__' of 'Exception' objects> a
avut loc.
Următorul element.

Elementul este: 0
Atentie! <class 'int'> <slot wrapper '__init__' of 'Exception' objects> a
avut loc.
Următorul element.

Elementul este: 7
Inversul lui: 7 este 0.14285714285714285

17.6.8 Arătați cum se poate asigura că data introdusă este un număr, folosind o buclă while.
R17.6.8
while True:
try:
numar = int(input('Introduceti un numar:'))
break
except ValueError:
print('Acesta nu este un numar, mai incercati')

Output
Introduceti un numar:f
Acesta nu este un numar, mai incercati
Introduceti un numar:%
Acesta nu este un numar, mai incercati
Introduceti un numar:5

17.6.9 Fie dat un dicționar unde cheia este un număr întreg, iar valoarea o sumă de bani (de
exemplu: d = [{6:350}, {23:2345}, {35:890}, {52:1234}). Să se scrie un program care să
parcurgă dicționarul pe baza valorilor întregi dintr-un interval și să se totalizeze suma de bani.
Dacă în dicționar nu există cheia corespunzătoare unui index, se va continua generând o
excepție.
244 Erori și excepții

R17.6.9
d = {2:350, 3:2345, 35:890, 52:1234}
tot = 0
for i in range(len(d)):
print(i)
try:
tot += d[i]
except KeyError:
print('Nu exista cheia =', i)
i += 1
print('Total: ', tot)

Output
0
Nu exista cheia = 0
1
Nu exista cheia = 1
2
3
Total: 2695
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 18

Expresii regulate RegEx


18.1 Definiție și utilizare
Definiție. O expresie regulată (Regular Expression, RegEx) este o secvență de caractere
definind un șablon de căutare. Șabloanele definite utilizând RegEx sunt utile la potrivirea
șirurilor căutate.
Expresiile regulate sunt deosebit de importante în prelucrarea datelor, în multe domenii, de
exemplu în introducerea datelor după formulare, în exploatarea bazelor de date cu caracter
economic, analiza datelor introduse prin sisteme interactive, etc.
Exemplul 18.1 #Șablon de încadrare între două caractere (cinci caractere)
^m...r$

Șirul de 5 caractere de mai sus definește un șablon RegEx. Șablonul are semnificația: oricare
șir de cinci caractere alfanumerice începând cu m și terminând cu r. Modul de funcționare
este prezentat în tabelul de mai jos.
Expresie ^m...r$
Șir mortar motor mosor Motor montor
Potrivire nu da da nu nu

Observație. Python are un modul numit re pentru lucrul cu RegEx.

Exemplul 18.2 #Căutare după șablon șir încadrat, cu 3 caractere interioare

import re
pattern = '^a...t$' #sablonul are 5 caractere (3 interioare)
sir_testat = 'avizat' #sirul are 6 caractere (4 interioare)
resultat = re.match(pattern, sir_testat)
if resultat:
print("Cautare reusita")
else:
print("Cautare nereusită")

Output
Cautare nereusită

Exemplul 18.3 #Căutare șir după șablon șir încadrat, cu 4 caractere interioare
import re
pattern = '^a....t$'
sir_testat = 'avizat'
resultat = re.match(pattern, sir_testat)
if resultat:
print("Cautare reusita")
else:
print("Cautare nereusită")

Output
Cautare reusita
246 Expresii regulate

În secvențele de program de mai sus s-a utilizat funcția re.match() pentru a căuta după un
șablon/formă/pattern în șirul test_string. Metoda întoarce obiectul “potrivit”/recunoscut, în caz
de succes al căutării. Altfel, întoarce None.
În modulul re sunt definite mai multe funcții pentru a lucra cu RegEx. Pentru a le utiliza, sunt
necesare câteva cunoștințe despre expresii regulate.

18.2 Specificarea șabloanelor/pattern-urilor utilizând RegEx


Metacaractere. Pentru specificarea expresiilor regulate se utilizează 11 simboluri denumite
metacaractere, interpretate într-un mod special de mașina RegEx. Acestea sunt: [] . ^ $ * + ?
{} () \ |
Metacaracterele se utilizează în cadrul unor secvențe speciale, conform descrierii din tabelul
care urmează.

Tabelul 18.1 Metacaractere și șiruri potrivite

Simbolul Șirul pe care îl potrivește


[] Paranteze patrate – specifică un set de caractere (mulțime)
. (punctul, dot) Un singur caracter (cu excepție caracterul newline).
\ Semnalează o secvență specială (ex.: “\d” )
+ Una sau mai multe potriviri
^ Potrivire de la început
$ Potrivire la sfârșit
? Zero sau una din expresiile anterioare (de ex.: \d+ potrivește zero sau un
digit / cifră)
* Zero sau mai multe din expresiile anterioare (de ex.: \d* potrivește
zero, unul, sau mai mulți digiți/cifre)
{} Specifică exact numărul de potriviri
| Sau
() Potrivește orice altă expresie regulată ce se află în paranteze și indică
startul și sfârșitul grupului.
[] Parantezele patrate specifică un set de caractere (o mulțime) care se dorește a fi potrivit, ca
în exemplul care urmează.

Expresie [abc]
Șir a ac televizor minerala
Potrivire da (1) da (2) nu da (2)

Schema [abc] se va potrivi dacă șirul căutat va conține a, b sau c.


Se poate specifica un interval de caractere în interiorul parantezelor patrate.
• [a-e] este la fel ca [abcde]
• [1-4] este la fel ca [1234]
247 Expresii regulate

•[0-39] este la fel ca [01239]


•[0-5][0-9] întoarce o potrivire pentru un număr de două cifre în intervalul 00-59.
•[A-Z] orice caracter între A și Z (upper-case, litere mari)
•[a-z] orice caracter între a și z (lower case, litere mici)
•[A-Za-z] orice caracter între a și z (case-insensitive)
•[^/]+ unul sau mai multe caractere până la un slash direct (și neincluzându-l)
Un caracter se poate inversa (complementa) cu simbolul ^ plasat după paranteza de început.
• [^abc] înseamnă orice caracter exceptând a sau b sau c.
• [^0-9] înseamnă orice caracter non-digit.

. – Punctul. Un punct potrivește un singur caracter (cu excepția newline '\n').

Expresie ..
Șir a ac televizor programare
Potrivire nu (0) da (1) da (4) da (5)

^ - Caret. Simbolul caret ^ se utilizează pentru a verifica dacă un șir începe cu un anumit
caracter.

Expresie ^a
Șir a abc bac
Potrivire Da da nu
Expresie ^al
Șir a abc altul
Potrivire nu nu da

$ - Dollar. Simbolul $ este utilizat pentru a verifica dacă un șir se termină cu un anumit
caracter.

Expresie a$
Șir a apa car
Potrivire da da nu

* - Asterix (star). Simbolul * verifică zero sau mai multe apariții care se potrivesc cu el.

Expresie ma*t
Șir material mtt marcat maat programat
Potrivire da da nu da da

+ - Plus. Simbolul + verifică una sau mai multe apariții care se potrivesc cu el.

Expresie ma+t
Șir material mtt marcat maat programat
Potrivire da nu nu da da

? – Semnul întrebării (question mark). Simbolul ? verifică zero sau cel mult una apariții
care se potrivesc cu el.

Expresie ma?t
Șir material mtt marcat maat programat
Potrivire da da nu nu da
248 Expresii regulate

{}– Acolade (braces) . Considerând codul {n,m}, acesta înseamnă că minimum n și


maximum m repetiții ale șablonului trebuie să se verifice.

Expresie a{}
Șir material mtt marcat maaaat
Potrivire da nu da nu
Expresie [0-9]{2,3}
Șir pt15xyz c2345x 6 sau 7 77 și 888
Potrivire da nu nu da

| - Alternarea. Bara verticală | semnifică alternarea, sau operația OR

Expresie c|t
Șir material mtt marcat mare cip
Potrivire da (1) da (2) da (2) nu Da (1)

Șablonul c|t găsește potrivire în șirurile care conțin fie c, fie t, sau ambele.

() – Group. Parantezele ( ) sunt folosite pentru a grupa sub-scheme. De exemplu,


(a|b|c)xz potrivește orice șir care corespunde fie lui a sau b sau c, urmat de xz.
Expresie (a|b|c)xz
Șir ab xz abxz axz cabxz
Potrivire nu da(1) da (2)

\ - Backslash-ul \ este utilizat pentru a obține diverse caractere inclusiv toate metacaracterele.
De exemplu, \$a se potrivește dacă un șir conține $ urmat de a. Aici, $ nu este interpretat de
RegEx într-un mod special. Dacă există o nesiguranță în ce privește semnificația specială a
unui caracter, se poate pune caracterul \ în fața acestuia. Ca urmare, caracterul nu este tratat în
vreun mod special.

Secvențe speciale. Permit scrierea mai ușoară a schemelor frecvente. În continuare se prezintă
câteva secvențe speciale mai des întâlnite în practică.

\A – Potrivire, caracterele specificate se află la începutul șirului.

Expresie \Aale
Șir alegator alfabet alergător al
nostru
Potrivire da nu da nu

\b - Potrivire, caracterele specificate se află la începutul sau la sfârșitul șirului.

Expresie \ban an\b


Șir analiză antecedent randament muntean castan dans
Potrivire da da nu da da nu

\B – Opusul lui \b. Potrivire, caracterele specificate nu se află la începutul sau la sfârșitul
cuvântului.
249 Expresii regulate

Expresie \Ban an\B


Șir analiză bani macara banal castan muntean
Potrivire nu da da da nu nu

\d – Potrivire pentru orice cifră zecimală. Echivalent cu [0-9]

Expresie \d
Șir 12abc3 abbcd “z32A%9Sq Windows 11
Potrivire da(3) nu da(3) da(2)

\D – Potrivire pentru orice caracter diferit de o cifră zecimală. Echivalent cu [^0-9]

Expresie \D
Șir 1ab34”50 2021 Python 3.10 8th
Potrivire da(3) nu da(7) da(2)

\s– Potrivire pentru cazul când șirul conține orice caracter spațiu alb (whitespace).
Echivalent cu [ \t\n\r\f\v].

Expresie \s
Șir Python RegEx PythonRegEx Python
RegEx
Potrivire da (1) nu da(1)

Observație. Secvența este utilizată pentru divizarea unui șir (split).


>>>print((re.split(r'\s', 'acesta este un sir care va fi divizat in
cuvinte')))
['acesta', 'este', 'un', 'sir', 'care', 'va', 'fi', 'divizat', 'in',
'cuvinte']

\S - Potrivire pentru cazul când șirul conține caractere non-spațiu alb


(non-whitespace). Echivalent cu [^\t\n\r\f\v].

Expresie \S
Șir ab Windows 11 “ “
3spații
Potrivire da(2) da(9) nu

\w– Potrivire, orice caracter alfanumeric (cifră sau literă) Echivalent cu [a-zA-Z0-9_].
Observație: underscore ( _ ) este considerat caracter alfanumeric.

Expresie \w
Șir 12&” ;c %”#: s-au testat d’ale carnavalului
Potrivire da (3) nu da(10) da(17)

>>>sir = "Acesta este sirul $$$ de test"


>>>print(re.findall("t\w", sir))
['ta', 'te', 'te']
>>>print(re.findall("t\w+", sir))
['ta', 'te', 'test']

\W - Potrivire, orice caracter non - alfanumeric. Equivalent to [^a-zA-Z0-9_]


250 Expresii regulate

Expresie \W
Șir 1a2&c abcde v-au răspuns La multi ani!
Potrivire da(1) nu da(1) da(1)

>>>print(re.findall("\W", sir))
[' ', ' ', ' ', '$', '$', '$', ' ', ' ']
>>>print(re.findall("\W+", sir))
[' ', ' ', ' $$$ ', ' ']

\Z – Potrivire, caracterul specificat se află la sfârșitul șirului.

Expresie cd\Z
Șir a fxc ecd a fxc ecd x Sa 65cd zszs
Potrivire da nu nu

18.3 Funcții pentru lucrul cu expresii regulate


Python are un modul denumit re pentru lucrul cu expresii regulate, care trebuie importat.
import re

Modulul conține mai multe funcții și constante pentru lucrul cu expresii regulate.
1) re.match(). Funcția match() va căuta modelul expresiei regulate sablon în sir
(începând cu primul caracter, doar în primele caractere) și va returna True dacă un obiect este
găsit (la prima apariție) , alfel va returna False (se iau în considerare indicatorii).
Sintaxa:
re.match(sablon, sir, indicatori = 0)

2) re.findall(). Metoda întoarce o listă de șiruri conținând toate potrivirile sablonului în


șir.
Sintaxa:
rezultat = re.findall(sablon, sir)

Observație. Dacă șablonul nu este găsit, re.findall()va întoarce o listă vidă.

3) re.split(). Metoda divizează un șir inițial în care există o potrivire și întoarce o listă a
șirurilor în care s-a făcut divizarea șirului inițial (sablonul reprezintă separatorii dintre șiruri).
Sintaxa:
re.split(sablon, sir)

4) re.sub(). Metoda întoarce un șir în care potrivirile găsite sunt înlocuite cu conținutul
variabilei sir_substituire.
Sintaxa:
rezultat = re.sub(sablon, sir_substituire, sir)

5) re.subn() . Similar cu re.sub(), întoarce un tuplu de 2 termeni conținînd noul șir și


numărul de substituiri făcute.
Sintaxa:
rezultat = re.subn(sablon, sir_substituire, sir)
251 Expresii regulate

6) re.search(). Metoda caută în tot șirul și va da numai prima locație unde șablonul are o
potrivire cu șirul. Dacă are succes, re.search() întoarce obiectul potrivit, altfel întoarce None.
Sintaxa:
rezultat = re.search(sablon, sir)

rezultat = re.search(sablon$, sir) – caută la sfârșitul șirului

18.4 Proprietățile obiectului găsit prin potrivire


Ca urmare a aplicării unei funcții de găsire a potrivirii se obține ca rezultat un obiect distinct,
denumit obiectul potrivit (match object). Acest obiect are proprietăți și metode proprii. Lista
întreagă a acestora se poate determina folosind funcția dir().

Metode:
- group(). Metoda întoarce acea parte din șir care s-a potrivit căutării.
- start(). Metoda întoarce indexul de start al subșirului găsit prin potrivire.
- end(). Metoda întoarce indexul de sfârșit al subșirului găsit prin potrivire.
- span(). Metoda întoarce un tuplu care conține indecșii de început și sfârșit ai potrivirii
găsite.
Atribute:
- re. Atributul întoarce obiectul expresie regulată care s-a utilizat la căutarea prin potrivire..
- string. Atributul întoarce șirul în care s-a căutat.

18.5 Utilizarea prefixului r sau R înaintea expresiei regulate


Utilizarea caracterelor r sau R înaintea expresiei regulate semnifică un șir brut (raw string). De
exemplu, “\n” semnifică trecerea la o linie nouă, dar r“\n” înseamnă doar două caractere
distincte: backslash-ul “\” urmat de n. Backslash-ul este tratat în acest mod ca un simplu
caracter.
Exemplul 18.4 #Utilizarea prefixului r determină tratarea caracterelor în mod distinct
import re
sir = '\n and \r sunt secvente speciale (escape)'
rezultat = re.findall(r'[\n\r]', sir)
print(rezultat)

Output
['\n', '\r']

Exemplul 18.5 #Utilizarea funcției match()


import re
sir = "Acesta este sirul 12 de test"
print(sir)
z = re.match("t\w", sir)
print(z)
sir = "tAcesta este sirul 12 de test"
print(sir)
z = re.match("t\w", sir)
print(z)
252 Expresii regulate

Output
Acesta este sirul 12 de test
None
tAcesta este sirul 12 de test
<re.Match object; span=(0, 2), match='tA'>

Exemplul 18.6 #Extragere numere dintr-un sir cu re.findall() și șablon '\d+'


import re
sir = 'ab$56lk98$$$nbg123ASDRS'
sablon = '\d+'
cifre = re.findall(sablon, sir)
print(cifre)

Output
['56', '98', '123']

Exemplul 18.7 #Divizarea unui șir cu re.split() și șablon '\d+'


import re
sir = 'ab$56mk98$$$nbg123ASDRS'
sablon = '\d+'
sirulete = re.split(sablon, sir)
print(sirulete)

Output
['ab$', 'mk', '$$$nbg', 'ASDRS']

Observație. Dacă șablonul nu este găsit, re.split() întoarce o listă conținând șirul original.
De asemenea, se poate utiliza argumentul maxsplit pentru metoda re.split(), prin care se
precizează numărul maxim de divizări permis.
Exemplul 18.8 #Diviziune cu număr maxim de diviziuni cu re.split()
import re
sir = 'ab$56mk98$$$nbg123ASDRS'
sablon = '\d+'
#nr. max. de divizari (maxsplit) = 1
sirulete = re.split(sablon, sir, 1)
print(sirulete)
Output
['ab$', 'mk98$$$nbg123ASDRS']

Observație. Valoarea implicită pentru maxsplit este 0, cu semnificația că toate diviziunile


sunt posibile.
Exemplul 18.9 #Înlocuirea potrivirilor găsite; eliminarea spațiilor albe cu re.sub()
import re
# sir multilinie
sir = '''iuhbYTF 986hbjb GGHH\
WEW 87 SAA\
fgEE \n 075ez'''
# gasirea tuturor spatiilor albe
sablon = '\s+'
sir_substituire = '' # inlocuirea cu un sir vid
sir_nou = re.sub(sablon, sir_substituire, sir)
print(sir_nou)

Output
iuhbYTF986hbjbGGHHWEW87SAA\fgEE075ez
253 Expresii regulate

Exemplul 18.10 #Eliminarea tuturor spațiilor albe cu re.subn()


import re
# sir multilinie
sir = '''iuhbYTF 986hbjb GGHH\
WEW 87 SAA\
fgEE \n 075ez'''
# gasirea tuturor spatiilor albe
sablon = '\s+'
sir_substituire = '' # inlocuire cu un sir vid
sir_nou = re.subn(sablon, sir_substituire, sir)
print(sir_nou)
#se afiseaza un tuplu care contine si nr de substituiri
Output
('iuhbYTF986hbjbGGHHWEW87SAA\\fgEE075ez', 7)

Exemplul 18.11 # Căutarea unui subșir la inceputul sirului


import re
sir = "Python este un limbaj de programare"
raspuns = re.search('\APython', sir)
if raspuns:
print("Sablon găsit la inceput de sir")
print(raspuns)
else:
print("Sablonul nu a fost gasit la inceputul sirului")
Output
Sablon găsit la inceput de sir
<re.Match object; span=(0, 6), match='Python'>
Exemplul 18.12 #Întoarcerea șirului găsit pentru care s-a făcut căutarea (group())
import re
sir = '39801 356, 2102 1111'
# Se caută un număr din trei cifre urmate de spațiu urmat de un număr din
două cifre.
sablon = '(\d{3}) (\d{2})'
# variabila raspuns conține obiectul găsit prin potrivire.
raspuns = re.search(sablon, sir)
print(raspuns)
if raspuns:
print(raspuns.group())
else:
print("sablonul nu a fost gasit")
Output
<re.Match object; span=(2, 8), match='801 35'>
801 35
Aici, variabila raspuns conține obiectul găsit prin potrivire.
Șablonul (\d{3}) (\d{2}) are două subgrupe: (\d{3}) și (\d{2}). Se pot obține fiecare
din cele două subgrupe, astfel:
>>> raspuns.group(1)
'801'
>>> raspuns.group(2)
'35'
>>> raspuns.group(1, 2)
('801', '35')
>>> raspuns.groups()
('801', '35')
254 Expresii regulate

Exemplul 18.13 #Găsirea indecșilor de start și sfârșit


>>> raspuns.start()
2
>>> raspuns.end()
8
>>> raspuns.span()
(2, 8)

Observație. start() a găsit indexul de început al potrivirii în șir, end() a găsit indexul de
sfârșit al potrivirii în șir, iar span() a dat tuplul conținând cei doi indecși.
Exemplul 18.14 # Întoarcerea expresiei regulate și a șirului utilizați în căutare
>>> raspuns.re
re.compile('(\\d{3}) (\\d{2})')
>>> raspuns.string
'39801 356, 2102 1111'

18.6 Întrebări, exerciții și probleme


18.6.1 Să se scrie un program pentru inserarea de spații între porțiunile unui șir compact care
încep cu literă mare. Ex.: “ManualInstructiuniDeLucru”
R18.6.1
import re
def SeparareLM(sir):
return re.sub(r"(\w)([A-Z])", r"\1 \2", sir)
print(SeparareLM("ManualInstructiuniDeLucru"))
Output
Manual Instructiuni De Lucru

18.6.2 Să se scrie un program pentru găsirea unui șir format dintr-un caracter “x” și un număr
de 0 sau 1 caractere “y”.
R18.6.2
import re
def potrivire(sir):
schema = 'ab?'
if re.search(schema, sir):
return 'Potrivire gasita'
else:
return 'Nu s-a gasit potrivirea'
print(potrivire("a"))
print(potrivire("ab"))
print(potrivire("abc"))
print(potrivire("xabbc"))
print(potrivire("yaabbc"))
Output
Potrivire gasita
Potrivire gasita
Potrivire gasita
Potrivire gasita
Potrivire gasita
18.6.4 Care este sablonul de potrivire a unui subșir compus dintr-un “a” urmat de doi sau trei
“b” într-un alt sir?
R18.6.4 Șablon = ‘ab{2,3}’. Se utilizează cu funcția re.search().
255 Expresii regulate

18.6.5 Care este șablonul de potrivire a unui subșir, conținând un “r”, într-un alt sir?
Exemplificați.
R18.6.5
import re
def potrivire(sir):
schema = ('\w*r.\w*')
if re.findall(schema, sir):
print(re.findall(schema, sir))
return 'Potrivire gasita'
else:
return 'Nu s-a gasit potrivirea'
sir = "greutatea se măsoară în kilograme"
print(potrivire(sir))
Output
['greutatea', 'măsoară', 'kilograme']
Potrivire gasita
18.6.6 Care este șablonul de potrivire a unui subșir, conținând litere mici, litere mari, cifre și
underscore, într-un alt sir?
R18.6.6 Sablonul este sirul '[a-zA-Z0-9_]*'. Exemplu:
>>>import re
>>>sir = "%Ziua de nastere este @25@ Noiembrie 2010"
>>>sir
'%Ziua de nastere este @25@ Noiembrie 2010'
>>>schema = '[a-zA-Z0-9_]*'
>>>print(re.findall(schema, sir))
['', 'Ziua', '', 'de', '', 'nastere', '', 'este', '', '', '', '25', '', '',
'Noiembrie', '', '2010', '']

18.6.7 Fie IP-urile: 876.003.001.45, 123.06.01.023. Se cere să se aducă IP-urile la forma


standard, eliminând zerourile nesemnificative din câmpurile componente.
R18.6.7
>>>sir_ip = '876.003.001.45, 123.06.01.023'
>>>schema = '\.[0]*'
>>>rezultat = re.sub(schema, '.', sir_ip)
>>>print(rezultat)
876.3.1.45, 123.6.1.23
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 19

Ecosistemul Python, prezentare generală


19.1 Calcul numeric și științific
19.1.2 NumPy

NumPy este o bibliotecă sub forma unui pachet Python. Numele reprezintă o abreviere de la
Numerical Python. Biblioteca este formată din obiecte de tip matrice multidimensionale și o
colecție de rutine pentru procesarea acestor obiecte. Numeric, strămoșul NumPy, a fost
dezvoltat de Jim Hugunin. De asemenea, a fost dezvoltat un alt pachet Numarray, având câteva
funcționalități suplimentare. În 2005, Travis Oliphant a creat pachetul NumPy prin
încorporarea caracteristicilor Numarray în pachetul Numeric.
Cu NumPy se pot face următoarele:
- Operații matematice și logice pe tablouri.
- Transformate Fourier și rutine pentru manipularea formelor.
- Operații legate de algebra liniară.
- NumPy are funcții încorporate pentru algebră liniară și generarea de numere aleatoare.
NumPy poate fi considerat un înlocuitor pentru Matlab, dacă este utilizat împreună cu pachete
precum SciPy (Scientific Python) și Matplotlib (bibliotecă de funcții grafice). Succesul pe scară
largă ca înlocuitor pentru MATLAB, cea mai cunoscută platformă pentru calculul tehnic, se
datorează în principal faptului că este open-source.
Practic, alternativa Python și a ecosistemului său la MATLAB este văzută în prezent ca
reprezentând mult mai mult: un mediu de programare mai modern și mai complet.

19.1.3 Vizualizarea datelor

Ipython este un shell interactiv (consolă) Python care are multe caracteristici interesante: acces
la comenzi shell, depanare (debugging), etc.
Pylab este o interfață care permite executarea unor sesiuni interactive, asemănătoare cu
MATLAB din punct de vedere al funcționalității. Pylab este o interfață pentru biblioteca de
funcții de reprezentare grafică, orientată pe obiecte, denumită matplotlib.
Matplotlib este o bibliotecă grafică pentru generarea reprezentărilor grafice științifice 2D și
3D. Dispune de o serie de facilități care permit obținerea de figuri de bună calitate, în multe
formate (png, pdf, svg, eps, etc.) necesare publicațiilor științifice.
Deorece a fost modelată foarte apropiat de MATLAB, majoritatea comenzilor de afișare
grafică, inclusiv parametrii acestora, sunt similare cu ale MATLAB-ului.
Seaborn este o bibliotecă pentru reprezentări statistice ale datelor în Python. Ea este bazată pe
Matplotlib și se află în strânsă legătură cu structurile de date utilizate de Pandas. Scopul
Seaborn este realizarea de reprezentări grafice aspectuoase ale datelor, dar în același timp să
ușureze și înțelegerea acestora și să scoată în evidență detaliile mai importante.
Funcțiile de reprezentare grafică operează asupra tablourilor de date și structurilor dataframe
din Pandas, efectuând toate prelucrările necesare asupra seturilor de date (“dataset oriented”)
257 Ecosistemul Python, prezentare generală

astfel încât din grafice să rezulte maximum de informație. Utilizatorul se poate concentra astfel
mai mult asupra semnificațiilor datelor și mai puțin pe aspectele de programare a graficelor.

19.1.4 Pandas

Pandas este o bibliotecă de o utilitate deosebită destinată analizei datelor (“Python Data
Analysis Library ”), numele său fiind posibil o prescurtare aproximativă pentru “panel data”.
Pandas a fost creată de Wes McKinney in 2008.
Cu și prin Pandas, procesarea datelor poate fi realizată în cinci pași: Încărcare, Pregătire,
Manipulare, Modelare, Analiză.
Pandas este construită pe baza NumPy și SciPy și poate fi văzută ca o simplă API care creează
funcționalitate mai bună pentru lucrul cu date eterogene organizate sub formă de serii de timp
(Series, 1 dimensiune), tablouri de date (DataFrame, 2 dimensiuni), containere de tablouri de
date (Panel, 3 dimensiuni). În consecință, deși nu este revoluționară, Pandas aduce multe
avantaje utilizatorilor.
Utilitatea Pandas apare atunci când se prelucrează date stocate în fișiere și baze de date care
conțin atât numere cât și șiruri, cum ar fi SQL și Excel. În cazul interogării (într-un anumit sens
asemănător modului în care se face în SQL) a unei baze de date în Pandas, se poate fece o citire
directă cu afișare printr-o singură linie de comandă.
În comparație cu NumPy care este proiectat pentru utilizarea general-eficientă a memoriei,
Pandas obține o performanță mai bună pentru tablouri care au un număr de rânduri mai mic de
500000. Indexarea seriilor Pandas este mai lentă decât indexarea tablourilor NumPy.
Instalarea Pandas se poate face cu utilitarul pip, sau automat prin instalarea Anaconda.
Resurse: https://pandas.pydata.org/

19.1.5 SciPy (sub-ecosistem)

În documentația generală legată de Python se utilizează aceeași denumire “Scipy”pentru două


lucruri distincte: (1) sub-ecosistemul și (2) biblioteca pentru calculul științific.
SciPy (pronunțat: ”saipai”) este un sub-ecosistem software open-source pentru matematică,
știință și inginerie, bazat pe Python. Este format din mai multe pachete, printre care de bază
sunt:
- NumPy (calcul numeric N-dimensional),
- SciPy (bibliotecă pentru calcul științific),
- Matplotlib (afișare grafică),
- Ipython (dispune de o consolă interactivă),
- SymPy (matematică simbolică),
- Pandas (bibliotecă pentru lucrul cu structuri de date și analiza datelor)
SciPy este open-source (https://www.scipy.org), sponsorizat de NumFOCUS.
258 Ecosistemul Python, prezentare generală

19.2. Biblioteci Machine Learning & Deep Learning

TensorFlow este o bibliotecă open-source pentru învățare automată (Machine Learning, ML)
și învățare profundă (Deep Learning, DL).
TensorFlow fost produs de echipa Google Brain și a fost făcut public prima dată în noiembrie
2015.
TensorFlow combină algebra de calcul și tehnicile de optimizare pentru calcularea ușoară a
multor expresii matematice. Caracteristicile cele mai importante ale TensorFlow sunt:
- definește, optimizează și calculează cu ușurință expresiile matematice cu ajutorul unor
tablouri multidimensionale numite tensori.
- include un suport de programare pentru rețele neuronale profunde (Deep Neural Network,
DNN) și tehnici de învățare automată (ML).
- TensorFlow folosește unitatea de calcul GPU, automatizând managementul acesteia. De
asemenea, include metode de optimizare a datelor utilizate și a funcționării GPU.
Scikit-Learn este o bibliotecă de algoritmi din domeniul Machine-Learning pentru rezolvarea
de probleme de tip clasificare și învățare supervizată și nesupervizată. Este ușor de folosit.
Caracteristicile sale sunt:
- Unealtă simplă și eficientă pentru analiza datelor.
- Ușor utilizabilă în contexte diverse.
- Construită numai pe baza NumPy, SciPy și Matplotlib.
- Open-source, licență BSD utilizabilă comercial.
- Are aplicații în: clasificare, regresie, clustering, reducerea dimensionalității datelor,
preprocesare, modelare, etc.
Resurse: http://scikit-learn.sourceforge.net și https://scikit-learn.org/
Keras este o bibliotecă open-source în Python pentru rețele neuronale artificiale (DNN).
Acționează ca o interfață pentru biblioteca TensorFlow 2.0.
A fost dezvoltat ca parte a unui proiect de cercetare și are ca prim autor și susținător un inginer
de la Google, François Chollet.
Keras conține numeroase blocuri preconstruite, componente pentru rețelele neuronale, ca:
layere, funcții de activare și optimizare, suport și unelte de lucru cu date de tip imagine și text
necesare pentru modelele de rețele neuronale tip convoluțional și recurent. Permite antrenarea
modelelor pe dispozitive GPU (Graphics Processing Unit) și TPU (Tensor Processing Unit).
Theano este o bibliotecă pentru Deep Learning (pentru antrenarea modelelor DNN),
dezvoltată de Universitatea din Montreal în 2007.
Theano este construit pe baza NumPy, având o interfață similară cu NumPy, fiind o bibliotecă
de funcții care permite definirea, optimizarea și evaluarea eficientă a expresiile matematice
care implică tablouri multi-dimensionale.
Mulți cercetători din domeniul Deep Learning s-au bazat pe Theano în dezvoltarea de modele
Deep Learning.
Autorul Theano, Yoshua Bengio, a anunțat in 2017 că dezvoltarea Theano încetează.

Pytorch este o bibliotecă open-source pentru Machine Learning bazată pe biblioteca Torch,
utilizată anterior pentru aplicații de vederea mașinilor și procesarea limbajului natural,
dezvoltată de Facebook în laboratorul AIResearch (FAIR).
259 Ecosistemul Python, prezentare generală

Pytoch dispune de două caracteristici principale:


- Asigură calculul tensorial (pornind de la modelul NumPy), cu posibilitatea execuției
accelerate pe unitatăți de tip GPU.
- Face posibilă construcția modelelor de tip Deep Neural Networks.

19.3 Dezvoltare Web (Web Development)


Django este un cadru de lucru pentru realizarea de situri web. Simplifică modul de administrare
a aplicației prin separarea codului Python de codul HTML prin utilizarea schemei MVC
(model, view, controller), redefinită astfel: view = template, controller = view. În consecință,
în cazul aplicației Django, schema se numește “MVT”.
Django utilizează conceptul ORM (Obiect-Relational Mapping) care îi dă posibilitatea să
interacționeze mai simplu cu bazele de date SQL, fără a folosi direct limbajul SQL. Mai exact,
se face o echivalare a tabelelor relaționale cu clasele de obiecte, astfel încât nu mai este
necesară utilizarea instrucțiunilor SQL pentru prelucrarea datelor.
Pyramid este un cadru Python de dezvoltare web: mic, rapid și practic. Pyramid poate fi utilizat
pentru construirea atât a site-urilor mici cât și a celor mari și prezintă avantajul că impune
relativ puține constrângeri de proiectare dând o libertate mai mare proiectantului în realizarea
softwarelui.

Flask este un “micro-cadru-de-lucru” utilizat în principal pentru construirea de aplicații (situri


web) mai mici, cu cerințe simple.

Bottle. WSGI (Web Server Gateway Interface) este o specificație care descrie cum comunică
un server web cu aplicațiile web și cum aplicațiile web pot fi înlănțuite pentru a procesa o
cerere. Bottle este un micro-cadru de lucru de tip WSGI pentru Python. Bottle este distribuit
ca un singur fișier modul și nu are alte dependențe în afară de Python.

PyScript este un cadru de lucru recent dezvoltat și în curs de testare care permite crearea de
aplicații Python într-un browser. Dezvoltatorii PyScript încă nu recomandă utilizarea acestuia
în “production”, deoarece este posibil să aibă unele deficiențe și să urmeze numeroase
modificări.
Alte cadre de lucru pentru Web Development mai sunt și Tornado, CherryPy, Web2py, etc.

19.4 Dezvoltarea interfețelor grafice (GUI Development)


Din categoria componentelor ecosistemului pentru construirea de interfețe grafice (GUI) sunt
și aplicațiile: tkinter, PyQt, PySide, Kivy, wxPython. Acestea sunt prezentate mai pe larg în
capitolul 20.
260 Ecosistemul Python, prezentare generală

19.5 Biblioteci pentru procesarea imaginilor (Image Processing


Libraries)
Mahotas, SimpleITK, Pillow, Scikit-Image, openCV se vor prezenta în capitolul 25.

19.6 Dezvoltare software (Software Development)


Buildbot este un sistem IDE de planificare a joburilor executabile în mod paralel și distribuit
în momentele în care resursele sunt disponibile. Asigură urmărirea și raportarea stării acestor
joburi. Este alcătuit din unul sau mai mulți “master” și o colecție de “worker”.

Trac este un sistem open-source, de dezvoltare a proiectelor software pentru web, de urmărire
a bug-urilor, controlul versiunii și pagini wiki.

Roundup este un sistem de urmărire a ideilor participanților la discuții și de notificare a părților


interesante. Se poate utiliza fie cu interfață în browser în web, fie în linie de comandă.

19.7 Administrarea sistemelor (System Administration)


Ansible este un software produs de Rad-Hat fiind o platformă pentru automatizarea unor
procese de operare, administrarea și luarea de decizii IT într-o varietate de domenii. Este utilizat
de Cisco, etc.

Salt (SaltStack) este un software open-source pentru automatizarea unor procese IT pe baza
evenimentelor, execuția la distanță a taskurilor și administrarea configurațiilor. Este utilizat de
Linkedin, NASA, etc.

Open Stack SDK Python este unul din serviciile Open Stack. Este destinat realizării unui SDK
(Software Development Toolkit), într-o interfață unitară, în ajutorul clienților bibliotecilor și
interfețelor Python.

19.8 Biblioteci pentru dezvoltarea jocurilor (Game Development


Libraries)
PyGame este un set de module pentru scrierea de jocuri video. Este de fapt o legătură scrisă în
Python la biblioteca standardizată deja pentru mai multe platforme, denumită SDL (Simple
Direct Media Layer), scrisă în C/C++, care asigură acces low-level la dizpozitive hardware prin
OpenGL și Direct3D.

PyGlet este un software open-source, o bibliotecă pentru dezvoltarea jocurilor și altor aplicații
cu interfață grafică complexă pe cele mai cunoscute platforme (Windows, Mac OS X și Linux).

PyOpenGL este cel mai cunoscut software open-source multiplatformă care leagă Python de
OpenGL. Este interoperabil cu un număr de biblioteci GUI: wxPython, PyGame, PyQt,
PySyde, PyGTK, Tkinter.
261 Ecosistemul Python, prezentare generală

Arcade este o bibliotecă Python pentru realizarea jocurilor cu grafică și sunete. La fel ca și
PyGame, este destinat jocurilor 2D, dar spre deosebire de PyGame care este orientat spre
grafica rastru, Arcade utilizează OpenGL (grafică vectorială) și are susținere pentru utilizarea
GPU pentru accelerarea randării, fiind foarte rapid.

Panda3D este un cadru de lucru open-source pentru randare 3D și jocuri real-time 3D,
simulări, vizualizări, experimente. Poate fi utilizată fie din Python, fie din C/C++.

19.9 Biblioteci pentru automatizarea testărilor (Automation


Testing Libraries)
Splinter este o unealtă open-source pentru testarea aplicațiilor web.

Robot este un cadru open-source de automatizare a operatorilor de testare a aplicațiilor


conform specificațiilor ATDD (Acceptance Test Driven Development) .

Behave aparține tehnologiei de tip “dezvoltare condusă de comportament” (Behavior–Driven


Development, BDD), fiind o tehnică de dezvoltare de tip “agile development” care încurajează
colaborarea între dezvoltatori, întrebările și răspunsurile și participarea ne-specialiștilor în
proiectele software. BDD se concentrează pe înțelegerea deplină a comportării softului dorit
prin discuții între creatorii săi.

PyUnit este un cadru de test standard pentru Python permițând automatizarea testării,
distribuirea codului pentru teste, agregarea testelor în colecții, independența testelor, etc.

PyTest este un cadru de lucru care permite utilizarea secvențelor de test în Python, simple sau
complexe, pentru baze de date, API, UI, etc.

19.10 Web Scrapping Libraries


Web scrapping este un termen pentru diverse metode utilizate pentru colectarea informațiilor
din internet. În general, acest lucru este făcut cu software care simulează comportamentul uman
de accesare a web-ului pentru colectarea unor informații specifice de pe diverse site-uri web.
Altă denumire pentru web scrapping ar putea fi “extragerea datelor din web”, sau “recoltarea
datelor din web” (web harvesting). Printre tehnicile de web scrapping sunt: copy-paste, text
pattern matching, HTTP programming, HTML parsing, semantic-annotation recognition, etc.

Requests este o aplicație pentru simplificarea emiterii cererilor de tip HTTP/1.1. Scopul cererii
efectuate de client este de a accesa o resursă pe un server utilizând URL.

Beautiful Soap este un software utilizat pentru extragerea datelor din pagini scrise într-un
limbaj de marcare (HTML, XML, etc.).

Selenium este un software open-source care permite testarea automată a aplicațiilor web în
raport cu browserele. Testarea manuală a unei aplicații web pentru a vedea comportarea față
262 Ecosistemul Python, prezentare generală

de un număr imens de browsere înseamnă un număr uriaș de ore de muncă pentru echipele de
testare. Selenium este destinat să evite acest efort.

lxml este o bibliotecă Python care permite prelucrarea ușoară a fișierelor XML și HTML și
care mai poate fi utilizată de asemenea pentru web scrapping. Se utilizează cu un API simplu
și puternic din Python pentru tratarea fișierelor XML și HTML.

Scrapy este un software open-source pentru extragerea informațiilor din site-ul web. Este
primul software de acest gen scris în Python. Inițial (2008) a fost creat pentru web crowling,
dar apoi a fost utilizat și pentru extragerea datelor.
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 20

Interfețe grafice utilizator (GUI)


20.1 Introducere în GUI
20.1.1 Generalități

Python este un limbaj de programare interactiv și în consecință atașarea unui cadru de lucru
(framework) de programare grafică (Graphical User Interface, GUI) este o chestiune naturală
și relativ simplu de realizat.
Python dispune în prezent de o gamă variată de opțiuni de interfețe de lucru de tip grafic (uzual,
în română, “interfețe GUI”). Unele dintre acestea sunt multiplatformă, sau multibrowser, altele
sunt specifice. Exemple: tkinter, PyQT, Kivy, wxPython, PyGTK, PyKDE, etc. O listă
completă a interfețelor de tip GUI se află la adresa:
https://wiki.python.org/moin/GuiProgramming
Cele mai cunoscute interfețe de tip GUI sunt prezentate pe scurt în continuare.

20.1.2 Tkinter
Tkinter este cadrul GUI standard al limbajului Python, care în mod obișnuit se instalează la
pachet cu Python. Denumirea tkinter provine de la tk interface.
Observație. Tk este o bibliotecă de elemente grafice de bază GUI dezvoltată de John
Ousterhout ca o extensie la limbajul de scripting Tcl, în 1991. Este open-source, cross-platform
(Linux, Unix, Mac OS, Windows).
Tkinter este cunoscut pentru simplitatea interfaței grafice a utilizatorului. Este open source și
este disponibil sub licența Python. Unul dintre avantajele alegerii Tkinter este că, din moment
ce vine implicit, se dispune de o abundență de resurse, atât coduri, cât și cărți de referință.
Odată scrisă o interfață în tkinter într-un sistem de operare, aceasta va funcționa și pe celelalte
sisteme de operare (este multiplatformă), dar va arăta pe fiecare în mod specific. De asemenea,
comunitatea fiind veche și activă, există multă documentație pe internet și mulți utilizatori care
pot ajuta în cazul apariției unor dificultăți.
Mai multe resurse la:
https://docs.python.org/2/library/tkinter.html.

20.1.3 QT, PyQT, PyQt5, PySide, QT Designer

Qt (pronunțat "cute") este un set de unelte widget pentru crearea de interfețe grafice, disponibil
ca aplicație multiplatformă (Linux, Windows, MacOS, Android sau sisteme embeded). Are un
grad mare de portabilitate, având nevoie de puține modificări ale codului de bază, la transferul
de pe o platformă pe alta.
Qt este susținut în prezent de “The Qt Company“, o firmă privată, de un proiect public open
source “Qt Project“ și de un număr mare de alte organizații și dezvoltatori individuali. Este
264 Interfețe grafice utilizator (GUI)

disponibil atât sub licență comercială, cât și sub licență open-source GPL 2.0, GPL 3.0 și LGPL
3.0.
PyQT este una dintre dezvoltările (denumite binding/“legătură”) de tip bibliotecă grafică multi
-platformă pentru Python care implementează biblioteca Qt. Aceasta face o combinație bună
între Python și Qt și permite programatorului să aleagă între programarea directă sau folosirea
Qt Designer pentru a crea dialoguri vizuale. Este disponibil atât în licență comercială, cât și în
licență GPL, care precizează că (deși este posibil ca unele funcții să nu fie disponibile în
versiunea gratuită), dacă aplicația proiectată este open source, atunci se poate folosi totuși sub
licența gratuită. PyQT a fost dezvoltat sub formă de versiuni până la PyQT5.
PySide este un set gratuit și multiplataformă de instrumente GUI Qt, inițiat și sponsorizat de
Nokia. PySide acceptă Linux/X11, Mac OS X și Windows. PySide oferă instrumente pentru
funcționarea cu documente multimedia, XML, rețea, baze de date și GUI. PySide este
compatibil API cu PyQt4.
Qt Designer este o unealtă care permite obținerea unei interfețe GUI de tip “what-you-see-is-
what-you-get (WYSIWYG)” pentru aplicații PyQt, în mod eficient. Obiectele Qwidget pot fi
trase și plasate cu mouse-ul într-un cadru-formă, gol. Apoi acestea pot fi aranjate coerent într-
un GUI utilizând manageri de straturi. Qt Designer permite previzualizarea GUI utlizând
diferite stiluri și rezoluții, conectarea semnalelor și sloturilor, crearea meniurilor și toolbar-
urilor, etc.
Qt designer nu produce cod Python, ci fișiere .ui, de tip XML, independente de platformă.
Acestea pot fi transformate în cod Python utilizând utilitarul pyuic5. Codul obținut va sta la
baza aplicației GUI.
https://wiki.python.org/moin/PyQt, https://realpython.com/python-pyqt-gui-calculator/

20.1.4. Kivy

Kivy este un GUI accelerat OpenGL ES 2 pentru crearea de noi interfețe utilizator. Suportă
mai multe platforme și anume Windows, MacOSX, Linux, Android, iOS și Raspberry Pi. Este
open source și vine cu numeroase (> 20) widget-uri în setul de instrumente.
Este preferat pentru aplicațiile pe dispozitive mobile.
Mai multe resurse la: https://kivy.org

20.1.5 WxPython

WxPython este un wrapper (învelitoare, ambalaj) Python, open source, pentru wxWidgets
(care este scris în C ++), un popular kit de instrumente GUI pe mai multe platforme, dezvoltat
de Robin Dunn împreună cu Harri Pasanen. WxPython poate fi utilizat pe Windows, Mac OS
și Unix, fiind implementat ca un modul de extensie. Modulele principale din API wxPython
includ un modul de bază conținând clasa wxObject, care este baza pentru toate clasele din API.
Modulul de control conține toate widgeturile utilizate în dezvoltarea aplicației GUI. De
exemplu, wx.Button, wx.StaticText (analog unei etichete), wx.TextCtrl (control text
modificabil) etc. De asemenea, wxPython API cuprinde modulul GDI (Graphics Device
Interface), un set de clase utilizate pentru desenarea pe widgeturi. Clase precum fontul,
culoarea, pensula etc. fac parte din acesta. Toate clasele de ferestre de containere sunt definite
în modulul Windows.
Mai multe resurse la: http://wxpython.org, https://zetcode.com/wxpython/,
265 Interfețe grafice utilizator (GUI)

https://tutorialspoint.com/wxpython/index.htm

20.1.6 PyGUI

PyGUI este un cadru multiplatformă de aplicații grafice pentru Unix, Macintosh și Windows.
În comparație cu alte cadre de lucru de tip GUI, PyGUI este de departe cel mai simplu și ușor
dintre toate, deoarece API-ul este pur sincronizat cu Python, fiind GUI-ul natural al platformei.
Mai multe resurse la: https://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/

20.1.7 PyGTK

PyGTK este un set de învelișuri/ambalaje pentru biblioteca GUI de tip GTK. PyGTK este un
software de tip free, licențiat LGPL. El este analog interfețelor PyQT (sau PySide) și wxPython
care la rândul lor sunt învelișuri pentru Qt, respectiv wxWidgets. A fost dezvoltat de autorul
interfeței originale GNOME (Linux), James Henstridge.

20.1.8 JPython

JyPython este un port pentru Java care permite ca dintr-un script Python să se acceseze fără
nici un effort bibliotecile de clase Java. Mai multe resurse la: www.jython.org.

20.2 Tkinter
20.2.1 Prezentare generală tkinter

Pentru a crea o aplicație de tip GUI utilizând tkinter trebuie să se parcurgă următorii pași:
- Se importă modulul tkinter.
- Se crează o aplicație fereastră pricipală (main Window).
- Se adaugă unul sau mai multe elemente de control widgets în aplicația GUI.
- Se crează bucla principală de evenimente care preia și tratează orice acțiune declanșată de
utilizator.
Tkinter gestionează geometria unei interfețe prin utilizarea mai multor metode.
- Metoda pack() – organizează widgeturile în blocuri înainte de a le plasa în widgetul părinte.
- Metoda grid() – organizează widgeturile într-o structură tabelară în widgetul părinter.
- Metoda place() – organizează widgeturile prin plasarea lor într-o poziție dorită, în
widgetul părinte.
Observație. Exemplele următoare se pot rula în IDLE Python, dar nu și în mediile de
dezvoltare care nu sunt configurate pentru tk, de exemplu în Spyder.
Exemplul 20.1 #Se execută în IDLE Python
import tkinter
window = tkinter.Tk()
#…………………
#Secventa de program care introduce si foloseste widget-uri
#…………………
window.mainloop()
266 Interfețe grafice utilizator (GUI)

În funcție de sistemul de operare instalat (Microsoft Windows, MacOS, Linux/Ubuntu) , va


apărea una din ferestrele de mai jos.

20.2.2 Tipurile de widgets specifice tkinter


Tkinter dispune de diferite elemente de control (denumite widgets): butoane, câmpuri de
etichetă și text, etc., specifice aplicațiilor de tip GUI, tabelul 1.
Tabelul 20.1 Tipuri de widgets curent utilizate
1 Button 6 Label 11 Radiobutton 16 Spinbox
2 Canvas 7 Listbox 12 Scale 17 PanedWindow
3 Checkbutton 8 Menubutton 13 Scrollbar 18 LabelFrame
4 Entry 9 Meniu 14 Text 19 tkMessageBox
5 Frame 10 Message 15 Toplevel 20
În continuare, se face o scurtă descriere a fiecăruia.
1. Button. Widgetul afișează butoane în aplicație.
Sintaxa:
w = Button( master, option=value, ... ),

unde master este fereastra părinte, iar options sunt: activebackground,


activeforeground, bd, command (funcția sau metoda ce va fi apelată când butonul este
apăsat), fg, font, height, highlightcolor, image, justify, padx, pady, relief,
state, underline, width, wraplength.
Metodele sale sunt flash() (produce câteva flashuri între culorile activă și normală și lasă
butonul în starea originală; se ignoră dacă butonul este invalidat) și invoke() (apelează
funcția callback a butonului și returnează ceea ce întoarce funcția callback. Se ignoră dacă
butonul este invalidat sau nu există callback).
Exemplul 20.2 #widgetul Button
import tkinter
from tkinter import messagebox
top = tkinter.Tk()
def helloCallBack():
messagebox.showinfo( "Hello Python", "Hello World")
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
top.mainloop()
Output
267 Interfețe grafice utilizator (GUI)

2. Canvas. Este un cadru (“pânza”) pentru desenarea formelor (linii, cercuri, ovale, poligoane,
dreptunghiuri, etc.).
Sintaxa:
w = Canvas( master, option=value, ... ),

unde master este fereastra părinte, iar options sunt: bd, bg, confine, cursor, height,
highlightcolor, relief, scrolregion, width, xscrollincrement,
xscrollcommand, yscrollincrement, yscrollcommand.
Widgeturile permise de Canvas sunt: arc, image, line, oval, polygon.
Exemplul 20.3 #widgetul Canvas
import tkinter
top = tkinter.Tk()
C = tkinter.Canvas(top, bg="blue", height=250, width=300)
coord = 10, 50, 240, 210
arc = C.create_arc(coord, start=0, extent=250, fill="yellow")
C.pack()
top.mainloop()

Output

3. Checkbutton. Șir de butoane pentru selectarea de opțiuni. Se pot selecta mai multe variante
simultan.
Sintaxa:
w = Canvas( master, option=value, ... ),

unde master este fereastra părinte, iar options sunt: activebackground,


activeforeground, bg, bitmap, bd, command, cursor, disableforeground, font,
fg, height, highlightcolor, image, justify, offvalue, onvalue, onvalue, padx,
pady, relief, selectcolor, selectimage, state, text, underline, variable,
width, wraplength.

Metodele sale sunt: deselect(), flash(), invoke(), select(), toggle().


268 Interfețe grafice utilizator (GUI)

Exemplul 20.4 #widgetul Checkbutton


from tkinter import *
from tkinter import messagebox
import tkinter
top = tkinter.Tk()
CheckVar1 = IntVar()
CheckVar2 = IntVar()
CheckVar3 = IntVar()
C1 = Checkbutton(top, text = "Muzică", variable = CheckVar1, \
onvalue = 1, offvalue = 0, height=5, \
width = 20)
C2 = Checkbutton(top, text = "Video", variable = CheckVar2, \
onvalue = 1, offvalue = 0, height=5, \
width = 20)
C3 = Checkbutton(top, text = "Film", variable = CheckVar3, \
onvalue = 1, offvalue = 0, height=5, \
width = 20)
C1.pack()
C2.pack()
C3.pack()
top.mainloop()

Output

4. Entry. Afișează o singură linie de text (un câmp) pentru introducerea valorilor de către
utilizator.
Sintaxa:
w = Entry( master, option, ... )

unde master este fereastra părinte, iar options sunt: bg, bd, command, cursor, font,
exportselection, fg, highlightcolor, justify, relief, selectbackground,
selectborderwidth, selectforeground, show, state, textvariable, width,
xscrollingcommand.

Metodele sunt: delete(first, last = None), get(), lcursor(index), index(index),


insert(index,s), select_adjust(index), select_clear(), selectfrom(index),
select_present(), select_range(start, end), select_to(index), xview(index),
xview_scroll(number, what).

Exemplul 20.5 #widgetul Entry


from tkinter import *
top = Tk()
L1 = Label(top, text="Nume și prenume")
L1.pack( side = LEFT)
269 Interfețe grafice utilizator (GUI)

E1 = Entry(top, bd =5)
E1.pack(side = RIGHT)
top.mainloop()

Output

5. Frame. Un cadru dreptunghiular pe ecran cu rol de container pentru organizarea altor


widget-uri.

Sintaxa:
w = Frame( master, option, ... )

unde master este fereastra părinte, iar options sunt: bg, bd, cursor, height,
highlightbackground, highlightcolor, highlightthickness, relief, width.

Exemplul 20.6 #widgetul Frame


from tkinter import *
root = Tk()
fr = Frame(root)
fr.pack()
bottomframe = Frame(root)
bottomframe.pack( side = BOTTOM )
rb = Button(fr, text="Roșu", fg="red")
rb.pack( side = LEFT)
gb = Button(fr, text="Verde", fg="green")
gb.pack( side = LEFT )
bb = Button(fr, text="Albastru", fg="blue")
bb.pack( side = LEFT )
nb = Button(bottomframe, text="Negru", fg="black")
nb.pack( side = BOTTOM)
root.mainloop()

Output

6. Label. Eticheta - este o singură line de text/descriere (titlu) pentru alte widgeturi. Poate să
conțină o imagine.
Sintaxa:
w = Label( master, option, ... )
270 Interfețe grafice utilizator (GUI)

unde master este fereastra părinte, iar options sunt: anchor, bg, bitmap, bd, cursor,
font, fg, height, image, justify, padx, pady, relief, text, textvariable,
underline, width, wraplength.

Exemplul 20.7 #widgetul Label


from tkinter import *
root = Tk()
var = StringVar()
label = Label(root, textvariable=var, relief=RAISED)
var.set("Atenție !! Pericol de electrocutare!")
label.pack()
root.mainloop()

Output

7. Listbox. Afișează o listă de opțiuni pentru utilizator.


Sintaxa:
w = Listbox( master, option, ... )

unde master este fereastra părinte, iar options sunt: bg, bd, cursor, font, fg,
height, highlightcolor, highlightthickness, relief, selectbackground,
selectmode, width, xscrollcommand, yscrollcommand.

Metodele sunt: activate(index), curselection(), delete(first, last=None),


get(first, last=None), index(i), insert(index, *elements), nearest(y),
see(index), size(), xview(), xview_moveto(fraction), xview_scroll(number,
what), yview(), yview_moveto(fraction), yview_scroll(number, what).

Exemplul 20.8 #widgetul Listbox


from tkinter import *
from tkinter import messagebox
import tkinter
top = Tk()
Lb1 = Listbox(top)
Lb1.insert(1, "Craiova")
Lb1.insert(2, "Pitesti")
Lb1.insert(3, "Cluj")
Lb1.insert(4, "Oradea")
Lb1.insert(5, "Timisoarea")
Lb1.insert(6, "Ploiesti")
Lb1.pack()
top.mainloop()

Output
271 Interfețe grafice utilizator (GUI)

8. Menubutton. Este o parte a unui meniu drop-down care este afișat permanent pe ecran.
Fiecare Menubutton este asociat cu un widget de tip Menu care afișează lista de alegeri pentru
Menubutton, atunci când utilizatorul selectează cu click.
Sintaxa:
w = Menubutton( master, option, ... )

unde master este fereastra părinte, iar options sunt: activebackground,


activeforeground, anchor, bg, bitmap, bd, cursor, direction,
disableforeground, fg, height, highlightcolor, image, justiy, menu, padx,
pady, relief, state, text, textvariable, underline, width, wraplength.

Exemplul 20.9 #widgetul Menubutton


from tkinter import *
from tkinter import messagebox
import tkinter
top = Tk()
mb= Menubutton ( top, text="Mărci auto", relief=RAISED )
mb.grid()
mb.menu = Menu ( mb, tearoff = 0 )
mb["menu"] = mb.menu
mayoVar = IntVar()
ketchVar = IntVar()
mb.menu.add_checkbutton ( label="Dacia", variable=mayoVar )
mb.menu.add_checkbutton ( label="Ford", variable=ketchVar )
mb.pack()
top.mainloop()

Output

9. Meniu. Creează mai multe tipuri de meniuri care pot fi utilizate în aplicații: pop-up,
toplevel și pulldown. De asemenea este posibilă utilizarea altor extensii de widget (de
exemplu, OptionMenu) pentru implementarea a noi tipuri de meniuri.
272 Interfețe grafice utilizator (GUI)

Sintaxa:
w = Menu( master, option, ... )

unde master este fereastra părinte, iar options sunt: activebackground,


activeborderwidth, activeforeground, bg, bd, cursor, disableforground, font,
fg, postcommand, relief, image, selectcolor, tearoff, title.

Metodele sunt: add_command(options), add_radiobutton(options),


add_checkbutton(options), add_cascade(options), add_separator(), add(type,
options), delete(startindex [, endindex]), entryconfig(index, options),
index(item), insert_separator(index), invoke(index), type(index).

Exemplul 20.10 #widgetul Meniu


from tkinter import *
def fara_actiune():
filewin = Nivel_Varf(root)
button = Button(filewin, text="Buton fara actiune")
button.pack()
root = Tk()
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="New", command=fara_actiune)
filemenu.add_command(label="Open", command=fara_actiune)
filemenu.add_command(label="Save", command=fara_actiune)
filemenu.add_command(label="Save as...", command=fara_actiune)
filemenu.add_command(label="Close", command=fara_actiune)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=root.quit)
menubar.add_cascade(label="File", menu=filemenu)
editmenu = Menu(menubar, tearoff=0)
editmenu.add_command(label="Undo", command=fara_actiune)
editmenu.add_separator()
editmenu.add_command(label="Cut", command=fara_actiune)
editmenu.add_command(label="Copy", command=fara_actiune)
editmenu.add_command(label="Paste", command=fara_actiune)
editmenu.add_command(label="Delete", command=fara_actiune)
editmenu.add_command(label="Select All", command=fara_actiune)
menubar.add_cascade(label="Edit", menu=editmenu)
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label="Help Index", command=fara_actiune)
helpmenu.add_command(label="About...", command=fara_actiune)
menubar.add_cascade(label="Help", menu=helpmenu)
root.config(menu=menubar)
root.mainloop()

Output
273 Interfețe grafice utilizator (GUI)

10. Message. Afișează un câmp cu mai multe linii de text.


Sintaxa:
w = Message( master, option, ... )

unde master este fereastra părinte, iar options sunt: anchor, bg, bitmap, bd, cursor,
font, fg, height, image, justify, padx, pady, relief, text, textvariable,
unfderline, width, wraplength.

Exemplul 20.11 #widgetul Message


from tkinter import *
root = Tk()
var = StringVar()
label = Message(root, textvariable=var, relief=RAISED)
var.set("Pentru pornire, apăsați butonul verde!")
label.pack()
root.mainloop()

Output

11. Radiobutton. Afișează un număr de opțiuni asemănător unei vechi “claviaturi” radio. Se
poate selecta o singură opțiune la un moment dat.

Sintaxa:
w = Message( master, option, ... )

unde master este fereastra părinte, iar options sunt: activebackground,


activeforeground, anchor, bg, bitmap, borderwidth, command, cursor, font,
fg, height, highlightbackground, highlightcolor, image, justify, padx, pady,
relief, selectcolor, selectimage, state, text, textvariable, underline,
value, variable, width, wraplength.
274 Interfețe grafice utilizator (GUI)

Metodele sunt: select(), deselect(), flash(), invoke(), select(),.

Exemplul 20.12 #widgetul Radiobutton


from tkinter import *
def sel():
selection = "Ati selectat optiunea " + str(var.get())
label.config(text = selection)
root = Tk()
var = IntVar()

R1 = Radiobutton(root, text="Optiunea 1", variable=var, value=1,


command=sel)
R1.pack(anchor = W)
R2 = Radiobutton(root, text="Optiunea 2", variable=var, value=2,
command=sel)
R2.pack(anchor = W)
R3 = Radiobutton(root, text="Optiunea 3", variable=var, value=3,
command=sel)
R3.pack(anchor = W)
label = Label(root)
label.pack()
root.mainloop()

Output

12. Scale. Indicator de tip bară glisantă (slider).


Sintaxa:
w = Scale( master, option, ... )

unde master este fereastra părinte, iar options sunt: bg, bd, command, cursor, digits,
font, fg, from_, highlightbackground, highlightcolor, label, length, orient,
relief, repeatdelay, resolution, showvalue, sliderlength, state, takefocus,
tickfocus, tickinterval, to, troughcolor, variable, width.

Metodele sunt: get(), set(value).


Exemplul 20.13 #widgetul Scale
from tkinter import *
def sel():
selection = "Valoare = " + str(var.get())
label.config(text = selection)
root = Tk()
var = DoubleVar()
scale = Scale( root, variable = var )
scale.pack(anchor=CENTER)
button = Button(root, text="Indicatie valoare", command=sel)
button.pack(anchor=CENTER)
label = Label(root)
275 Interfețe grafice utilizator (GUI)

label.pack()
root.mainloop()

Output

13. Scrollbar. Adaugă posibilitate de derulare pentru alte widgeturi (ca de exemplu list-boxes).
Sintaxa:
w = Scrollbar( master, option, ... )

unde master este fereastra părinte, iar options sunt: activebackground, bg, bd,
command, cursor, elementborderwidth, highlightbackground, highlightcolor,
highlightthickness, jump, orient, repeatdelay, repeatinterval, takefocus,
troughcolor, width.

Metodele sunt: get(), set(first, last)


Exemplul 20.14 #widgetul Scrollbar
from tkinter import *
root = Tk()
scrollbar = Scrollbar(root)
scrollbar.pack( side = RIGHT, fill = Y )
xlista = Listbox(root, yscrollcommand = scrollbar.set )
for linie_text in range(100):
xlista.insert(END, "Linia text nr: " + str(linie_text))
xlista.pack( side = LEFT, fill = BOTH )
scrollbar.config(command = xlista.yview)
mainloop()

Output

14. Text. Afișează multiple linii de text.


276 Interfețe grafice utilizator (GUI)

Sintaxa:
w = Text( master, option, ... )

unde master este fereastra părinte, iar options sunt: bg, bd, cursor, exportselection,
font, fg, height, highlightbackground, highlightcolor, highlightthickness,
insertbackground, insertborderwidth, insertofftime, insertontime,
insertwidth, padx, pady, relief, selectbackground, selectborderwidth,
spacing1, spacing2, spacing3, state, tabs, width, wrap, xscrollcommand,
yscrollcommand.

Metodele obiectului text sunt:


delete(startindex, [,endindex]), get(startindex[, endindex]), index(index),
insert(index [, string] …), see(index).

Widget-urile de tip text beneficiază de trei structuri distincte care sunt de ajutor: Mark, Tab,
Index.
Un marcaj (Mark) este utilizat pentru a indica sau consemna pozițiile dintre două caractere
într-un text dat. Pentru manipularea marcajelor sunt disponibile următoarele metode:
index(mark), mark_gravity(mark [, gravity]), mark_names(), mark_set(mark,
index), mark_unset(mark).

Etichetele (Tag) sunt utilizate pentru a asocia nume la regiuni de text care ușurează modificarea
setările de afișare ale unor zone de text. Pentru manipularea tagurilor sunt disponibile
următoarele metode: tag_add(tagname, startindex[, endindex] …), tag_config,
tag_delete(tagname), tag_remove(tagname [,startindex [, endindex]]…).

Exemplul 20.15 #widgetul Text


from tkinter import *
def onclick():
pass
root = Tk()
text = Text(root)
text.insert(INSERT, "Aaaaaaaa.....")
text.insert(END, "Bbbbbbbb.....")
text.pack()
text.tag_add("Text_A", "1.0", "1.13")
text.tag_add("Text_B", "1.13", "1.26")
text.tag_config("Text_A", background="yellow", foreground="blue")
text.tag_config("Text_B", background="cian", foreground="green")
root.mainloop()

Output

15. Toplevel. Fereastră container care poate conține toate celelalte ferestre (este parent
window) ale aplicației (aplicațiilor, dacă sunt mai multe). Se utilizează in general pentru
afisarea de informații suplimentare ale utilizatorului.
Sintaxa:
w = Toplevel(master, option, ... )
277 Interfețe grafice utilizator (GUI)

unde master este fereastra părinte, iar options sunt: bg, bd, cursor, class_,
font, fg, height, relief, width.
Metodele sunt: deiconify(), frame(), group(window), iconify, protocol(name,
function), state(), transient([master]), withdraw(), maxsize(width, height),
minsize(width, heigth), positionfrom(who), resizable(width, height),
sizefrom(who), title(string).

Exemplul 20.16 #widgetul Toplevel


from tkinter import *
root = Tk()
root.geometry("150x200")
root.title("main")
l = Label(root, text = "Aceasta este root window")
top = Toplevel()
top.geometry("180x100")
top.title("toplevel")
l2 = Label(top, text = "Aceasta este toplevel window")
l.pack()
l2.pack()
top.mainloop()
Output

16. Spinbox. Este o variantă a widgetului Enter, care poate fi utilizat pentru selectarea unui
număr fix de valori.
Sintaxa:
w = Spinbox(master, option, ... )

unde master este fereastra părinte, iar options sunt: activebackground, bg, bd,
command, cursor, disablebackground, disableforeground, fg, font, format,
from_, justify, relief, repeatdelay, repeatinterval, state, textvariable,
to, validate, validatecommand, values, vcmd, width, wrap, xscrollcommand.

Metodele sunt: delete(startindex [, endindex]), get(startindex [, endindex]),


identify(x,y), index(index), insert(index [, string]…), invoke(element).

Exemplul 20.17 #widgetul Spinbox


from tkinter import *
master = Tk()
w = Spinbox(master, from_=0, to=10)
w.pack()
278 Interfețe grafice utilizator (GUI)

mainloop()

Output

17. PanedWindow. Este un container care poate conține un număr de paneluri, aranjate
orizontal sau vertical. Fiecare panel conține un widget și fiecare pereche de paneluri este
separată printr-o bară (sash) deplasabilă prin mișcări ale mouse-ului. Mișcarea unui sash face
ca widgetul de cealaltă parte a sashului să fie redimensionat.
Sintaxa:
w = Spinbox(master, option, ... )

unde master este fereastra părinte, iar options sunt: bg, bd, borderwidth, cursor,
handlepad, handlesize, height, orient, relief, sashcursor, sashrelief,
sashwidth, showhandle, width.

Metodele sunt: add(child, options), get(startindex [, endindex]),


config(options).

Exemplul 20.18 #widgetul PanedWindow


from tkinter import *
m1 = PanedWindow()
m1.pack(fill=BOTH, expand=1)
left = Label(m1, text="Panoul din stanga")
m1.add(left)
m2 = PanedWindow(m1, orient=VERTICAL)
m1.add(m2)
top = Label(m2, text="Panoul de sus")
m2.add(top)
bottom = Label(m2, text="Panoul de jos")
m2.add(bottom)
mainloop()

18. LabelFrame. Este o variantă mai simplă a widgetului Frame. Poate fi utilizat pentru
trasarea unei borduri etichetate pentru entități copil, sau poate fi utilizat ca Container pentru
widgeturi înrudite.
Sintaxa:
w = LabelFrame(master, option, ... )

unde master este fereastra părinte, iar options sunt: bg, bd, cursor, font, height,
labelAnchor, highlightbackground, highlightcolor, highlightthickness,
relief, test, width.
279 Interfețe grafice utilizator (GUI)

Exemplul 20.19 #widgetul LabelFrame


from tkinter import *
root = Tk()
labelframe = LabelFrame(root, text="Acesta este o bordură etichetată ")
labelframe.pack(fill="both", expand="yes")
left = Label(labelframe, text="\n\n Interiorul cadrului etichetat \n\n")
left.pack()
root.mainloop()

19. tkMessageBox. Câmp pentru afișarea de mesaje.


Sintaxa:
tkMessageBox.FunctionName(title, message [, options])

FunctionName este numele funcției casetei mesajului.


title este textul ce va fi afișat în bara de titlu a casetei mesajului.
message este textul ce va fi afișat ca mesaj.
Options sunt opțiuni alternative care se pot utiliza pentru a adapta o casetă de mesaje standard.
Unele dintre opțiuni sunt implicite și părinte. Opțiunea implicită este utilizată pentru a specifica
butonul implicit, cum ar fi ABORT, RETRY sau IGNORE în caseta de mesaj. Opțiunea părinte
este utilizată pentru a specifica fereastra deasupra căreia urmează să fie afișată caseta de
mesaje.
Această casetă de mesaje se poate utiliza împreună cu una din funcțiile: showinfo(),
showwarning(), showerror (), askquestion(), askokcancel(), askyesno (),
askretrycancel().

Exemplul 20.20 #widgetul tkMessageBox


import tkinter
from tkinter import messagebox
top = tkinter.Tk()
def comanda_mesaj():
tkinter.messagebox.showinfo("Mesaj", "Start joc!")
B1 = tkinter.Button(top, text = "Mesaj", command = comanda_mesaj)
B1.pack()
top.mainloop()

Output
280 Interfețe grafice utilizator (GUI)

20.2.3 Atributele standard ale widgeturilor


Toate widgeturile sunt obiecte care dispun de o serie de atribute standard, ca de exemplu:
mărime, culoare, font, etc. În continuare se analizează mai în detaliu.
1. Dimensiunile
Unități de măsurare: c (centimetri), i (inci), m (milimetri), p (puncte ~ 1/72”)
Opțiuni de lungime: borderwidth, highlightthickness, padX padY,
selectborderwidth, wraplength, height, underline, width.

2. Culorile
Reprezentare hexazecimală: roșu, verde, albastru (RGB). ”#000000” = negru, ”fff” = alb.
Opțiuni de culoare: activebackground, activeforeground, background,
disableforeground, foreground, highlightbackground, highlightcolor,
selectbackground, selectforeground.

3. Fonturile
Reprezentare prin tupluri: (”Times”,”18”, ”bold italic”).
Reprezentare prin obiecte: importare tkFont și creare obiect, astfel:
import tkFont
font = tkFont (option, …), unde opțiunile sunt: family, size, weight, slant,
underline, overstrike. Exemplu:
helv24 = tkFont.Font(family = ”Helvetica”, size = 24, weight = ”bold”)
Reprezentare prin fonturi Windows: se utilizează numele fomntului.

4. Ancorele
Ancorele definesc locul unde se poziționează textul în raport cu un punct de referință. Se
identifică prin constantele desemnând direcții cardinale: NW, N, NE, W, CENTER, E, SW,
S, SE. Exemplu: dacă se utilizează CENTER ca ancoră de text, textul va fi centrat orizontal și
vertical în jurul punctului de referință.

5. Stilurile în relief
Stilul în relief al unui widget se referă la efectele simulate 3D în jurul acestuia. Atributele
utilizate pentru crearea efectelor sunt: FLAT, RAISED, SUNKEN, GROOVE, RIDGE:
281 Interfețe grafice utilizator (GUI)

6. Bitmap-urile
Atributele corespund unor bitmap-uri care afișează unele simboluri, coespunzând denumirilor:
"error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info",
questhead", "question", "warning"
7. Cursoarele
• Tkinter permite utilizarea unor cursoare de diferite forme și aspecte, în funcție de sistemul
de operare. Atributele corespund unor bitmap-uri care afișează unele simboluri, coespunzând
denumirilor: "arrow", "circle", "clock", "cross", "dotbox", "exchange",
"fleur", "heart", "heart", "man", "mouse", "pirate", "plus", "shuttle",
"sizing", "spider", "spraycan", "star", "target", "tcross", "trek", "watch".

20.3 Întrebări, exerciții și probleme


20.3.1 Scrieți un program perntru a desena un dreptunghi centrat la (50, 50) cu lățimea 50 și
înălțimea 70 pixeli.
R20.3.1 Argumentele corespunzătoare coordonatelor sunt pentru funcția create_rectangle: (x1,
y1, x2, y2).
from tkinter import *
D = Tk()
D.title('Dreptunghi')
D.geometry('200x200')
D.config(bg='#345')
canvas = Canvas(D, height=100, width=100, bg="#fff")
canvas.pack()
canvas.create_rectangle(25, 15, 75, 85,
outline="#fb0", fill="#fb0")
D.mainloop()

20.3.2 Să se scrie un program pentru introducerea unui text folosind Button, Entry si Label.
R20.3.2
from tkinter import *
root = Tk()
e = Entry(root, width=40)
e.pack()
e.insert(0, "Introduceti numele: ") #prompter pentru entry
def myClick():
myLabel = Label(root, text="Salut! " + e.get())
282 Interfețe grafice utilizator (GUI)

myLabel.pack()
myButton = Button(root, text="Scrieti numele", command = myClick)
myButton.pack()
root.mainloop()

Output

20.3.3 Să se scrie un program care să ajute la calcule vânzătorul din piața de fructe și legume.
Programul va calcula câștigul vânzătorului (sau pierderea) prin diferența între suma cheltuită
la achiziționarea unei cantități de produse cu un preț și suma obținută prin vânzare la alt preț.
R20.3.3
from tkinter import *
def intro_date():
produs=e1.get()
pret_achiz=e2.get()
pret_vanz=e3.get()

cost_achizitie="Cost achizitie lei: %.2f" % ( float(produs) *


float(pret_achiz))
val_vanzare="Vanzare lei: %.2f" % ( float(produs) * float(pret_vanz))
profit=( (float(produs) * float(pret_vanz) ) - (float(produs) *
float(pret_achiz)) )
castig="Castig net lei: %.2f" % profit
for item in [cost_achizitie, val_vanzare,castig]:
listb.insert(END,item)
def stergere():
listb.delete(0,END)

root=Tk()
label=Label(root, text="Cantitate produse").grid(row=0)
label2=Label(root,text="Pret achizitie").grid(row=1)
label3=Label(root,text="Pret vanzare").grid(row=2)
e1=Entry(root,bg='white')
e2=Entry(root,bg='white')
e3=Entry(root,bg='white')
e1.grid(row=0,column=1)
e2.grid(row=1,column=1)
e3.grid(row=2,column=1)
button=Button(root, text='calculeaza', command=intro_date)
button.grid(row=3,column=1)
clearb=Button(root, text="sterge",command=stergere)
clearb.grid(row=3,column=0)
listb=Listbox(root,bg='light blue')
listb.grid(row=4,column=1, padx=20,pady=20)
root.mainloop()

Output
283 Interfețe grafice utilizator (GUI)

20.3.4 Să se scrie un program care să efectueze cele patru operații aritmetice elementare.
R20.3.4
#Un calculator pentru cele 4 operații aritmetice elementare
from tkinter import *
root = Tk()
root.title("Calculator aritmetic")
e = Entry(root, width=40, borderwidth=5)
e.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
# b = “buton”
def b_click(cifra):
current = e.get()
e.delete(0,END)
e.insert(0,str(current) + str(cifra))
def b_sterge():
e.delete(0,END)
def b_adunare():
primul_numar = e.get()
global f_num
global math
math = "adunare"
f_num = int(primul_numar)
e.delete(0, END)
def b_egal():
al_doilea_numar = e.get()
e.delete(0, END)
if math == "adunare":
e.insert(0, f_num + int(al_doilea_numar))
if math == "scadere":
e.insert(0, f_num - int(al_doilea_numar))
if math == "inmultire":
e.insert(0, f_num * int(al_doilea_numar))
if math == "impartire":
e.insert(0, f_num / int(al_doilea_numar))
def b_scadere():
primul_numar = e.get()
global f_num
global math
math = "scadere"
f_num = int(primul_numar)
e.delete(0, END)
def b_inmultire():
primul_numar = e.get()
global f_num
284 Interfețe grafice utilizator (GUI)

global math
math = "inmultire"
f_num = int(primul_numar)
e.delete(0, END)
def b_impartire():
primul_numar = e.get()
global f_num
global math
math = "impartire"
f_num = int(primul_numar)
e.delete(0, END)
# Definire butoane
B_1 = Button(root,text="1",padx=40,pady=20,command = (lambda: b_click(1)))
b_2 = Button(root,text="2",padx=40,pady=20,command = (lambda: b_click(2)))
b_3 = Button(root,text="3",padx=40,pady=20,command = (lambda: b_click(3)))
b_4 = Button(root,text="4",padx=40,pady=20,command = (lambda: b_click(4)))
b_5 = Button(root,text="5",padx=40,pady=20,command = (lambda: b_click(5)))
b_6 = Button(root,text="6",padx=40,pady=20,command = (lambda: b_click(6)))
b_7 = Button(root,text="7",padx=40,pady=20,command = (lambda: b_click(7)))
b_8 = Button(root,text="8",padx=40,pady=20,command = (lambda: b_click(8)))
b_9 = Button(root,text="9",padx=40,pady=20,command = (lambda: b_click(9)))
b_0 = Button(root,text="0",padx=40 pady=20,command = (lambda: b_click(0)))
b_adunare = Button(root,text="+",padx=39,pady=20,command= b_adunare)
b_egal = Button(root,text="=",padx=91,pady=20,command= b_egal)
b_sterge = Button(root,text="Clear",padx=79,pady=20,command= b_sterge)
b_scadere = Button(root,text="-",padx=39,pady=20,command= b_scadere)
b_inmultire = Button(root,text="*",padx=39,pady=20,command= b_inmultire)
b_impartire = Button(root,text="/",padx=39,pady=20,command= b_impartire)
# Afisare butoane pe ecran
b_1.grid(row=3, column=0)
b_2.grid(row=3, column=1)
b_3.grid(row=3, column=2)
b_4.grid(row=2, column=0)
b_5.grid(row=2, column=1)
b_6.grid(row=2, column=2)
b_7.grid(row=1, column=0)
b_8.grid(row=1, column=1)
b_9.grid(row=1, column=2)
b_0.grid(row=4, column=0)
b_sterge.grid(row=4, column=1, columnspan=2)
b_adunare.grid(row=5, column=0)
b_egal.grid(row=5, column=1, columnspan=2)
b_scadere.grid(row=6, column=0)
b_inmultire.grid(row=6, column=1)
b_impartire.grid(row=6, column=2)
root.mainloop()
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 21

Numpy. Elemente de bază


21.1 Introducere
Python, ca limbaj de programare, nu dispune de tipul tablou (array).
Observație. În Python se utilizează listele pentru suplinirea absenței tipului tablou. Listele
(care sunt secvențe) pot crește dinamic, având o dimensiune variabilă și pot conține elemente
de diferite tipuri. O rezolvare incompletă, doar pentru tablourile unidimensionale, se face prin
importul modulului array, soluția fiind nesatisfăcătoare.
Numpy este o bibliotecă al cărei rol este suplinirea lipsei tipului tablou în Python.
Denumirea Numpy este o prescurtare pentru Numerical Python.
Definiție. Un tablou (array) stochează și reprezintă date de orice fel într-un mod structurat,
conținând informația referitoare la datele utile, modul de localizare al datelor și modul de
interpretare al acelor date.
Tradițional, la nivel structural, un tablou este la bază un set de pointeri, adică o combinație de
adrese de memorie, un tip de dată, o formă și un pas (stride), astfel:
- Pointerul data indică adresa de memorie începând cu primul octet al tabloului.
- Tipul de dată, adică pointerul dtype descrie felul elementelor tabloului.
- shape indică forma unui tablou/array.
- stride reprezintă numărul de octeți care trebuie săriți în memorie pentru a ajunge la
următorul element al tabloului. De exemplu, dacă (10, 4) este un stride, atunci trebuie săriți
patru octeți pentru a ajunge la următoarea coloană și 10 octeți pentru a ajunge la următorul
rând.
Clasa de tablouri în Numpy este ndarray ("tablou n-dimensional"), cu aliasul array.
ndarray este un obiect în Numpy, încapsulând un tablou n-dimensional de un tip omogen de
date și în plus mai multe operații (metode) care produc performanță în cod compilat.
În Numpy, tabloul conține elemente de același tip, de lungime fixă, indexate printr-un tuplu de
numere întregi. Dimensiunile în Numpy sunt denumite "axe".
Observație. Singura excepție permisă în Numpy legată de dimensiunea fixă a elementelor este
când elementele sunt obiecte.
Observație. Clasa numpy.array este diferită de clasa array.array din Python, care este
unidimensională.
Câteva atribute mai importante ale unui obiect ndarray sunt:
286 Numpy

ndarray.ndim - numărul de axe (dimensiuni)


ale tabloului. În Numpy, numerotarea axelor
începe de la zero. Prin precizarea axei se poate
indica direcția însumării rândurilor sau a
coloanelor, sensul de alipire (concatenare,
stivuire) al tablourilor pe orizontală sau pe
verticală, etc.
Valoarea negativă a numărului axei reprezintă
direcția opusă sensului convenit.
Tablourile unidimensionale au o singură axă,
axa 0.
ndarray.shape - dimensiunea tabloului, este un tuplu de întregi, unde fiecare întreg indică
mărimea fiecărei dimensiuni. Pentru o matrice cu n linii și m coloane, shape va fi tuplul (n ,m).
Lungimea tuplului va fi egală cu numărul de axe, ndim.
ndarray.size este numărul total de elemente ale unui array, egal cu produsul componentelor
tuplului shape.
ndarray.dtype descrie tipul elementelor (numpy.int32, numpy.int16, numpy.float64) .
Atributele unui obiect care are tipul dtype sunt: dtype.byteorder (big endian sau little
endian), dtype.itemsize, dtype.name, dtype.type (obiect type utilizat la crearea
scalarilor).
ndarray.itemsize este mărimea în octeți a elementelor arrayului (float64 -> itemsize=8).

ndarray.data bufferul conținînd array-ul.

21.2 Comparație între caracteristicile tablourilor în Python și


Numpy
Caracterizarea diferențelor dintre tablouri în Python și Numpy se poate face la nivel de definiție
și la nivel de conținut.
La nivel de definiție:
- În Python tablourile sunt variabile de tip list care pot crește dinamic (sunt secvențe).
- În Numpy tablourile sunt obiecte de tip array conținând elemente de dimensiuni care sunt
precizate la creare.
La nivel de conținut:
- În Python lista are o dimensiune variabilă și poate conține tipuri de date diferite.
- În Numpy tablourile sunt omogene din punct de vedere al tipurilor de date, datele conținute
având aceeași lungime.
Observație. În Numpy, pentru modificarea unui tablou se creează un tablou nou, iar cel vechi
se șterge.
Observație. Pachetele științifice bazate pe Python convertesc automat secvențele din Python
în tablouri în Numpy, înainte de procesare, iar rezultatele livrate sunt în Numpy.
287 Numpy

Modul de lucru vectorizat caracteristic Numpy a apărut deoarece în Python modul de


desfășurare al calculului este destul de lent. Un exemplu tipic de organizare a acestor calcule
se prezintă mai jos.
Exemplul 21.1 # Modul de lucru in Python
# Tabloul numeric este stocat într-o listă Python
a = [1,2,3]
b = [4,5,6]
c = []
for i in range (len(a)):
c.append(a[i]*b[i])
print(c)
Output
[4, 10, 18]

Forma de calcul de mai sus, efectuată în Python este dezavantajoasă prezentând un consum
mare de timp pentru liste foarte mari (milioane de numere).
Observație. În limbajul C/C++ viteza de calcul este mai mare, dar sintaxa este incomodă din
cauza relativei complexități a codului, care deranjează pentru un număr mai mare de operații
cu tablouri. Pentru ilustrarea afirmației, se prezintă echivalentul în C/C++ al programului de
mai sus:
for(i=0; i<rows; i++) {c[i] = a[i]*b[i]}

iar în cazul 2D, astfel:


for(i=0;i;rows;i++){ for(j=0;j<col;j++){ c[i][j]=a[i]*b[i][j]} }

Numpy utilizează vectorizarea în efectuarea operațiilor. Vectorizarea înseamnă absența


buclelor și a indexării din codul program (acestea există totuși ascunse în fundal), generând un
aspect mai apropiat de notația matematică. Ca urmare codul vectorizat este mai concis și mai
ușor de citit. În plus, mai puține linii de program înseamnă mai puține erori.
Prin vectorizare, în Numpy se simplifică sintaxa, care se prezintă mai simplu, comparativ cu
varianta din C/C++, astfel:
Exemplul 21.2 #modul de lucru in Numpy (vectorizat)
import numpy as np
a = np.array([1,2,3])
b = np.array([4,5,6])
c = np.array([])
c = a * b
print(c)

Output
[ 4 10 18]
Avantajele modului de lucru vectorizat din Numpy, față de modul cu utilizare de bucle sunt:
- mai concis și mai ușor de citit;
- mai puține linii de cod, implicit mai puține erori;
- notație mai apropiată de cea matematică.
Observație. Modul de lucru vectorizat în Numpy este doar la suprafață; în fundal, ascuns, la
implementare există totuși bucle repetitive.
288 Numpy

Observație. În Numpy, ndarray este o clasă (OOP) care posedă numeroase atribute și metode,
ce se pot obține cu comanda dir(ndarray), prezentate în lista următoare.
['T', '__init_subclass__ '__setattr__',
'__abs__', ', '__setitem__', 'itemset',
'__add__', '__int__', '__setstate__', 'itemsize',
'__and__', '__invert__', '__sizeof__', 'max',
'__array__', '__ior__', '__str__', 'mean',
'__array_finalize__', '__ipow__', '__sub__', 'min',
'__array_function__', '__irshift__', __subclasshook__ 'nbytes',
'__array_interface__', '__isub__', ', 'ndim',
'__array_prepare__', '__iter__', '__truediv__', 'newbyteorder',
'__array_priority__', '__itruediv__', '__xor__', 'nonzero',
'__array_struct__', '__ixor__', 'all', 'partition',
'__array_ufunc__', '__le__', 'any', 'prod',
'__array_wrap__', '__len__', 'argmax', 'ptp',
'__bool__', '__lshift__', 'argmin', 'put',
'__class__', '__lt__', 'argpartition', 'ravel',
'__complex__', '__matmul__', 'argsort', 'real',
'__contains__', '__mod__', 'astype', 'repeat',
'__copy__', '__mul__', 'base', 'reshape',
'__deepcopy__', '__ne__', 'byteswap', 'resize',
'__delattr__', '__neg__', 'choose', 'round',
'__delitem__', '__new__', 'clip',
'__dir__', '__or__', 'compress', 'searchsorted',
'__divmod__', '__pos__', 'conj', 'setfield',
'__doc__', '__pow__', 'conjugate', 'setflags',
'__eq__', '__radd__', 'copy', 'shape',
'__float__', '__rand__', 'ctypes', 'size',
'__floordiv__', '__rdivmod__', 'cumprod', 'sort',
'__format__', '__reduce__', 'cumsum', 'squeeze',
'__ge__', '__reduce_ex__', 'data', 'std',
'__getattribute__', '__repr__', 'diagonal', 'strides',
'__getitem__', '__rfloordiv__', 'dot', 'sum',
'__gt__', '__rlshift__', 'dtype', 'swapaxes',
'__hash__', '__rmatmul__', 'dump', 'take',
'__iadd__', '__rmod__', 'dumps', 'tobytes',
'__iand__', '__rmul__', 'fill', 'tofile',
'__ifloordiv__' '__ror__', 'flags', 'tolist',
'__ilshift__', '__rpow__', 'flat', 'tostring',
'__imatmul__', '__rrshift__', 'flatten' 'trace',
'__imod__', '__rshift__', 'getfield', 'transpose',
'__imul__', '__rsub__', 'imag', 'var',
'__index__', '__rtruediv__', 'item', 'view']
'__init__', '__rxor__',

21.3 Crearea și accesarea tablourilor în Numpy


21.3.1 Crearea tablourilor
În Numpy, un tablou se poate crea prin mai multe metode.
1. Explicit, dintr-o listă de valori, utilizând funcția np.array(), care întoarce un obiect
ndarray:
>>> np.array([1,2,3,4])
array([1, 2, 3, 4])
2. Ca un domeniu de valori:
>>> np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
289 Numpy

3. Prin specificarea numărului de elemente:


>>> np.linspace(0, 1, 5)
array([ 0. , 0.25, 0.5 , 0.75, 1. ])
4. Prin inițializare cu zero:
>>> np.zeros((2,2))
array([[0., 0.],
[0., 0.]])
5. Prin inițializare cu unu:
>>> np.ones((1,5))
array([[ 1., 1., 1., 1., 1.]])
6. Fără inițializare:
>>> np.empty((1,3))
array([[1.29064133e-311, 1.29064133e-311, 0.00000000e+000]])
7. Cu diagonală de valori uniforme (unitate):
>>> np.eye(3)
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])

8. Cu diagonală de valori diferite:


>>>np.diag([1,2,3,4])
array([[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4]])

Mertoda de creare explicită a unui tablou din liste sau tuple folosind funcția np.array()este
cea mai frecventă.
Exemplul 21.3 #crearea tablourilor din liste sau tuple utilizând np.array()
import numpy as np
a = np.array ([2, 3, 4]) #creare din lista
a1 = np.array (([2, 3, 4]), dtype = np.float64) #creare din tuplu
print(a.dtype)
print(a1.dtype)
c = np.array ([[1,2],[3,4]], dtype = complex)
print(c.shape)

Output
int32
float64
(2, 2)

Observație. Implicit, dtype al tablourilor create este float32.


Pentru a crea secvențe de numere, NumPy asigură o funcție analogă funcției range din Python,
care întoarce tablouri în loc de liste, denumită arange.
Exemplul 21.4 #crearea tablourilor cu funcția np.arange()
>>> np.arange( 10, 30, 5 )
290 Numpy

array([10, 15, 20, 25])


>>> np.arange( 0, 2, 0.25 ) #argumente de tip float
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75])

Crearea secvențelor liniare cu funcția linspace


Dacă funcția arange se utilizează cu argumente float, nu se poate prezice întotdeauna numărul
de elemente obținute, datorită preciziei limitate a unității de calcul în virgulă flotantă. Din acest
motiv, se recomandă utilizarea funcției linspace care primește ca argument numărul de
termeni dorit, ca pas.
Exemplul 21.5 #crearea tablourilor cu funcția linspace()
import numpy as np
from matplotlib import pyplot as plt Output
n = np.linspace(0, 2, 9)
x = np.linspace(0, 2*np.pi, 100)
s = np.sin(x)
plt.subplot(121)
plt.plot(s)
plt.subplot(122)
plt.plot(n)
#executat in Spyder

Alte funcții pentru crearea tablourilor în Numpy sunt:


array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange,
linspace, numpy.random.rand, numpy.random.fromfunction, fromfile, etc.

21.3.2 Accesarea ordonată a tablourilor în Numpy


Accesul la conținutul unui tablou în Numpy se poate face prin intermediul indecșilor, felierii
(slicing) și iterării. Accesarea conținutului este mai simplă în cazul tablourilor unidimensionale
(o axă, un index), fiind similară cu accesarea diferitelor tipuri de secvențe din Python, în
principal listele. În cazul tablourilor multidimensionale, indecșii se utilizează în tupluri,
separați prin virgulă.
Exemplul 21.6 #indexare, slicing și iterare tablou unidimensional
>>> a = np.arange(12)**2
>>> a
array([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121], dtype=int32)
>>> a[4]
16
>>> a[3:8]
array([ 9, 16, 25, 36, 49], dtype=int32)
>>> a[: : -1]
array([121, 100, 81, 64, 49, 36, 25, 16, 9, 4, 1, 0], dtype=int32)
>>> a[:5:3]
array([0, 9], dtype=int32)
>>> for j in a:
print(j*2, " ", end="")
0 2 8 18 32 50 72 98 128 162 200 242
291 Numpy

Exemplul 21.7 # indexare, slicing și iterare tablou multidimensional


Pentru crearea unui array/tablou multidimensional se utilizează frecvent funcția
numpy.fromfunction, care primește ca argumente o funcție, dimensiunile sub forma unui
tuplu și tipul elementelor.
Exemplul 21.8 #creare tablou multidimensional utilizând o funcție lambda
np.fromfunction(lambda i, j: i + j, (3, 3), dtype=int)
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])

Exemplul 21.9 #creare tablou multidimensional (2D) folosind o funcție definită


>>> def f(x,y):
return 2*x + 3*y
>>> c = np.fromfunction(f, (4,6),dtype=int)
array([[ 0, 3, 6, 9, 12, 15],
[ 2, 5, 8, 11, 14, 17],
[ 4, 7, 10, 13, 16, 19],
[ 6, 9, 12, 15, 18, 21]])
>>> c[2,3]
13
>>> c[:,2]
array([ 6, 8, 10, 12])
>>> c[2:3,:]
array([[ 4, 7, 10, 13, 16, 19]])

Observație. Dacă numărul indecșilor este mai mic decât numărul de axe, se consideră indecșii
lipsă ca fiind (slice) compleți (parcurg toate elementele), ca în exemplul care urmează.
Exemplul 21.10 #indexare cu număr mai mic de indecși
>>> c[-1]
array([ 6, 9, 12, 15, 18, 21])

Indexul -1 corespunde ultimului rând, iar de pe ultimul rând s-au parcurs toate elementele.

Observație. Pentru completarea unui tuplu de indexare se poate utiliza simbolul dots (...). De
exemplu, dacă z este un array cu 4 axe, atunci z[1, …] este echivalent cu z[1, :, :, :].
Observație. Iterarea unui tablou multidimensional se face în raport cu prima axă.
Exemplul 21.11 #iterarea se face după prima axă
>>> for row in c:
print(row)
[ 0 3 6 9 12 15]
[ 2 5 8 11 14 17]
[ 4 7 10 13 16 19]
[ 6 9 12 15 18 21]

Observație. Pentru prelucrarea tablourilor multidimensionale după fiecare element, se poate


utiliza atributul flat, astfel:
Exemplul 21.12 #iterare după fiecare element
>>> for termen in c.flat:
... print(termen)
0
3
292 Numpy

6
9
12
15
.......
6
9
12
15
18

21.4 Prelucrarea formei tablourilor în Numpy


21.4.1 Schimbarea formei tablourilor
Forma unui tablou este dată de numărul de elemente existente pe fiecare axă.
Dimensiunile formei sunt exprimate prin atributul shape.
În operațiile de modificare a formei tablourilor sunt implicate funcțiile: ndarray, shape,
reshape, resize, ravel.

Exemplul 21.13 #utilizarea atributului shape


>>>import numpy as np
>>>x = np.random.random((3,4))
>>>x
array([[0.16228276, 0.72672429, 0.23611816, 0.46480716],
[0.88528053, 0.21038363, 0.26788794, 0.93454818],
[0.5763467 , 0.77456515, 0.94848778, 0.73097995]])
>>> x.shape
(3, 4)
Numpy oferă posibilitatea schimbării formei unui tablou prin diferite metode, fără a modifica
tabloul original.
Exemplul 21.14 #Funcția de aplatizare ravel()
>>> x.ravel()
array([0.16228276, 0.72672429, 0.23611816, 0.46480716, 0.88528053,
0.21038363, 0.26788794, 0.93454818, 0.5763467 , 0.77456515,
0.94848778, 0.73097995])

Exemplul 21.15 #funcția reshape() întoarce un tablou cu o formă modificată


>>> np.floor(100*x.reshape(6,2))
array([[16., 72.],
[23., 46.],
[88., 21.],
[26., 93.],
[57., 77.],
[94., 73.]])

Exemplul 21.16 #Aplicarea transpusei T


>>> x.T
array([[0.16228276, 0.88528053, 0.5763467 ],
[0.72672429, 0.21038363, 0.77456515],
[0.23611816, 0.26788794, 0.94848778],
[0.46480716, 0.93454818, 0.73097995]])
>>> (np.floor(10*x)).T
array([[1., 8., 5.],
[7., 2., 7.],
293 Numpy

[2., 2., 9.],


[4., 9., 7.]])
>>> (np.floor(10*x)).T.shape
(4, 3)
>>> x.shape
(3, 4)

Observație. Spre deosebire de funcția reshape(), funcția ndarray.resize() modifică însăși


tabloul original.
Exemplul 21.17 #funcția resize() modifică tabloul original
>>> y = np.floor(10*np.random.random((3,4)))
>>> y
array([[2., 3., 2., 7.],
[9., 0., 5., 3.],
[2., 3., 1., 8.]])
>>> y.shape
(3, 4)
>>> y.resize((2,6))
>>> y
array([[2., 3., 2., 7., 9., 0.],
[5., 3., 2., 3., 1., 8.]])

Observație. Atât reshape, cât și newaxis pot adăuga o nouă dimensiune unui array.
Exemplul 21.18 #reshape și newaxis adaugă o axă nouă unui array
>>> z=np.array([1.,2.,3.,4.])
>>> z.shape
(4,)
>>> z.reshape(2,2) #reshape adaugă o axă nouă
array([[1., 2.],
[3., 4.]])
>>> z
array([1., 2., 3., 4.])
>>> w = np.array([5., 6., 7., 8.])
>>> w.shape
(4,)
>>> w1 = w[np.newaxis, ...]
>>> w1.shape
(1, 4)
>>> w2=w1[np.newaxis, ...]
>>> w2.shape
(1, 1, 4)

21.4.2 Alipirea și divizarea tablourilor în Numpy


Mai multe tablouri pot fi alipite (stivuite) pe diferite axe, orizontal sau vertical.
În operațiile de alipire (stivuire) sunt implicate funcțiile: hstack, vstack, column_stack,
row_stack, concatenate, c_, r_.

Exemplul 21.19 #alipirea tablourilor ta și tb


>>>ta = np.floor(10*np.random.random((2,2)))
>>>tb = np.floor(10*np.random.random((2,2)))
>>>ta
array([[3., 3.],
[3., 8.]])
>>>tb
array([[6., 1.],
294 Numpy

[8., 8.]])
>>>np.vstack((ta, tb))
array([[3., 3.],
[3., 8.],
[6., 1.],
[8., 8.]])
>>>np.hstack((ta,tb))
array([[3., 3., 6., 1.],
[3., 8., 8., 8.]])

Un tablou mai mare poate fi divizat în unele de dimensiuni mai mici, fie pe orizontală, folosind
funcția hsplit, fie pe verticală, folosind funcția vsplit. Pentru a se preciza axa pentru care se
dorește diviziunea, se utilizează funcția array_split.
Exemplul 21.20 #splitarea tablourior
>>> x = np.floor(10*np.random.random((4,12)))
>>> x
array([[7., 5., 8., 7., 3., 5., 4., 2., 7., 6., 3., 4.],
[9., 0., 8., 5., 5., 7., 7., 9., 4., 5., 9., 5.],
[1., 5., 3., 0., 4., 7., 7., 3., 5., 4., 4., 6.],
[1., 8., 3., 0., 7., 3., 2., 7., 9., 3., 7., 4.]])
>>> np.hsplit(x,3)
[array([[7., 5., 8., 7.],
[9., 0., 8., 5.],
[1., 5., 3., 0.],
[1., 8., 3., 0.]]), array([[3., 5., 4., 2.],
[5., 7., 7., 9.],
[4., 7., 7., 3.],
[7., 3., 2., 7.]]), array([[7., 6., 3., 4.],
[4., 5., 9., 5.],
[5., 4., 4., 6.],
[9., 3., 7., 4.]])]
>>> np.vsplit(x,2)
[array([[7., 5., 8., 7., 3., 5., 4., 2., 7., 6., 3., 4.],
[9., 0., 8., 5., 5., 7., 7., 9., 4., 5., 9., 5.]]), array([[1., 5.,
3., 0., 4., 7., 7., 3., 5., 4., 4., 6.],
[1., 8., 3., 0., 7., 3., 2., 7., 9., 3., 7., 4.]])]

21.5. Copierea tablourilor în Numpy


21.5.1 Atribuirea simplă a tablourilor
Simpla atribuire a tablourilor nu produce copierea obiectelor tablou.
Exemplul 21.21 #crearea unei referințe la obiectul tablou
>>> x = np.arange(10)
>>> y = x
>>> y is x
True
>>> y.shape = 2,5
>>> x.shape
(2, 5)
În programul de mai sus, y nu este un nou obiect. Se constată că schimbând forma lui y se
schimbă și forma lui x, deci y nu este independent de x, ceea ce arată că y nu este o copie
distinctă de x. Pentru a demonstra acest lucru, se poate folosi funcția id(), prin care se poate
obține identificatorul unic al unui obiect.
295 Numpy

>>> id(x)
1549550520520
>>> id(y)
1549550520520
Se observă că x și y au același identificator de obiect.
Observație. Dacă tabloul original este șters din memorie cu del, dar referința nu a fost ștearsă,
datele vor rămâne în continuare accesibile prin referință.
Exemplul 21.22 #accesul la date în memorie prin referință, dacă originalul a fost șters
>>> x
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> w = x
>>> del x
>>> x
Traceback (most recent call last):
. . . . . .
x
NameError: name 'x' is not defined
>>> w
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])

21.5.2 Copierea prin crearea unei “vederi” (view, shalow)


Este posibil ca două obiecte tablou diferite să se refere la aceleași date, care sunt conținute însă
doar într-unul singur dintre cele două obiecte. Acest lucru este posibil când cele două obiecte
au clase derivate din aceeași clasă părinte (se poate arăta prin metoda base).
Metoda view creează un nou obiect tablou care se referă la aceleași date conținute de obiectul
tablou original, dar nu creează o copie distinctă (doar forma poate fi modificată distinct).
Exemplul 21.23 #crearea unei vederi prin metoda view
>>> x = np.arange(10)
>>> x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> y = x.view()
>>> y
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> y is x
False
>>> y.base is x
True
>>> y.flags.owndata #obiectul tablou y nu conține date proprii!
False

Modificând forma tabloului “vedere” (view), se modifică și forma tabloului original. Totuși,
dacă se modifică conținutul tabloului view, se modifică și conținutul tabloului original, ceea
ce arată că tabloul view nu este o copie distinctă a tabloului original:
>>> y.shape = 2,5
>>> y.shape
(2, 5)
>>> x.shape
(10,)
296 Numpy

>>> y[1,3] = 555


>>> x
array([ 0, 1, 2, 3, 4, 5, 6, 7, 555, 9])

Observație. Și modificările făcute asupra unor obiecte view create prin feliere din tabloul
original modifică datele din tabloul original.

21.5.3. Copierea efectivă (reală) a tablourilor în Numpy


Copierea efectivă și completă a unui tablou și a datelor sale se poate face cu funcția/metoda
copy().

Exemplul 21.24 #copierea reală a unui tablou cu metoda copy()


>>> x = np.arange(12)
>>> x.shape = 3,4
>>> x
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> w = x.copy()
>>> w is x
False
>>> w.base is x
False
>>> w[2,3]
11
>>> w[2,3] = 88
>>> x
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])

Observație. Dacă nu se dorește copierea întregului tablou, ci numai a unei părți obținute prin
slicing, copierea va fi de asemenea efectivă.

21.6. Broadcasting-ul (difuziunea, sau extinderea automată)


În matematică sunt permise operații cu tablouri numai dacă acestea îndeplinesc anumite
condiții referitoare la dimensionalitatea lor. Operațiile aritmetice se fac de regulă între
elementele corespondente, astfel încât dacă două tablouri au exact aceeași formă (același număr
de axe) și dimensiune (număr de elemente pe fiecare axă), atunci operațiile se fac fără erori.
Numpy extinde posibilitatea desfășurării unor operații între două tablouri având aceeași formă
(sau nu), dar dimensiuni diferite, care nu sunt potrivite pentru natura operațiilor, prin
broadcasting. Cel mai mic tablou este extins la dimensiunea celui mai mare, astfel încât
operația să se poată efectua.
Exemplul 21.25 #egalitatea atât a formei cât și a dimensiunilor
>>> import numpy as np
>>> a = np.array([1,2,3,4]) #4 elemete
>>> b = np.array([10,20,30,40]) #4 elemente
>>> c = a*b
>>> print(c)
[ 10 40 90 160]
297 Numpy

Exemplul 21.26 #un caz de nepotrivire pentru broadcasting


>>> x = np.array([1,2,3,4,5]) #5 elemente
>>> y = np.array([10,20,30,40]) #4 elemente
>>> w = x * y
Traceback (most recent call last):
. . .
w = x * y
ValueError: operands could not be broadcast together with shapes (5,) (4,)
Se deduce din exemplul de mai sus că pentru a avea broadcasting, trebuie îndeplinite totuși
anumite cerințe, prezentate în continuare.
Reguli pentru broadcasting
R1. Tabloul cu ndim mai mic decât celălalt (număr de axe mai mic), va fi completat cu „1” (se
adaugă o axă) în forma sa.
Exemplul 21.27 #completarea tabloului cu număr de dimensiuni mai mic
>>>import numpy as np
>>>np.array([[4, 5, 6], [7, 8, 9]]) + np.array([1, 2, 3])
array([[ 5, 7, 9],
[ 8, 10, 12]])

4 5 6 + 1 2 3 => 5 7 9
7 8 9 1 2 3 8 10 12

R2. Dimensiunea fiecarei axe a formei de ieșire este maximul dimensiunilor de intrare pentru
acea axă.
Exemplul 21.28
K (tablou 4D) : 8x1x6x1 #dimensiuni la intrare
M (tablou 3D): 7x1x5 #dimensiuni la intrare
Rezultat 8x7x6x5 #dimensiuni la ieșire (max.din intrări)
R3. Un tablou de intrare poate fi utilizat în calcul, dacă dimensiunea sa de date pentru o axă se
potrivește cu dimensiunea de ieșire de date a axei, sau valoarea sa este exact 1.
Exemplul 21.29
K (tablou 4D) : 8x6
M (tablou 1D): 1
Rezultat 8x6
sau
K (tablou 4D) : 8x6
M (tablou 1D): 6
Rezultat 8x6
R4. Dacă un tablou de intrare are dimensiunea axei egală cu 1, prima intrare de date din acea
axă este utilizată pentru toate calculele de-a lungul acelei axe.
Exemplul 21.30
>>>np.array([1, 2, 3]) + 1
array([2, 3, 4])
298 Numpy

1 2 3 + 1 1 1 =>
2 3 4
Se spune că un set de tablouri poate fi difuzat (este “broadcastabil”) dacă regulile de mai sus
produc un rezultat valid și una dintre următoarele aserțiuni este adevărată:
i. Tablourile au exact aceeași formă.
ii. Tablourile au același număr de axe, iar lungimea fiecărei axe este fie comună, fie 1.
K (tablou 3D) : 15 x 3 x 5
M (tablou 3D): 15 x 1 x 5
Rezultat 15 x 3 x 5
iii. Tabloul care are prea puține axe își va incrementa forma cu o axă de lungime 1, astfel încât
proprietatea menționată mai sus este adevărată.
K (tablou 3D) : 15 x 3 x 5
M (tablou 3D): 3 x5
Rezultat 15 x 3 x 5
Observație. Pentru operațiile elementwise (între elemente, element cu element), din condițiile
prezentate mai sus, se deduce că în cazul tablourilor, dimensiunile sunt compatibile dacă:
1. Numărele de elemente pe axă sunt egale, sau
2. Unul din tablouri este 1.
Exemplul 21.31 #a (2D, 4x3) și b (1x3) au dimensiuni compatibile
import numpy as np
a = np.array([[0.0,0.0,0.0],[10.0,10.0,10.0],[20.0,20.0,20.0],
[30.0,30.0,30.0]])
b = np.array([1.0,2.0,3.0])
print ('Tabloul T1:' )
print (a)
print ('Tabloul T2:' )
print (b)
print ('Suma T1 + T2')
print (a + b)

Output
Tabloul T1:
[[ 0. 0. 0.]
[10. 10. 10.]
[20. 20. 20.]
[30. 30. 30.]]
Tabloul T2:
[1. 2. 3.]
Suma T1 + T2
[[ 1. 2. 3.]
[11. 12. 13.]
[21. 22. 23.]
[31. 32. 33.]]

Exemplul 21.32 #a(2D, 4x3) și b(2D, 2x3) nu corespund regulilor de broadcasting


a = np.array([[0.0,0.0,0.0],[10.0,10.0,10.0],[20.0,20.0,20.0],
[30.0,30.0,30.0]])
299 Numpy

b = np.array([1.0,2.0,3.0], [50.,50.,50.])
print ('Tabloul T1:' )
print (a)
print ('Tabloul T2:' )
print (b)
print ('Suma T1 + T2')
print (a + b)

Output
Traceback (most recent call last):
. . . . .
b = np.array([1.0,2.0,3.0], [50.,50.,50.])
TypeError: Field elements must be 2- or 3-tuples, got '50.0'

Observație. Broadcastingul este de utilitate deosebită în cazul aplicațiilor de procesare a


imaginilor. Tablourile implicate nu au de multe ori același număr de dimensiuni. De exemplu,
o imagine RGB poate fi un tablou de 256x256x3 valori. Dacă se dorește să se multiplice fiecare
culoare din imagine cu o valoare diferită, se poate multiplica imaginea cu un tablou
monodimensional cu 3 valori.
Imaginea (Tablou 3D): 256x256x3
Scalarea (Tablou 1D): 3
Rezultat (Tablou 3D): 256x256x3
Observație. Broadcastingul este util și în cazul unor operații cu tablouri de tip produs extern
sau sumă externă.
Exemplul 21.33 #broadcasting
>>> t = np.array([1,2,3,4]) #vector linie
>>> t
array([1, 2, 3, 4])
>>> t.shape
(4,)
>>> t1 = t[:, np.newaxis] #se adaugă o axă nouă -> vector coloană
>>> t1
array([[1],
[2],
[3],
[4]])
>>> t1.shape
(4, 1)
>>> s = np.array([10, 20, 30])
>>> s.shape
(3,)
>>> w = t1 +s #suma
>>> w.shape #rezultă un tablou 4x3
(4, 3)
>>> w
array([[11, 21, 31],
[12, 22, 32],
[13, 23, 33],
[14, 24, 34]])

21.7. Printarea tablourilor în Numpy


La imprimarea tablourilor, în Numpy se respectă următoarele reguli:
- Ultima axă se imprimă de la stânga la dreapta.
- Penultima axă se imprimă de sus în jos.
300 Numpy

- Restul de axe se imprimă de sus în jos, secționând în secvențe cîte axe reprezintă restul.
Între secvențe se inserează câte o linie vidă.
Exemplul 21.34 #imprimare vector (1D)
import numpy as np
a = np.arange(12)
print(a)
[ 0 1 2 3 4 5 6 7 8 9 10 11]

Exemplul 21.35 #imprimare matrice (2D)


b = np.arange(12).reshape(4,3)
print(b)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]

Exemplul 21.36 #imprimare structură 3D


c = np.arange(24).reshape(2,3,4)
print(c)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
# linie vidă între secvențe
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]

În cazul tablourilor de mari dimensiuni, funcția print() omite la listare partea din mijloc a
tabloului.
Exemplul 21.37 #imprimare vector de mare dimensiune
print(np.arange(10000))
[ 0 1 2 ... 9997 9998 9999]

Exemplul 21.38 #imprimare matrice de mare dimensiune


print(np.arange(10000).reshape(100,100))
[[ 0 1 2 ... 97 98 99]
[ 100 101 102 ... 197 198 199]
[ 200 201 202 ... 297 298 299]
. . . . . . . . . . . . . . . . . . .
[9700 9701 9702 ... 9797 9798 9799]
[9800 9801 9802 ... 9897 9898 9899]
[9900 9901 9902 ... 9997 9998 9999]]
Schimbarea opțiunilor la printare se face cu np.set_printoptions(). De exemplu, opțiunea
obligării ca la listare să se afișeze întreg tabloul se face astfel:
import sys
np.set_printoptions(threshold = sys.maxsize)
# print(np.arange(10000))

21.8. Operațiile de bază cu tablouri în Numpy


În Numpy, matricile reprezintă o subclasă a clasei ndarrays. Matricile sunt strict
bidimensionale (2D), în timp ce ndarrays sunt multidimensionale (ND). Matricile moștenesc
toate atributele și metodele obiectelor ndarray.
301 Numpy

Observație. Documentația Numpy recomandă utilizarea obiectelor ndarray în locul


obiectelor matrice.
Operațiile în Numpy se pot face pentru vectori, matrice și tablouri cu dimensiunea mai mare
ca 2. Acestea pot fi simple sau specifice algebrei matriciale sau multidimensionale. Zece dintre
acestea vor fi prezentate în continuare.
1. Produsul interior (inner product) este o generalizare a produsului punct.

f = ∑ai ∙bi, (1D), f = ∑i∑j aij ∙bij (2D)

Produsul interior între doi vectori de mărime egală întoarce un singur număr (un scalar). Doi
vectori sunt perpendiculari dacă produsul lor este zero. Se poate face cu funcțiile np.inner()
și np.dot().
Produsul interior/inner se poate face și între două tablouri ndarray. În acest caz, rezultatul este
un tablou.
2. Produsul punct (dot product), sau produsul scalar, este un caz special al produsului interior.
Orice produs punct este un produs interior, dar nu invers.

𝑷 = ∑𝑛1 𝑎𝑖 𝑏𝑖 = 𝑎1 𝑏1 + 𝑎2 𝑏2 + ⋯ + 𝑎𝑛 𝑏𝑛 ,(array 1D)

𝑎11 𝑎12 𝑏11 𝑏12 𝑎 . 𝑏 + 𝑎12 𝑏21 𝑎11 . 𝑏12 + 𝑎12 𝑏22
𝐀𝐁 = [𝑎 𝑎22 ] . [𝑏21 ] = [ 11 11 ], (array 2D)
21 𝑏22 𝑎21 . 𝑏11 + 𝑎22 𝑏21 𝑎21 . 𝑏12 + 𝑎22 𝑏22

Observație. . Produsul punct (scalar) oferă rezultate diferite dacă este aplicat unor obiecte
definite ndarray sau obiecte definite matrix. În cazul obiectelor definite ca ndarray produce
același efect ca operatorul “*” (produsele element cu element = elementwise), ci = ai * bi
În cazul obiectelor definite ca matrix produce același efect ca operatorul funcție np.dot(),
cij = ∑aij ∙bij (produs matricial).
Exemplu. 21.39 #produse între două matrici
import numpy as np
# Matricile ca obiecte ndarray
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [8, 9]])
print("a", type(a))
print(a)
print("\nb", type(b))
print(b)
f = np.inner(a, b)
print ("Produsul inner a doua obiecte ndarray, np.inner(a, b),")
print(f)
# Matricile ca obiecte matrix
c = np.matrix([[1, 2], [3, 4]])
d = np.matrix([[5, 6], [8, 9]])
print("\nc", type(c))
print(c)
print("\nd", type(d))
print(d)
print("\n* operatia a*b cu două obiecte ndarray (Elementwise)")
print(a * b)
print("\n* operatia a*b cu două obiecte matrix (rezultat ca cel dat de
np.dot())")
print(c * d)
302 Numpy

g = np.inner(c, d)
print ("Produsul interior a doua obiecte matrix, np.inner(c, d)")
print(g)

Output
a <class 'numpy.ndarray'>
[[1 2]
[3 4]]
b <class 'numpy.ndarray'>
[[5 6]
[8 9]]
Produsul interior a doua obiecte ndarray, np.inner(a, b),
[[17 26]
[39 60]]
c <class 'numpy.matrix'>
[[1 2]
[3 4]]
d <class 'numpy.matrix'>
[[5 6]
[8 9]]
* operatia a*b cu două obiecte ndarray (Elementwise)
[[ 5 12]
[24 36]]
* operatia a*b cu două obiecte matrix (rezultat ca cel dat de np.dot())
[[21 24]
[47 54]]
Produsul interior a doua obiecte matrix, np.inner(c, d)
[[17 26]
[39 60]]

3. Transpusa (transpose), valabilă doar pentru matrici (obiecte 2D), se obține schimbând
liniile cu coloanele. Se poate face cu funcția np.transpose(), sau cu
ndarray.transpose(), sau cu metoda ndarray.T (o metoda specială care nu cere paranteze).
Rezultatul este același în toate cazurile.
4. Urma (trace) este suma elementelor de pe diagonala principală a unei matrice. Se poate
calcula folosind funcția trace(), sau însumând simplu elementele extrase cu metoda
diagonal().

5. Rangul (rank) unei matrici este numărul maxim de vectori coloane sau rânduri liniar
independente din care este compusă. Rangul matricii se poate determina cu funcția
matrix_rank din pachetul linalg din Numpy.

6. Determinantul se poate calcula numai pentru matrici pătrate cu funcția det() din pachetul
linalg. Dacă este zero, atunci matricea este singulară și nu se poate inversa.

7. Inversa (true inverse) unei matrice poate fi calculată numai dacă determinantul său este
diferit de zero. Se utilizează funcția inv() din pachetul linalg.
8. Pseudo-inversa. Chiar dacă determinantul matricii este zero (matricea este singulară), se
poate calcula matricea pseudo-inversă utilizând funcția pinv() din pachetul linalg.
9. Aplatizarea (flatten). În multe aplicații este necesară transformarea unei matrici într-un
vector. Acest lucru se poate face cu ajutorul metodei flatten().
10. Valorile și vectorii proprii. Prin definiție, λ este o valoare proprie (eigenvalue) și x un
vector propriu (eigenvector), dacă este îndeplinită relația: Ax = λX. Vectorii și valorile proprii
sunt importanți în Analiza Componentelor Principale (PCA). În PCA vectorii proprii ai
303 Numpy

matricilor de corelație și covarianță reprezintă direcțiile varianței maxime (componentele


principale), iar valoarea proprie corespunzătoare reprezintă mărimea variației fiecărei
componente principale.
Exemplul 21.40 #alte operații cu vectori și matrice
>>> V = np.array ([1, 2, 3, 4])
>>> U = np.array ([5, 6, 7, 8])
>>> U
array([5, 6, 7, 8])
>>> V
array([1, 2, 3, 4])
>>> U*V
array([ 5, 12, 21, 32])

>>> A = np.array([[1,2],[3,4]])
>>> B = np.array([[3,1],[5,2]])
>>> A
array([[1, 2],
[3, 4]])
>>> B
array([[3, 1],
[5, 2]])
#operatorul @
>>> A@B
array([[13, 5],
[29, 11]])
>>> A.dot(B)
array([[13, 5],
[29, 11]])
>>> A.transpose() #calculul transpusei unei matrici
array([[1, 3],
[2, 4]])
>>> np.eye(3) #generarea unei matrici unitate
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
>>> np.trace(B) #suma elementelor de pe diagonala principală
5
>>> print("\Produs fct. inner:", np.inner(U, V))
Produs fct. inner: 70

>>> print("Produs fct. dot:", np.dot(U, V))


Produs fct. dot: 70

>>> np.linalg.inv(A) #calculul inversei unei matrici


array([[-2. , 1. ],
[ 1.5, -0.5]])

>>> y = np.array([[3.],[2]])
>>> y
array([[3.],
[2.]])

>>> np.linalg.solve(A, y) #rezolvarea ecuației liniare


array([[-4. ],
[ 3.5]])

>>> np.linalg.eig(B) #determinarea valorilor proprii și a


>>> #vectorilor proprii ale unei matrici
304 Numpy

(array([4.79128785, 0.20871215]), array([[ 0.48744474, -0.33726692],


[ 0.87315384, 0.94140906]]))

Observație. Operatorii ca += , sau *= acționează asupra aceleiași variabile fără a crea una
nouă. Când se operează cu tipuri diferite, tipul rezultat va fi de tipul cel mai general dintre
operanzi.
Multe operații unare, ca de exemplu calculul sumei tuturor elementelor din array
sunt implementate ca metode ale clasei ndarray.
Exemplul 21.38 #operații implementate ca metode ale clasei ndarray
a = np.random.rand(2, 3)
print(a)
print(a.sum())
print(a.min())
print(a.max())

Output
[[0.96808519 0.33146709 0.58640625]
[0.23741603 0.27951038 0.12903056]]
2.531915496232442
0.1290305582787633
0.968085191684956

Observație. Se reamintește că o clasă poate avea “date atribut” și “metode atribut”.


Exemplul 21.39 #atributul dată (variabilă) și atributul metodă
class caine:
def __init__(self):
self.nume = "Grivei"
def latra(self):
print ("Ham! Ham!")

Atributul data_atribut se scrie:


obj.data_atribut

Atributul metoda_atribut se scrie:


obj.metoda_atribut()

Observație. Se recomandă să se evite aceeași denumire pentru atributul “dată” și atributul


“metodă”, deoarece în anumite circumstanțe atributul “dată” va suprascrie atributul “metodă”.

21.9. Funcții universale în Numpy


Funcțiile matematice de tipul sin, cos, exp, etc. sunt denumite în Numpy "funcții
universale" sau ufunc. Exemple:
add, all, any, apply_along_axis, argmax, argmin, argsort, average, bincount,
ceil, clip, conj, corrcoef, cov, cross, comprod, cumsum,diff, dot, floor,
inner, inv, lexsort, max,maximum, mean, median, min, minimum, nonzero, outer,
prod, re, round, sort, std, sum, trace, transpose, var, vdot, vectorize,
where.

Exemplul 21.40 #utilizare funcții universale


>>> N = np.arange(4)
>>> N
array([0, 1, 2, 3])
305 Numpy

>>> np.exp(N)
array([ 1. , 2.71828183, 7.3890561 , 20.08553692])
>>> np.sqrt(N)
array([0. , 1. , 1.41421356, 1.73205081])
>>> M = np.array([-2, -1, -3, -4])
>>> np.add(M, N)
array([-2, 0, -1, -1])
>>> np.sin(N)
array([0. , 0.84147098, 0.90929743, 0.14112001])
>>> np.cos(M)
array([-0.41614684, 0.54030231, -0.9899925 , -0.65364362])

21.10 Întrebări, exerciții și probleme


21.10.1 Care este sintaxa corectă pentru a crea un tablou Numpy ?
a. np.object([1,2,3,4,5])
b. np.array([1,2,3,4,5])
c. np.CreateArray([1,2,3,4,5])

R21.10.1 b
21.10.2 Care este sintaxa corectă pentru verificarea numărului de dimensiuni ale unui tablou?
a. np.dim b. np.ndim c. np.ndim() d. np.dim()

R21.10.2 c
21.10.3 Care este sintaxa corectă pentru imprimarea corectă a numărului 7 din tabloul
următor?
tb = np.array([1,2,3,4], [5,6,7,8])
a. print(tb[3,0]) b. print([2,3]) c. p([1, 2]) d. p([0,3])

R21.10.3 c
21.10.4 Care este sintaxa corectă pentru a imprima numerele [2,3,4] din tabloul următor :
xt = np.array([1,2,3,4,5,6,7,8])
a. print(xt[2:5]) b.print(xt[1:4]) c.print(xt[3:6]) d.print(xt[2:4])
e. print(xt[1:3])

R21.10.4 b
21.10.5 Care este sintaxa pentru imprimarea ultimelor 4 numere din tabloul de mai jos ?
tb = np.array([1,2,3,4,5,6,7,8])
a. print(tb[:4]) b. print(tb[4:]) c. print(tb[5:]) d. print(tb[5:8])

R21.10.5 b
21.10.6 Care este sintaxa care va imprima numerele cu index par din tabloul de mai sus ( tb) ?
a. print(tb[1:3:5:7]) b. print(tb[0:step=2]) c. print(tb[::2])

R21.10.6 c
21.10.7 Care este sintaxa corectă pentru verificarea tipului de dată al unui tablou ?
a. x.type b. X.ntype c. x.dtype d.datatype
R21.10.7 c
21.10.8 Care este sintaxa corectă pentru a crea un tablou de tip float ?
306 Numpy

a. tf=np.float([1,2,3,4]) b. tf=np.array([1,2,3,4], dtype= 'f')


c. tf=array([1,2,3,4], float)

R21.10.8 b
21.10.9 Ce înseamnă în Numpy forma unui tablou (shape)?
a. numărul de coloane b. numărul de elemente pe fiecare axă c. numărul de rânduri
R2110.9 b
21.10.10 Care dintre următoarele afirmații referitoare la y = x.view() în Numpy este
adevărată ?
a. Vederea y nu este afectată de schimbările în tabloul original x.
b. Vederea y este afectată de schimbările în tabloul original x.
R21.10.10 b
21.10.11 Care este sintaxa corectă pentru obținerea formei tabloului xt ?
a. xt.shape b. shape(xt) c. xt.shape()

R21.10.11 a
21.10.12 Care este metoda corectă pentru lipirea a două tablouri ?
a. join() b. concatenate() c. array_join()
R21.10.12 b, de exemplu: np.concatenate((xt, tb), axis=0)
21.10.13 Care este metoda corectă de căutare a unui element într-un tablou ?
a. search() b. find() c. where() d. searchsorted()

R21.10.13 c, de exemplu:
import numpy as np
b = np.array([1, 2, 2, 3, 4, 5, 2,])
x = np.where(b == 2)
print(x)

Output
array([1, 2, 6], dtype=int64)

21.10.14 Cum se utilizează random în Numpy, pentru obținerea unei distribuții normale de
numere concentrate în jurul lui mu = 60 cu o deviație standard sigma = 0.2 ?
R21.10.14 random.normalvariate(mu = 60, sigma = 0.2)
21.10.15 Care este sintaxa corectă în Numpy pentru adunarea numerelor din tabloul x1 la
numerele din tabloul x2 ?
a. sum(x1, x2) b. np.append(x1, x2) c. np.add(x1, x2)

R21.10.15 a. suma tuturor numerelor din tabloul x1 la suma tuturor numerelor din tabloul x2,
b. concatenează cele două tablouri, c. însumează element cu element cele două tablouri. De
exemplu:
x1 = np.array([1, 2, 3, 4])
x2 = np.array([1, 2, 3, 4])
>>> sum(x1, x2)
array([11, 12, 13, 14])
>>> np.append(x1, x2)
307 Numpy

array([1, 2, 3, 4, 1, 2, 3, 4])
>>> np.add(x1, x2)
array([2, 4, 6, 8])

21.10.16 Se poate reformata un array 1D cu 24 elemente ca un array de forma (2, 4, 3) ? Dar


un array cu 25 elemente?
R21.10.16 După cum se vede din exemplul care urmează, pentru ca reformatarea să fie posibilă,
este necesar ca numărul de elemente al noului array să fie egal cu numărul total de elemente al
vechiului array. Arrayul cu 25 nu se poate reformata sub forma (2, 4, 3).
import numpy as np
a = np.arange(24)
a.ndim
print(a)
print("reshape array cu 24 elemente")
b = a.reshape(2,4,3)
print(b)
print("reshape array cu 25 elemente -> eroare")
c = np.arange(25)
d = c.reshape(2,4,3)

Output
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
reshape array cu 24 elemente
[[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]

[[12 13 14]
[15 16 17]
[18 19 20]
[21 22 23]]]
reshape array cu 25 elemente -> eroare
Traceback (most recent call last):
. . . . . . .
d = c.reshape(2,4,3)
ValueError: cannot reshape array of size 25 into shape (2,4,3)

21.10.17 Care va fi rezultatul adunării următoarelor două array-uri: a de forma (2,3,4), iar b de
forma (2,1,4) ? Explicați.
R21.10.17 Cele două array-uri sunt compatibile în dimensiuni pentru a se aplica procesul
broadcasting, deoarece au aceeași număr de elemente în două dimensiuni, iar pe a treia
dimensiune unul din array-uri are 1 element.
import numpy as np
a = np.arange(24).reshape(2,3,4)
b = np.arange(8).reshape(2,1,4)
c = a + b
print(c.shape)
Output
(2, 3, 4)

21.10.18 Funcția broadcast() este utilă când se dorește să se verifice dacă este posibilă
operațiunea de broadcasting și să se vadă forma sumei a + b fără un calcul explicit. Să se scrie
o secvență de exemplificare.
308 Numpy

R21.10.18
import numpy as np
a = np.empty((4,5,3,2))
b = np.empty((3, 2))
x = np.broadcast(a,b)
print(x.shape)

Output
(4, 5, 3, 2)

21.10.19 Să se divizeze un tablou unidimensional cu 6 elemente în 3 subtablouri cu câte 2


elemente utilizând funcția numpy.split().
R21.10.19 Rezultatul diviziunii unui array este o listă conținând subarray-urile cerute.
import numpy as np
a = np.array([1, 2, 3 ,4 ,5, 6])
b = np.split(a,3)
print(b)

Output
[array([1, 2]), array([3, 4]), array([5, 6])]
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 22

Matplotlib
22.1 Generalități
Matplotlib este un modul care conține clase și funcții de creare grafice și figuri, asemănător
stilului Matlab.
Matplotlib a fost creat de John D. Hunter. În majoritate Matplotlib este scris în Python, restul
în C, Objectiv-C și Javascript.
Cel mai important submodul este pyplot, având aliasul plt.
>>>import matplotlib.pyplot as plt

Utilizând comanda dir(plt) se poate obține lista tuturor obiectelor submodulului.


Observație. Stabilirea inițială a tuturor proprietăților Matplotlib se face prin fișierul text de
configurare matplotlibrc. Aceste proprietăți pot fi modificate la execuție, dacă se dorește de
programator, prin atribuirea altor valori parametrilor rc (“runtime configuration”) din
obiectul rcParam. La lansare, Matplotlib caută fișierul de configurare automat în mai multe
locații prestabilite (începând cu directorul curent de lucru).
Obiectele de bază utilizate sunt figure și axes.
Obiectul figure poate fi considerat o pagină virtuală, asemănătoare unei “pânze” (canvas) pe
care apare graficul.
Obiectul axes conține setul de axe (de regulă coordonatele x și y) față de care sunt reprezentate
datele.
Orice desen este alcătuit din puncte (markeri). Pentru desenarea punctelor pe ecran se utilizează
funcția plot(). Implicit, funcția plot() trasează o linie din punct în punct. Pentru specificarea
punctelor, funcția utilizează argumente. Argumentul 1 este un tablou conținând coordonatele
punctelor corespunzătoare axei x, iar argumentul 2 este un tablou conținând coordonatele
punctelor corespunzătoare axei y.
Funcția plot()poate avea orice număr de argumente.
Sintaxa:
plot([x], y, [fmt], [x2], y2, [fmt2], ..., **kwargs)

x și y sunt coordonatele punctelor sau nodurilor liniei. Valorile lui x sunt opționale.
Parametrul fmt este opțional și se utilizează pentru definirea culorii, markerului, sau stilului
liniei.
Funcția întoarce o listă de obiecte Line2D reprezentând datele afișate.
Exemplul 22.1 #Utilizarea funcției pyplot.plot() cu crearea automată a figurii și axelor
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100.5,0.5) # Generarea valorilor corespunzătoare axei x
y = 2.0*np.sqrt(x) # Calcularea valorilor corespunzătoare axei y
plt.plot(x,y) # Crearea obiectelor figure și axis
plt.show() # Afișarea graficului pe ecran
310 Matplotlib

Output

Observație. Utilizând funcția pyplot.plot() se generează figura și axele automat. Dacă se


dorește un control mai mare asupra modului de afișare se pot utiliza separat funcția
pyplot.figure() pentru generarea figurii și metoda add_axes() pentru crearea axelor.
add_axes(rect, projection=None, polar=False, **kwargs),

unde rect este o secvență de float reprezentând dimensiunile cadrului de lucru în care sunt
prezente axele: [l, b, w, h] (left, bottom, width, height).
Exemplul 22.2 #funcția plot cu număr arbitrar de argumente
import numpy as np
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4, 5, 6], [1, 4, 9, 16, 25, 36], 'ro')
plt.show()

Output

Exemplul 22.3 #utilizarea funcției pyplot.figure()și utilizarea metodei add_axes().


import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100.5,0.5) # Generarea valorilor coresp. axei x
y = 2.0*np.sqrt(x) # Calcularea valorilor coresp. axei y
fig = plt.figure() # Crearea figurii
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # Crearea axelor
ax.plot(x,y) # Plotarea datelor
plt.show() # Afișarea pe ecran

Output
311 Matplotlib

S-a obținut aceeași ieșire și cu procedeul alternativ.


Făcând o comparație a celor două abordări se observă, ca în tabelul de mai jos, că linia de
program 1A (conținând instrucțiunea plt.plot) este echivalentă cu trei linii de program 1B,
2B, 3B. Mai mult, se constată că funcția plot() nu este aceeași în cele două abordări (1A este
o funcție în modulul pyplot, diferită de 3B, care reprezintă o metodă a obiectului ax).
A. utilizarea generării automate a B. utilizarea funcției plt.figure()și metodei
figurii și axelor add_axes()

1A) plt.plot 1B)fig = plt.figure()


2A) plt.show 2B)ax = fig.add_axes([0.1,0.1,0.8,0.8])
3B)ax.plot(x,y)
4B)plt.show()

22.2 Lucrul cu mai multe figuri și mai multe axe


Pyplot utilizează conceptele “figura curentă” și “axele curente”.
Dacă se declară mai multe figuri în programul Python, figura curentă este întotdeauna ultima
declarată în secvența program, până la următoarea declarație.
Observație. În exprimarea subplot(abc), unde abc sunt cifre, ab reprezintă grid-ul, iar c este
numărul de ordine al reprezentării în cadrul gridului. De exemplu: subplot(211) reprezintă un
grid 2x1, figura numărul 1, subplot(325) reprezintă un grid 3x2, figura a cincea, etc.
Exemplul 22.4 #figura curentă
import numpy as np
import matplotlib.pyplot as plt
def f1(t):
return np.exp(-t) * np.cos(2*np.pi*t)
def f2(t):
return np.exp(-t/4) * np.cos(2*np.pi*t)
t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)
plt.figure(1) #figura curentă 1
plt.subplot(211) #plotul numarul 1 din figura 1
plt.plot(t1, f1(t1), 'bo', t2, f1(t2), 'k')
plt.subplot(212) #plotul numărul 2 din figura 1
plt.plot(t2, np.cos(2*np.pi*t2), 'r--')
plt.show(1)
312 Matplotlib

#pana aici figura curenta a fost figura 1


plt.figure(2) #de aici figura curenta este figura 2
plt.subplot(211) #plotul numarul 1 din figura 2
plt.plot(t1, f2(t1), 'bo', t2, f2(t2), 'k')
plt.subplot(212) #plotul numarul 2 din figura 2
plt.plot(t2, np.cos(2*np.pi*t2*4), 'g--')
plt.show(2)

Output

Observație. În cazul imprimării unei figuri sau grafic în cadrul unui singur set de axe (2 axe)
se recomandă utilizarea funcției pyplot.plot(), iar în cazul existenței mai multor axe de
coordonate se recomandă utilizarea metodelor pyplot.figure() și figure.add_axes(), sau
similare.
Pentru a lucra cu figurile și axele curente sunt necesare referințe, astfel:
- referința la figura curentă se obține cu pyplot.gfc(), de exemplu: fig = plt.gfg()
- referința la axele curente se obține cu pyplot.gca(), de exemplu: ax = plt.gca().
Plotarea mai multor linii pe un singur set de axe se face prin utilizarea repetată a funcției
pyplot.plot(), sau metodei axes.plot(). Culoarea fiecărui set de linii se selectează
automat, diferit.
Exemplul 22.5 #plotare mai multe grafice (linii) pe un set de axe
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100.5,0.5) # Generare valori pentru axa x
y1 = 2.0*np.sqrt(x) # Calcul y1 pentru axa y
y2 = 12*x**(1.0/3.0)+20 # Calcul y2 pentru axa y
y3 = (x)**(1.1) - 7 # Calcul y3 pentru axa y
plt.plot(x,y1) # Plotare y1
plt.plot(x,y2, ls = '--') # Plotare y2
plt.plot(x,y3, ls ='-.') # Plotare y3
plt.show() # Afișare plot pe ecran

Output
313 Matplotlib

În varianta alternativă se obține același rezultat.


Exemplul 22.6 #plotare mai multe grafice (linii) pe un set de axe, varianta alternativă
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100.5,0.5) # Generare valori pentru axa x
y1 = 2.0*np.sqrt(x) # Calcul valori y1 pentru axa y
y2 = 12*x**(1.0/3.0)+20 # Calcul valori y2 pentru axa y
y3 = (x)**(1.1) - 7 # Calcul valori y3 pentru axa y
fig = plt.figure() # Creare figura
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # Creare axe
ax.plot(x,y1) # Plotare y1
plt.plot(x,y2, ls = '--') # Plotare y2
plt.plot(x,y3, ls ='-.') # Plotare y3
plt.show() # Afișare plot pe ecran

Output

Observație. Se poate adăuga un nou plot în aceeași figură prin adăugarea unui nou obiect
axes în același canvas.

Exemplul 22.7 #includerea unui plot în plot


import matplotlib.pyplot as plt
import numpy as np
import math
x = np.arange(0, math.pi*2, 0.05)
fig=plt.figure()
314 Matplotlib

axes1 = fig.add_axes([0, 0, 1, 1]) #Axele principale


axes2 = fig.add_axes([0.6, 0.5, 0.35, 0.4]) #axele inserate = np.sin(x)
axes1.plot(x, y, 'b')
axes2.plot(x,np.cos(x),'g')
axes1.set_title('Sinus')
axes2.set_title("Cosinus")
plt.show()

Output

Argumentele funcției plot()


Cuvântul cheie utilizat pentru stabilirea culorii liniei afișate în plot este color, sau simplu c,
căruia i se atașează o valoare corespunzătoare culorii în matplotlib, astfel:
–'b', ‘blue’
–'g', ‘green’
–'r', ‘red’
–'c', ‘cyan’
–'m', ‘magenta’
–'y', ‘yellow’
–'k', ‘black’
–'w', ‘white’
Observație. Culorile pot fi specificate și ca în HTML, prin nume sau reprezentarea
hexazecimală:
Exemplul 22.8 #reprezentare prin nume, sau ca valori hexazecimale (în total 167 variante)
fuchsia = #FF00FF
aquamarine = #7FFFD4
315 Matplotlib

Nuanțele de gri pot fi reprezentate prin numere reale cuprinse între 0.0 și 1.0, unde extremele
0.0 reprezintă negru/black și 1.0 reprezintă alb/white.
Alte cuvinte cheie pentru reprezentarea stilului liniilor afișate în grafice sunt : linestyle (sau
ls), linewidth (sau lw), marker, markersize , etc.

1. Linestyle (sau ls) precizează stilul liniei afișate. Valori posibile:


Solid ls = ' -'
Linii (dashed) ls = '--'
linie-dot ls = ‘-.’
Două-puncte (colon) ls = ‘:’
Fără linie ls =
‘None’
2. Linewidth (sau lw) precizează lățimea liniei afișate. Valori posibile: 1, 2, 3, etc.
3. marker precizează stilul markerului. Valori posibile:
circle marker = 'o'
diamond marker = 'D'
thin diamond marker = 'd'
no marker marker = 'None'
+ marker = '+'
x marker = 'x'
point marker = '.'
square marker = 's'
star marker = '*'
triangle down marker = 'v'
triangle up marker = '^'
triangle left marker = '<'
triangle right marker = '>'
pentagon marker = 'p'
hexagon marker = 'h' or 'H'
octagon marker = '8'
down-caret marker = '7'
left-caret marker = '4'
right-caret marker = '5'
up-caret marker = '6'
horizontal line marker = '_'
vertical line marker = '|'
4. markersize (sau ms) precizează mărimea markerului în puncte. Valori posibile: 10, 14, 17,
etc.
5. markeredgecolor (sau mec) precizează culoarea liniei de delimitare/contur a markerului.
Valori posibile: orice culoare acceptată de matplotlib.
6. markeredgewidth (mew) precizează grosimea liniei de contur a markerului. Valori posibile:
2, 3, 5, etc.
7. markerfacecolor (sau mfc) precizează culoarea de umplere a markerului. Valori posibile:
orice culoare acceptată de matplotlib.
8. label – se utilizează pentru identificarea liniilor. Valori posibile: orice șir.

Exemplul 22.9 #utilizare opțiuni plot


import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100,10) # Calcul valori axa x
y1 = 7.0*np.sqrt(x) # Calcul valori axa y
y2 = 3.0*x**(1.0/3.0) # Calcul valori axa y
316 Matplotlib

y3 = (x)**(1.1) - 7 # Calcul valori axa y


plt.grid()
plt.plot(x,y1,c = 'r', ls = '-', marker = 'o', mec = 'blue',
ms = 15)
plt.plot(x,y2,c = 'aquamarine', ls = '--', lw = 5, marker = 'd',
mec = 'brown', mfc = 'green', mew = 3, ms = 10)
plt.plot(x,y3,c = 'r', ls = '-', marker = 'x', mec = 'blue',
ms = 15)
plt.show() # Afisare figura pe ecran

Output

Observație. Pentru specificarea rapidă a culorii liniilor și a stilurilor se pot utiliza scurtături,
ca în exemplul care urmează.

Exemplul 22.10 #scurtături pentru culori și stiluri


import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100,10) # Generarea valorilor pentru axa x
y1 = 2.0*np.sqrt(x) # Calcularea valorilor y1
y2 = 3.0*x**(1.0/3.0) # Calcularea valorilor y2
plt.plot(x,y1,'r-o')
plt.plot(x,y2,'g--^')
plt.grid()
plt.show() # afisare pe ecran

Output
317 Matplotlib

Observație. Graficele logaritmice se obțin prin înlocuirea comenzii plot() cu următoarele


comenzi:
- semilogx() creează axa logaritmică x.
- aemilogy() creează axa logaritmică y.
- loglog() creează ambele axe logaritmice, x și y.

Comenzile pentru afișarea titlurilor sunt:


- pyplot.title(' titlul - sir de caractere ')
- axes.title(' titlul - sir de caractere ')

Controlul mărimii caracterelor prin utilizarea cuvântului cheie size. Opțiuni:


1. 'xx-small',
2. 'x-small',
3. ‘small’,
4. ‘medium’,
5. ‘large’,
6. ‘x-large’,
7. ‘xx-large’,
8. Mărimea numerică a fontului exprimată în puncte.

Etichetele pentru axe se afișează astfel:


a. Utilizând funcții pyplot:
• pyplot.xlabel('etichetă – șir de caractere')
• pyplot.ylabel('etichetă – șir de caractere')
b. Utilizând metodele axelor:
• axes.set_xlabel('etichetă – șir de caractere')
• axes.set_ylabel('etichetă – șir de caractere')
c. prin cuvântul cheie size.

Etichetele axelor pot primi următorii parametri:


• size – la fel ca în titlul graficului
• horizontalalignement = ['center' | 'right' | 'left']
• verticalalignement = ['center' | 'top' | 'bottom' | 'baseline']
• rotation = [unghi exprimat in grade | 'vertical' | 'horizontal']
• color = orice culoare acceptată de matplotlib

Includerea în titluri și etichete a simbolurilor grecești sau matematice se poate face prin
următoarele metode:
- Utilizând sintaxa LaTeX.
- Șirurile matematice trebuie să fie incluse între semne $.
- Șirurile matematice pot fi reprezentate ca șiruri brute (r 'șir').
- Elementele cuprinse între acolade { } sunt grupate împreună.
- Caracterele superscript și subscript sunt definite prin simbolurile '^' și '_'.
- Spațiile sunt inserate prin backslash-slash '\/'.
- Literele grecești sunt redate printr-un backslash urmat de numele literei.
Exemplul 22.11 #metode pentru specificarea etichetelor axelor
318 Matplotlib

• r'$x^{10}$' >> x10


• r'$R_{final}$' >> Rfinal
• r'$\alpha^{\eta}$' >> αn
• r'$\sqrt{x}$' >> √𝑥
3
• r'$\sqrt[3]{a}$' >> √𝑎𝑛

http://matplotlib.sourceforge.net/users/mathtext.html#mathtext-tutorial

Pentru stabilirea limitelor axelor se utilizează următoarele variante (min și max sunt limitele
inferioară și superioară ale domeniului axelor):
- precizarea limitelor prin funcții: xlim(min, max), ylim(min, max).
- Precizarea limitelor prin metodele obiectului axe: set_xlim(min, max), set_ylim(min,
max).

Stabilirea poziției diviziunilor și etichetelor se poate face prin funcții și metode având
următoarele argumente:
-loc este o listă sau tuplu conținând pozițiile diviziunilor (ticks).
-lab este o listă sau tuplu opționale conținând etichetele pentru diviziuni.
Observație. loc și lab trebuie să aibă aceeași dimensiune. Pentru fonturi se poate utiliza
size.
Funcții pyplot:
- xticks(loc, lab)
- yticks(loc, lab)
Metode ale axelor:
- set_xticks(loc) și set_xticklabels(lab)
- set_yticks(loc) și set_ytickslabels(lab)

Exemplul 22.12 #stabilirea poziției diviziunilor


import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100.5,0.5) # Generarea valorilor corespunzătoare axei x
y = 2.0*np.sqrt(x) # Calcularea valorilor corespunzătoare axei y
loc = (5, 35, 65, 95)
lab = ('Unu', 'Doi', 'Trei', 'Patru')
plt.xticks(loc, lab, size = 'x-large')
plt.grid()
plt.plot(x,y) # Crearea obiectelor figure și axis
plt.show() # Afișarea graficului pe ecran

Output
319 Matplotlib

Observație. În cazul în care se utilizează un număr mare de figuri, pentru folosirea eficientă
a memoriei, se recomandă închiderea figurilor neutilizate cu comanda pyplot.close.

22.3 Trasarea și proprietățile grilelor


Liniile de tip grilă pot fi adăugate unei figuri utilizând funcția pyplot grid(). Argumentele
principale ale funcției sunt:
- axis = 'both' | 'x' | 'y' (implicit plt.grid(axis = 'both'))
- which = 'major' | 'minor' | 'both' (se referă la diviziunile axelor)
De asemenea, mai sunt și alte argumente pentru controlul tipului de linie, culorii, etc.
Exemplul 22.13 #adăugarea grilelor
import numpy as np
import matplotlib.pyplot as plt
x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
y = np.array([240, 252, 240, 265, 270, 290, 385, 310, 320, 350, 320, 325])
plt.title("Vanzari automobile")
plt.xlabel("Luna din an")
plt.ylabel("Voloare mii lei")
plt.plot(x, y)
#se precizeaza proprietatile grilei
plt.grid(color = 'green', linestyle = '--', linewidth = 0.5)
plt.show()

Output

Duplicarea axelor este o altă operațiune care prezintă interes frecvent. Axele x și y pot fi
făcute să apară pe partea opusă a figurii grafice utilizând funcțiile twinx() și twiny().
Exemplul 22.14 #duplicare cu păstrarea scalei
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_axes([0.15, 0.1, 0.75, 0.85])
x = np.arange(0.0,100.0)
y = x**3
ax1.plot(x,y)
ax1.set_yticks([0, 250000, 500000, 750000, 1000000])
ax1.set_ylabel('Y (metri)', size = 'large')
320 Matplotlib

ax2 = plt.twinx(ax1)
ax2.set_yticks(ax1.get_yticks())
plt.show()

Output

Exemplul 22.15 #duplicare cu scale diferite: stânga diferită de dreapta


import numpy as np
import matplotlib.pyplot as plt
# Creare de date de test
t = np.arange(0.01, 10.0, 0.01)
data1 = np.exp(t)
data2 = np.sin(2 * np.pi * t)
fig, ax1 = plt.subplots() #creeaza o figura si un set de subploturi
#implicit nows=1, ncols=1
color = 'tab:red'
ax1.set_xlabel('timpul (s)')
ax1.set_ylabel('exponentiala', color=color)
ax1.plot(t, data1, color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx() #instantiaza al doilea set de axe care duplica
#setul de axe ax1
color = 'tab:blue'
ax2.set_ylabel('sinusoida', color=color) # acelasi x-label cu ax1
ax2.plot(t, data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout() # ajustare plot in figura
plt.show()

Output
321 Matplotlib

Artificiul folosit mai sus este utilizarea a două axe diferite care partajează aceeași axă x. Astfel
se pot folosi formateri și locatori matplotlib.ticker separați, axele fiind independente.
Observație. Pentru a genera axe care partajează axa y, dar generează scale diferite sus și jos
(scale x diferite) se utilizează metoda Axes.twiny.

22.4 Crearea legendelor


Pentru a crea o legendă este necesar să se atribuie fiecărei linii reprezentate în grafic o etichetă
(un șir care descrie linia), utilizând cuvântul cheie label în funcția plot(). Legenda va fi
creată utilizând funcția pyplot.legend sau metoda axes.legend().
Poziția legendei poate fi precizată printr-o valoare care corespunde unei poziții în cadrul figurii,
astfel:
0 → best location
1 → upper right
2 → upper left
3 → lower left
4 → lower right
5 → right
6 → center left
7 → center right
8 → lower center
9 → upper center
10 → center.
Exemplul 22.16 #crearea legendei
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,100.0)
y1 = np.cos(2*np.pi*x/50.0)
y2 = np.sin(2*np.pi*x/50.0)
plt.plot(x, y1, 'b-', label = 'Cosinus')
plt.plot(x, y2, 'r--', label = 'Sinus')
plt.legend(('Cosinus', 'Sinus'), loc = 0)
plt.show() # afisare plot

Output
322 Matplotlib

Stabilirea dimensiunii fontului în legendă


Pentru a utiliza diferite mărimi de font în legende, se poate specifica dimensiunea fontului fie
ca număr de puncte (de exemplu, 12) sau prin cuvinte cheie 'large', 'small', etc.
ax.legend(loc=0,prop=dict(size=12))

Exemplul 22.17 #stabilire dimensiune font în legendă


import numpy as np
from matplotlib import pyplot as plt
x = np.arange(0,200.0)
y1 = np.cos(2*np.pi*x/50.0)
y2 = np.sin(2*np.pi*x/50.0)
plt.plot(x, y1, 'b-', label = 'Cosine')
plt.plot(x, y2, 'r--', label = 'Sine')
leg = plt.legend(('Cosine', 'Sine'), loc = 0)
for t in leg.get_texts():
t.set_fontsize('small')
plt.show() # afisare plot

Output

Alți parametri pentru legend

Argument Descriere
numpoints Număr de puncte pentru linia de bordură a legendei
markerscale Raportul mărimii markerelor legendă - figură
frameon True|False, bordura legendei există sau nu
fancybox None|True|False, legenda are sau colțuri rotunde
shadow None|True|False, legenda se afișează cu efect umbră
ncol Număr de coloane pentru legendă
mode ‘expand’|None, extinde legenda orizontal la toată
figura
title Șirul reprezentând titlul
borderpad Număr de spații la interiorul cadrului legendei
labelspacing Spațierea verticală a intrărilor legendei
handlelength Lungimea liniilor din legendă
handletextpad Paddingul între liniile legendei și text
borderaxespad Paddingul între axe și bordura legendei
columnspacing Spațierea orizontală între coloane
323 Matplotlib

Exemplul 22.18 #utilizare parametri pentru legend


#Calculul seriei

import matplotlib.pyplot as plt


import matplotlib.lines as lns
import numpy as np
def f_serie(n, t):
summation = sum([np.sin(k * t) / k for k in range(1, n + 1)])
return summation
ts = np.linspace(-5, 15.5, 1000)
for n in range(1, 5):
fs = [f_serie(n, t) for t in ts]
plt.plot(ts, fs, label=f'$n={n}$')
plt.margins(x=0, tight=True)
plt.xlabel('$t$')
plt.ylabel('$f(n, t)$')
plt.legend(ncol=2, numpoints = 3, fancybox = true, shadow = true)
plt.legend(ncol=2, title = 'Numar termeni in serie', numpoints = 2,
fancybox = True, shadow = True)
plt.show()

Output

22.5 Reprezentarea polară


Pentru o afișare grafică în coordonate polare se utilizează funcția:
pyplot.polar(unghi, distanța)

unde unghi este în radiani.


Observație. Și alți parametri (linestyles, color, etc.) utilizați în funcția plot() sunt
acceptați de funcția polar().
Exemplul 22.19 #reprezentarea polară
import numpy as np
import matplotlib.pyplot as plt
324 Matplotlib

theta = np.linspace(0,8*np.pi,100)
r = np.linspace(0,20,100)
plt.polar(theta, r, 'bs--')
plt.show()

Output

22.6 Grafic cu bare


Graficul cu bare se obține cu pyplot și metoda bar().
Exemplul 22.20 #graficul cu bare
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,10)
y = [6.6, 4.0, 1.5, 3.3, 8.0, 2.8, 4.2, 7.7, 2.3, 8.9]
plt.bar(x,y, width = 0.4, align = 'center')
plt.show()

Output
325 Matplotlib

Tabelul 22.1 #parametrii funcției bar()

Keyword Purpose Values


color Controls color of the bars Any valid matplotlib color such as
'red', 'black', 'green' etc.
edgecolor Controls color of bar edges Any valid matplotlib color such as
'red', 'black', 'green' etc.
bottom y coordinate for bottom of List of floating point values.
bars Useful for creating stacked bar
graphs.
width Controls width of bars Floating point value
linewidth or lw Controls width of bar Floating point value. None for
outline default linewidth, 0 for no edges.
xerr or yerr Generates error bars for List of floating point numbers
chart.
capsize Controls size of error bar Floating point. Default is 3.
caps
align Controls alignment of bars ‘edge’ or ‘center’
with axis labels
orientation Controls orientation of ‘vertical’ or ‘horizontal’
bars
log Sets log axis False or True

22.7 Grafic de tip pie (pie chart)


Graficul de tip pie se obține cu pyplot și metoda pie(.

Exemplul 22.21 #graficul pie


import matplotlib.pyplot as plt
import numpy as np
ponderi = [58, 30.6, 15.5, 8]
eticheta = ['Grau', 'Porumb', 'Secară', 'Orez']
plt.pie(ponderi, labels = eticheta)
plt.show()

Output

Exemplul 22.22 # pie-chart cu separare


import matplotlib.pyplot as plt
import numpy as np
326 Matplotlib

ponderi = [58, 30.6, 15.5, 8]


eticheta = ['Grau', 'Porumb', 'Secară', 'Orz']
plt.pie(ponderi, explode = [0, 0.12, 0.15, 0.1], labels = eticheta)
# parametrul explode stabileste spatiile dintre segmente
plt.show()

Output

22.8 Plasarea textului pe grafic


Sunt disponibile două procedee de precizare a coordonatelor a începutului de text pe grafic:
- procedeul “coordonatelor datelor”;
- procedeul “coordonatelor axelor”.
În ambele cazuri, plasarea textului pe grafice se poate face utilizând funcția sau metoda
text(x,y,s) aparținând pyplot, respectiv obiectului axes. Argumentele x și y sunt
coordonatele calculate în mod specific fiecărui procedeu, iar s este șirul reprezentând textul ce
va fi afișat. Se mai pot utiliza și alte argumente ca de exemplu size, color, rotation,
backgroundcolor, linespacing, horizontalalignement și verticalalignament.

Metoda “coordonatelor datelor” de plasare a textului este implicită. Coordonatele x și y


corespund valorilor în reprezentarea 2D tradițională a liniilor pe grafic, luînd valori pozitive și
negative.
În cazul procedeului “coordonatelor axelor”, coordonatele sunt normalizate relativ la axe, cele
patru colțuri ale figurii fiind marcate de limitele (0,0) – stânga-jos, (1,0) – dreapta-jos, (0, 1) –
stânga-sus, (1,1) – dreapta-sus.
Pentru a utiliza procedeul “coordonatele axelor” este necesară utilizarea cuvântului cheie
transform, astfel:
transform = ax.transAxes

Observație. Utilizarea lui transform solicită o instanță obiect axes, astfel că în prealabil
acesta trebuie creat, astfel:
ax = plt.gca()
327 Matplotlib

Exemplul 22.23 #plasarea textului pe grafic


import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,100.5,0.5)
y = 2.0*np.sqrt(x*x*x)
plt.plot(x,y)
plt.text(25,150,'Text plasat prin coordonatele datelor')
ax = plt.gca()
plt.text(0.35, 0.65,'Text plasat utilizând coordonatele axelor', transform
= ax.transAxes)

Output

22.9 Desenarea de linii pe figură


1. Desenarea de linii orizontale și verticale
Pentru desenare se poate utiliza fie procedeul funcțiilor pyplot, fie metodele obiectelor axe.
Liniile orizontale se obțin cu funcția hlines():
hlines(y, xmn, xmx) desenează o linie orizontală la coordonata y precizată.
Liniile verticale se obțin cu funcția vlines():
vlines(x, ymn, ymx) desenează o linie verticală la coordonata x specificată.
Argumentele xmn, xmx, ymn și ymx sunt opționale, reprezentând coordonatele minime și
maxime ale liniilor.
2. Desenarea de linii arbitrare
Pentru a desena o linie oarecare pe figură se utilizează funcția Line2D() din modulul
matplotlib.lines, care trebuie importat.

Observație. Dacă se dorește să se mai adauge o linie în figură, se poate folosi metoda
add_line() a obiectului ax.
Exemplul 22.24 #utilizarea metodei add_line()
import matplotlib.pyplot as plt
import matplotlib.lines as lns #importul functiei lines
import numpy as np
x = np.arange(0, 100.0)
328 Matplotlib

y = 50*np.sin(2*np.pi*x/50.0)
plt.plot(x,y)
ax = plt.gca()
#crearea liniei
l = lns.Line2D((0,25,40, 60,80),(-40, 30,40,-10,20), ls = '--')
ax.add_line(l) #adaugarea liniei
plt.show()

Output

Crearea săgeților cu funcția annotate()


Mai sus s-a arătat cazul general de plasare a unui text într-o poziție arbitară în cadrul sistemului
de axe de coordonate prin intermediul funcției text().
O utilizare frecventă de plasare a unui text este adnotarea unei anumite caracteristici a
graficului. În acest scop se utilizează metoda annotate(). Aceasta trebuie să conțină ca
argumente două tuple (x, y):
- Primul tuplu indică locația adnotată;
- Al doilea tuplu indică plasamentului textului afișat.
Exemplul 22.25 #utilizarea funcției annotate()
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
t = np.arange(0.0, 5.0, 0.01)
s = np.cos(2*np.pi*t)
line, = ax.plot(t, s, lw=2)
ax.annotate('maxim local', xy=(2, 1), xytext=(3, 1.5),
arrowprops=dict(facecolor='black', shrink=0.05),
)
ax.annotate('minim local', xy=(2.5, -1), xytext=(3.5, -1.5),
arrowprops=dict(facecolor='black', shrink=0.05),
)
ax.set_ylim(-2, 2)
plt.show()

Output
329 Matplotlib

22.10 Specificarea dimensiunii și salvarea imaginii unui grafic


Imaginile pot fi salvate utilizând metoda savefig(nume_fisier) a obiectului figură. Tipul de
fișier salvat poate fi, în funcție de extensie, jpeg, jpg, pdf, png, ps, tif, etc.
Înainte de salvare pot fi specificate dimensiunile prin utilizarea metodei obiectului figură
set_size_inches(w, h), unde w și h sunt lățimea, respectiv înălțimea figurii în inchi.

Exemplul 22.26 #specificarea dimensiunilor și salvarea imaginii graficului


import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100.5,0.5) # Generarea valorilor corespunzătoare axei x
y = 2.0*np.sqrt(x) # Calcularea valorilor corespunzătoare axei y
plt.plot(x,y) # Crearea obiectelor figure și axis
fig = plt.gcf() # Obținerea referinței la figura curentă
fig.set_size_inches(3, 2) # Stabilirea dimensiunii figurii curente
plt.show() # Afișarea graficului pe ecran
fig.savefig('fisier_imagine_salvat.pdf')

22.11 Afișarea graficului în format “stem”


Graficul de tip stem este redat prin linii verticale, perpendiculare pe o linie de bază (baseline),
în vârful cărora se află un marker precizat.
Poziția liniei de bază poate fi modificată utilizând argumentul bottom. Parametrii linefmt,
markerfmt și basefmt determină proprietățile formatului figurii graficului.

Exemplul 22.27 #grafic în format stem


import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0.1, 2 * np.pi, 41)
y = np.exp(np.sin(x))
plt.stem(x, y)
plt.show()
markerline, stemlines, baseline = plt.stem(x, y, linefmt='grey',
markerfmt='*', bottom=0)
#markerline, stemlines, baseline = plt.stem(x, y, linefmt='grey',
330 Matplotlib

markerfmt='D', bottom=1.1)
markerline.set_markerfacecolor('none')
plt.show()

Output

bottom=0, markerfmt='*' bottom=1.1, markerfmt='D'

22.12 Afișarea cu axe logaritmice, sau neliniare


Matplotlib.pyplot suportă nu numai axe liniare, ci și axe logaritmice, sau neliniare..
Schimbarea scării se face astfel:
plt.xscale('log')

În exemplul următor se afișează grafice liniar, logaritmic, simetric logaritmic și logistic.


Exemplul 22.28 #afisare cu axe liniare, logaritmice, simetric logaritmice și logistice
import matplotlib.pyplot as plt
import numpy as np
#se afiseaza datele aleatoare din intervalul (0,1) in diferite
#tipuri de reprezentari grafice grafice: liniar, logaritmic, simetric
#logaritmic si logistic (logis)
np.random.seed(19680801) # asigurare reproductibilitate set date
y = np.random.normal(loc=0.5, scale=0.4, size=1000) # interval date (0,1)
y = y[(y > 0) & (y < 1)]
y.sort() #datele aleatoare din intervalul (0,1) sunt sortate
x = np.arange(len(y))

# Afisare cu diverse reprezentări ale axelor


plt.figure()

# liniar
plt.subplot(221)
plt.plot(x, y)
plt.yscale('linear')
plt.title('liniar')
plt.grid(True)

# logaritmic
plt.subplot(222)
331 Matplotlib

plt.plot(x, y)
plt.yscale('log')
plt.title('logaritmic')
plt.grid(True)

# simetric logaritmic
plt.subplot(223)
plt.plot(x, y - y.mean())
plt.yscale('symlog', linthresh=0.01)
plt.title('Logaritmic simetric')
plt.grid(True)

# logit (logistic)
plt.subplot(224)
plt.plot(x, y)
plt.yscale('logit')
plt.title('logistic')
plt.grid(True)
# Ajustare cadru subplot pentru reglarea reprezentarii logistice
plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95,
hspace=0.35,wspace=0.35)

plt.show()

Output

22.13 Afișarea histogramelor


O histogramă este un grafic al distribuției de frecvență, sau mai simplu spus este un grafic
afișând numărul de observații în fiecare interval dat. Funcția din Matplotlib care creează
histograme este hist().
Exemplul 22.29 #histograma numerelor aleatoare (distribuția normală/Gaussiană)
#random.normal(loc=m, scale=n, size=k)
332 Matplotlib

#loc = centrul distribuției


#scale = deviația standard (împrăștierea)
#size = număr de eșantioane afișate
import matplotlib.pyplot as plt
import numpy as np
x = np.random.normal(100, 5, 300)
plt.hist(x)
plt.show()

Output

22.14 Reprezentări 3D
Reprezentarea liniilor în 3D

O reprezentare 3D este produsă prin precizarea parametrului projection='3d' care


determină crearea unui obiect 3D asociată obiectului figure.

Exemplul 22.30 #Reprezentarea 3D a unei înfășurători cilindrice

# Ecuațiile înfășurătoarei y=sin(x) și z = cos(x).


import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
x = np.linspace(-10*(np.pi),10*(np.pi),10000)
y = np.sin(x)
z = np.cos(x)
ax.plot(x, y, z, label='$y=sin(x)$ si $z = cos(x)$')
ax.legend()
ax.set_title('Reprezentare 3D')
ax.set_xlabel('$x$')
ax.set_ylabel('$y = sin(x)$')
ax.set_zlabel('$z = cos(x)$')
plt.show()

Output
333 Matplotlib

Observație. Pentru reprezentarea folosind markeri se poate utiliza metoda scatter() a


obiectului figure. Markerul dorit se poate stabili apelând metoda precizând markerul, de
exemplu scatter(x, y, z, marker='*').
Reprezentare funcțiilor ca rețele 3D (grid-uri 3D.)
Funcția meshgrid se utilizează pentru generarea unei rețele de puncte utilizând valorile a două
variabile (pot fi coordonatele x și y).
Funcția 3D de reprezentat modulează această rețea generând coordonata z în funcție de valorile
celorlalte două coordonate, mărimea pașilor pe x și y fiind precizați prin parametrii rstride și
cstride. Transmiterea coordonatelor x, y și z se face prin metoda plot_wireframe a
obiectului figure.
Exemplul 22.31 #reprezentarea ca rețea 3D a funcției 𝑧 = √𝑥 2 + 𝑦 2
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
a = np.arange(-5, 5, 0.35)
b = np.arange(-5, 5, 0.35)
x, y = np.meshgrid(a, b)
z = np.sqrt(x**2 + y**2)
ax.plot_wireframe(x, y, z, rstride=2, cstride=2)
ax.set_xlabel('$x$')
ax.set_ylabel('$y$')
ax.set_zlabel('$z = \sqrt{x^{2}+y^{2}}$')
ax.set_title('Reprezentare tip retea 3D')
plt.show()

Output
334 Matplotlib

Reprezentarea suprafețelor

Suprafețele se reprezintă asemănător rețelelor de puncte, fiind suficientă înlocuirea metodei


plot_wireframe()cu metoda plot_surface().

Exemplul 22.32 #reprezentarea funcției 𝑧 = √5 ∗ 𝑥 2 + 20 ∗ 𝑦 2 ca suprafață 3D


import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
a = np.arange(-5, 5, 0.35)
b = np.arange(-5, 5, 0.35)
x, y = np.meshgrid(a, b)
z = np.sqrt(5*x**2 + 20*y**2)
ax.plot_surface(x, y, z, rstride=2, cstride=2)
ax.set_xlabel('$x$')
ax.set_ylabel('$y$')
ax.set_zlabel('$z = \sqrt{5*x^{2}+20*y^{2}}$')
ax.set_title('Reprezentare tip suprafață 3D')
plt.show()
335 Matplotlib

22.15 Întrebări, exerciții și probleme


22.15.1 Să se creeze un grid 3x2 grafice.
R22.15.1
import numpy as np Output
import matplotlib.pyplot as plt
x = np.linspace(0.1, 2 * np.pi)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.tan(x)
y4 = np.sin(x*x)
y5 = np.sin(x*x+8*x)
y6 = np.sin(x)+np.cos(x)
plt.subplot(321)
plt.plot(x, y1, 'ro-')
plt.subplot(322)
plt.plot(x, y2, 'ro-')
plt.subplot(323)
plt.plot(x, y3, 'ro-')
plt.subplot(324)
plt.plot(x, y4, 'ro-')
plt.subplot(325)
plt.plot(x, y5, 'ro-')
plt.subplot(326)
plt.plot(x, y6, 'ro-')
plt.show()

22.15.2 Să se creeze 2 figuri diferite, fiecare conținând câte un grid de 2x2 grafice.
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0.1, 3 * np.pi)
y1 = np.sin(x); y2 = np.cos(x)
y3 = np.tan(x); y4 = np.sin(x*x)
y5 = np.sin(x*x+8*x);
y6 = np.sin(x)+np.cos(x)
y7 = np.exp(np.sin(x))
y8 = np.cos(np.exp(x)) Figura 1
plt.figure(1)
plt.subplot(221); plt.plot(x, y1, 'ro-')
plt.subplot(222); plt.plot(x, y2, 'ro-')
plt.subplot(223); plt.plot(x, y3, 'rd-')
plt.subplot(224); plt.plot(x, y4, 'rd-')
plt.figure(2)
plt.subplot(221); plt.plot(x, y5, 'b*-.')
plt.subplot(222); plt.plot(x, y6, 'b*-.')
plt.subplot(223); plt.plot(x, y7, 'bx-.')
plt.subplot(224); plt.plot(x, y8, 'bx-.')
plt.show() Figura 2

22.15.3 Utilizând matplotlib, să se deseneze un poligon și să se umple cu o culoare.


R22.15.3 Se va utiliza metoda modificării, la execuție, a parametrilor de configurare rcParam
din fișierul de configurare matplotlibrc.
import matplotlib.pyplot as plt
#PatchCollection usurează aplicarea colormap unei colectii de obiecte
from matplotlib.collections import PatchCollection
#Se importa clasa Polygon din matplotlib.patches
from matplotlib.patches import Polygon
import numpy as np
plt.rcParams["figure.figsize"] = [16, 7] #latimea si inaltimea figurii
336 Matplotlib

plt.rcParams["figure.autolayout"] = True
fig, ax = plt.subplots(1)
#Poligonul va avea 6 varfuri (6 perechi x,y)
polygon = Polygon(np.random.rand(6, 2), closed=True, alpha=1)
collection = PatchCollection([polygon])
ax.add_collection(collection)
plt.show()

Output

22.15.4 Functia Rosenbrook (rosen) este utilizată frecvent pentru testarea algoritmilor de
optimizare bazați pe gradienți. Funcția Rosenbrook de două variabile are expresia cea mai
generală: Rosenbrook(x,y)= a*(1-x)**2 + b* ((y-x**2))**2
Frecvent se utilizează valorile a = 1 și b =100. Să se reprezinte grafic funcția Rosenbrook pentru
-5 < x <5 și -5 < y < 5.
R22.15.4
import numpy as np
import matplotlib.pyplot as plot
from matplotlib import cm #color map
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from mpl_toolkits.mplot3d import Axes3D
#functia rosen(x,y)= (1-x)**2 + 100* ((y-x**2))**2
fig = plot.figure()
ax = fig.gca(projection='3d')
s = 0.1 # se incearca 0.05, 0.1, 0.25, s=1
X = np.arange(-5, 5.+s, s)
Y = np.arange(-5, 5.+s, s)
X, Y = np.meshgrid(X, Y)
Z = (1.-X)**2 + 100.*(Y-X*X)**2
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
fig.colorbar(surf, shrink=0.5, aspect=5)
plot.show()

Output
337 Matplotlib

22.15.5 Ce este supertitlul? Cum se asigură distanța suficientă între subploturi? Exemplificați.
R22.15.5 Supertitlul este comun grupului de subploturi. Distanțierea suficientă se asigură cu
atributul layout='tight'în comanda figure().
import matplotlib.pyplot as plt
import numpy as np
#plot 1:
x = np.array([0, 1, 2, 3, 4, 5, 6])
y = np.array([3.6, 3.5, 3.6, 3.7, 3.9, 3.8, 3.9])
plt.figure(layout='tight') #atributul layout='tight' asigura
#distantierea suficienta între subploturi pentru claritate
plt.subplot(1, 2, 1)
plt.grid(axis = 'x')
plt.xlabel("Timpul")
plt.ylabel("Amplitudinea")
plt.plot(x,y, linestyle = 'dashed', marker='o', color='r')
plt.title("Curent")
#plot 2:
x = np.array([0, 1, 2, 3, 4, 5, 6])
y = np.array([39.3, 39.4, 38.6, 38.2, 38.4, 38.3, 38.4])
plt.subplot(1, 2, 2)
plt.grid(axis = 'y')
plt.xlabel("Timpul")
plt.ylabel("Amplitudinea")
plt.plot(x,y, linestyle = 'dotted', marker='d', color='b')
plt.title("Tensiune")
plt.suptitle("Caracteristici electrice")
plt.show()

Output
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 23

Pandas
23.1 Concepte și definiții
Dezvoltarea Pandas a început în 2008 de către Wes McKinney ca răspuns la cererea de creare
a unui nou instrument pentru analiza datelor.
Pandas este o bibliotecă Python dedicată lucrului cu un model de date frecvent întâlnit în
știință, tehnică, economie și alte domenii, denumit “serie de date” .
Corespunzător obiectului “serie de date” (1D), Pandas introduce și tratează și un alt obiect
denumit ”DataFrame” (2D), asemănător unui tablou de date bidimensional, în care prima
coloană este un index, iar celelalte coloane sunt serii de date. De asemenea, se introduce și un
al treilea obiect denumit Panel (3D). Denumirea de Pandas este legată de “Panel Data" .
Motivul pentru care structurile de date de tip Pandas au avut succes se datorează faptului că
este mai ușor de ținut minte și lucrat cu entități denumite “rând”, sau “coloană”, spre
deosebire de “axa 1”, sau “axa 2”, ca în Numpy.
Pandas se instalează automat de unele pachete software ca Anaconda, sau Spyder, dar poate fi
instalat și individual.
Observație. Toate valorile din structurile de date Pandas sunt mutabile, dar seriile ( Series)
sunt imutabile.
Observație. Sistemul de lucru Pandas este foarte puternic și versatil. De exemplu, se pot
realiza cu ușurință prelucrări specifice bazelor de date relaționale, similare cu cele executate
prin intermediul comenzilor SQL.
Datorită capacității de a manipula masive de date, aplicațiile Pandas se regăsesc în special în
domeniile:
- economie,
- sisteme de recomandare (de exemplu: Spotify și Netflix),
- predicția stocurilor la bursele de valori mobiliare,
- neuroștințe, machine learning,
- statistică,
- publicitate,
- analiza datelor,
- Big Data (Hadoop și Spark),
- Data Science,
- Natural Langage Processing (NLP).
Seria de date
Definiție. O serie de date este o secvență de date, cum ar fi o listă în Python, sau un tablou 1D
în NumPy (vector, sau array 1D). Ca și vectorul NumPy, o serie are un singur tip de date, dar
indexarea unei serii este diferită.
În NumPy, controlul, destul de limitat, asupra elementelor unui tablou se face prin indici
numerici pentru rânduri și coloane.
339 Pandas

Într-o serie, ca și în Numpy, fiecare element din serie trebuie să aibă un index unic. Dar, spre
deosebire de Numpy, acest index poate fi însă și altceva decât un număr; poate fi un nume,
sau “o cheie”, în general.
De exemplu, indexul unei serii poate consta din șiruri de caractere reprezentând nume de
localități, elementele corespunzătoare putând fi valori statistice (populația, valoarea
produsului economic, etc.) și numele județului. Un alt caz foarte cunoscut este seria de valori
a unei acțiuni la bursă, pentru care indexul este ziua de tranzacționare (data din calendar).
Seriile de timp sunt frecvent întâlnite; ele sunt mulțimi de valori care apar ca urmare a unei
măsurări sau constatări la momente precizate de timp.
DataFrame
În practică apare frecvent necesitatea prelucrării unor colecții de date grupate și memorate în
fișiere tabelare (cu rânduri având același număr egal de date separate prin virgulă) al căror tip
este denumit CSV (Coma Separated Values).
Definiție. Un DataFrame este o structură de date alcătuită din mai multe serii de aceeași
lungime, având un index comun, legate împreună într-un obiect tabelar, figura 23.1.
Obiectul DataFrame seamănă cu un ndarray 2D Numpy, dar nu este același lucru. Nu toate
coloanele trebuie să aibă același tip de date. Reluând exemplul tabelului în care valorile
indexului sunt localități, coloanele pot fi foarte diferite ca tip de dată: populația (întreg),
producția industrială în ml sau mld euro (real), numele primarului (șir de caractere), sau un
cod boolean indicând statutul de capitală, sau nu, al județului. Această reprezentare din mai
multe tipuri diferite de date este dificil de realizat în Numpy.
De asemenea, fiecare coloană în DataFrame are un nume unic, de regulă un șir de identificare
a informațiilor pe care le conține.
Cu un astfel de obiect DataFrame se pot simplifica stocarea, accesarea, manipularea și se pot
prelucra mai eficient datele unei aplicații.

Fig. 23.1 Structura de date “DataFrame” în Pandas

23.2 Crearea unei serii de date


O serie se poate crea din obiecte Python de tip asemănător unei secvențe sau tablou: liste,
tupluri, dicționare, obiecte ndarray Numpy, etc.
Orice serie trebuie să aibă un index. Îndexul se poate crea separat, asemănător seriei, dar cu
restricția că toate valorile sale trebuie să fie unice.
340 Pandas

Dacă nu se asociază un index seriei, se va utiliza implicit o secvență numerică întreagă


începând cu 0.
Pentru a lucra în Python este necesar să se importe obiectele Series și DataFrame în spațiul
numelor:
>>>import numpy as np
>>>import pandas as pd
>>>from pandas import Series, DataFrame
Sintaxa:
Series(data, index, dtype, copy),

unde:
- data: ndarray, listă, constante
- index: trebuie să fie unic și hașabil, de aceeași lungime cu datele. Implicit (adică nu e
dat) este np.arange(n)
- dtype: precizează tipul datelor. Dacă nu este dat, va fi dedus.
- copy: copie a datelor. Implicit: False.
În exemplul care urmează se vor crea mai multe serii constând din elemente diferite.
Exemplul 23.1 #Crearea unei serii dintr-o listă, indexare implicită
>>>seria1 = Series([1,2,3,4,5])
>>>print(seria1)
0 1
1 2
2 3
3 4
4 5
dtype: int64
>>>seria2 = Series(['a', 'b', 'c', 'd'])
>>>print(seria2)
0 a
1 b
2 c
3 d
dtype: object

Seriile de mai sus au fost create cu indexul implicit, o valoare întreagă pornind de la 0.
Utilizând argumentul index, se pot eticheta valorile indexului cu nume:
Exercițiul 23.2 #Atribuirea unui index seriei
>>>h = [4, 6, 5] #aici seria este un scalar
>>>seria3 = Series(h, index = ['x', 'y', 'z'])
>>>print(seria3)
x 4
y 6
z 5
dtype: int64
Când o serie se creează dintr-un dicționar, cheia din dicționar devine indexul seriei.
Exercițiul 23.3 #crearea unei serii dintr-un dicționar
import numpy as np
import pandas as pd
from pandas import Series, DataFrame
temperaturi = {"ora8": 5, "ora11": 10, "ora14": 15} #dictionar
seria4 = Series(temperaturi)
print(seria4)
341 Pandas

Output
ora8 5
ora11 10
ora14 15
dtype: int64

Observație. Dacă indexul atribuit este mai scurt decât lista (sau secvența de chei mai scurtă
decât numărul de perechi din dicționar), seria se va scurta corespunzător.
Crearea unui index
Un index poate fi creat independent. În exemplul următor se va crea un index conținând
numele unor localități.
Exemplul 23.4 #Crearea unui index în Pandas
>>>idx_Loc = pd.Index(["Bucuresti", "Timisoare", "Iasi", "Cluj",
"Constanta", "Brasov", "Pitesti", "Craiova"])
>>>print(idx_Loc)
Index(['Bucuresti', 'Timisoare', 'Iasi', 'Cluj', 'Constanta', 'Brasov',
'Pitesti', 'Craiova'], dtype='object')

Indexul obținut ca mai sus poate fi utilizat la crearea unor serii. În exemplul care urmează,
indexul constă dintr-o listă de localități, iar cele trei serii create conțin numărul populației
(conform ro.wikipedia, date pentru anul 2012), județul și altitudinea acelor localități. Pentru
crearea seriilor se vor utiliza ca intrări o listă, și respectiv dicționare.
Exemplul 23.5 #Crearea seriilor având disponibil indexul creat anterior
>>>nr_pop = Series([1898425, 303708, np.nan, 309136, 283872,
253700, 155329, 269506], index =idx_Loc, name =
"Nr_locuitori")
>>>print(nr_pop)
Bucuresti 1898425.0
Timisoare 303708.0
Iasi NaN
Cluj 309136.0
Constanta 283872.0
Brasov 253700.0
Pitesti 155329.0
Craiova 269506.0
Name: Nr_locuitori, dtype: float64

>>>judete = Series({"Pitesti":"AG","Craiova":"DJ","Timisoara":"TM",
"Cluj":"CJ","Brasov":"BV","Constanta":"CT"},name =
"Judet")
>>>print(judete)
Pitesti AG
Craiova DJ
Timisoara TM
Cluj CJ
Brasov BV
Constanta CT
Name: Judet, dtype: object

>>>altit = Series({"Pitesti":287,"Brasov":625,"Timisoara":90, "Cluj":405,


"Craiova":100, "Bucuresti":85, "Constanta":25},name = "Altitudine")
>>>print(altit)
Pitesti 287
Brasov 625
Timisoara 90
342 Pandas

Cluj 405
Craiova 100
Bucuresti 85
Constanta 25
Name: Altitudine, dtype: int64

În seria Nr_locuitori, în cazul Iașului s-a arătat intenționat că se poate utiliza np.nan pentru
o dată lipsă. Lungimea seriilor este diferită, cu intenție.
Accesarea datelor din serie se poate face prin poziție (similar nd.array), sau prin
eticheta/indexul asociat, fie individual, fie ca listă de elemente.
Exemplul 23.6 #accesarea datelor unei serii
>>>print(seria1[1])
2
>>>print(seria1[:3])
0 1
1 2
2 3
dtype: int64
>>>print(nr_pop['Cluj'])
309136.0
>>>print(nr_pop[['Cluj', 'Brasov']])
Cluj 309136.0
Brasov 253700.0
Name: Nr_locuitori, dtype: float64

23.3 Crearea unui DataFrame


Așa cum s-a definit anterior, un cadru de date/DataFrame este o combinație de serii puse
împreună pentru a forma un obiect de tip tablou (rândurile sau coloanele sale fiind serii).
Un DataFrame se poate crea în diferite moduri.
De asemenea, se poate atribui unui DataFrame un index și se poate specifica numele
coloanelor ca argument listă.
Sintaxa:
DataFrame(data, index, columns, dtype, copy),

unde:
- data: ndarray, serii, liste, dicționare, constante, alt DataFrame
- index: corespunde rândurilor. Dacă nu e precizat, implicit este np.arange(n).
- columns: coloanele (nume), dacă nu sunt precizate va fi implicit nd.arange(n).
- copy: copiere, implicit = False.

Exemplul 23.7 #Crearea DataFrame dintr-un array Numpy


>>>sr = np.arange(0,9).reshape(3, 3) #crearea listei
>>>print(sr)
[[0 1 2]
[3 4 5]
[6 7 8]]
>>>df = DataFrame(sr) #crearea DataFrame, index implicit
>>>print(df)
0 1 2
0 0 1 2
1 3 4 5
343 Pandas

2 6 7 8
>>> #Adaugarea etichetelor la randuri si coloane
>>>df = DataFrame(sr, index=['A','B','C'], columns = ['X', 'Y', 'Z'])
>>>print(df)
X Y Z
A 0 1 2
B 3 4 5
C 6 7 8

Exemplul 23.8 #Crearea DataFrame dintr-o listă de tuple


>>>lt = [(1, 'A'), (2, 'B'), (3, 'C')]
>>>print(lt)
[(1, 'A'), (2, 'B'), (3, 'C')]

>>>df = DataFrame(lt, columns = ['Coloana1', 'Coloana2'])


>>>print(df)
Coloana1 Coloana2
0 1 A
1 2 B
2 3 C

Exemplul 23.9 #Crearea DataFrame dintr-un dicționar


>>>df = DataFrame({'Coloana1':[1,2,3],'Coloana2':['A','B','C']})
>>>print(df)
Coloana1 Coloana2
0 1 A
1 2 B
2 3 C

Observație. La crearea DataFrame din serii de lungime diferită se va produce eroare, cu


excepția cazului când seriile au avut alocat un index la creare.
Reluînd seriile definite pentru indexul localităților, se poate obține un DataFrame, deși seriile
au fost create de lungime inegală, ca în exemplul care urmează.
Exemplul 23.10 #Crearea DataFrame din serii inegale, dar având alocat un index
>>>df = DataFrame([nr_pop, judete, altit])
>>>print(df)
Bucuresti Timisoare Iasi ... Pitesti Craiova Timisoara
Nr_locuitori 1898425.0 303708.0 NaN ... 155329.0 269506.0 NaN
Judet NaN NaN NaN ... AG DJ TM
Altitudine 85.0 NaN NaN ... 287.0 100.0 90.0

[3 rows x 9 columns]

De remarcat că valorile NaN se datorează lipsei datelor, deoarece seriile care au fost
combinate au fost de lungime inegală.
Acest DataFrame creat mai sus este dificil de utilizat deoarece are coloanele formate din ceea
ce s-ar dori să fie cheile (indexul), iar rândurile sunt variabilele. Pentru ușurința modului de
procesare dispunerea ar trebui să fie inversată. Acest lucru este posibil fie imprimând tabelul
cu utilizarea opțiunii de transpunere, fie redefinind DataFrame-ul folosind un dicționar, astfel:
Exemplul 23.11 #Crearea DataFrame din serii inegale, cu index, din dictionar
>>>df = DataFrame({"Nr.locuitori":nr_pop,"Judetul":judete, "Altitudinea":
altit})
>>>print(df)
Nr.locuitori Judetul Altitudinea
344 Pandas

Brasov 253700.0 BV 625.0


Bucuresti 1898425.0 NaN 85.0
Cluj 309136.0 CJ 405.0
Constanta 283872.0 CT 25.0
Craiova 269506.0 DJ 100.0
Iasi NaN NaN NaN
Pitesti 155329.0 AG 287.0
Timisoara NaN TM 90.0
Timisoare 303708.0 NaN NaN

23.4 Atributele și metodele obiectelor Series și DataFrame


Tabelul 1. Atributele și metodele obiectului Series
Nr. Atributul sau Funcționalitatea
metoda
1 axes Întoarce o listă a etichetelor axei rând
2 dtype Întoarce tipul
3 empty Întoarce True dacă seria e vidă
4 ndim Întoarce numărul dimensiunilor datelor, implicit 1
5 size Întoarce numărul de elemente al datelor
6 values Întoarce seria sub formă de ndarray
7 head() Întoarce primele n rânduri
8 tail() Întoarce ultimele n rânduri
Exemplul 23.12 #atributele și metodele obiectului Series
import pandas as pd
import numpy as np
#crearea unei serii cu 4 elemente
s = pd.Series(np.random.rand(4))
print("Axa rand este:", s.axes)
print("Este seria (obiectul) vida?:", s.empty)
print("Dimensiunea obiectului serie, ndim=", s.ndim)
print("Marimea obiectului serie, size=", s.size)
print("valorile seriei de date:\n", s.values)
print("Primele doua randuri ale seriei de date:\n", s.head(2))
print("Ultimele doua randuri ale seriei de date:\n", s.tail(2))
Output
Axa rand este: [RangeIndex(start=0, stop=4, step=1)]
Este seria (obiectul) vida?: False
Dimensiunea obiectului serie, ndim= 1
Marimea obiectului serie, size= 4
valorile seriei de date:
[0.51283637 0.14103745 0.28969398 0.16785029]
Primele doua randuri ale seriei de date:
0 0.512836
1 0.141037
dtype: float64
Ultimele doua randuri ale seriei de date:
2 0.289694
3 0.167850
dtype: float64

Tabelul 2. Atributele și metodele obiectului DataFrame


Nr. Atributul sau Funcționalitatea
metoda
345 Pandas

1 T Transpune rânduri și coloane


2 axes Întoarce o listă a etichetelor axelor rând și coloană
3 dtype Întoarce tipul de date al fiecărei coloane
4 empty Întoarce True dacă obiectul este vid
5 ndim Întoarce numărul dimensiunilor DataFrame (2)
6 size Întoarce numărul de elemente din DataFrame
7 values Întoarce seria sub formă de tablou ndarray
8 head() Întoarce primele n rânduri
9 tail() Întoarce ultimele n rânduri
Exemplul 23.13 #atributele și metodele obiectului DataFrame
import pandas as pd
import numpy as np
#Datele initiale sunt sub forma unui dictionar de obiecte Series
d =
{'Nume:':d.Series(['Ion','George','Emil','Tudor','Petre','Bogdan','Marian']
), "Varsta:": pd.Series([10, 12, 15, 11, 12, 14, 13]), "Punctaj joc:" :
pd.Series([52, 64, 71, 55, 57, 62, 63])}
df = pd.DataFrame(d)
print("Seriile sunt:\n", df)
print("Transpusa, T:", df.T)
print("Etichetele axelor, axes:", df.axes)
print("Tipurile de date, pe fiecare coloana, sunt:\n", df.dtypes)
print("Obiectul DataFrame este vid? :", df.empty)
print("Dimensiunea obiectului DataFrame, ndim=", df.ndim)
print("Forma obiectului este:", df.shape)
print("Obiectul este:\n", df)
print("Marimea obiectului DataFrame, size=", df.size)
print("valorile DataFrame :\n", df.values)
print("Primele doua randuri:\n", df.head(2))
print("Ultimele doua randuri:\n", df.tail(2))

Output
Seriile sunt:
Nume: Varsta: Punctaj joc:
0 Ion 10 52
1 George 12 64
2 Emil 15 71
3 Tudor 11 55
4 Petre 12 57
5 Bogdan 14 62
6 Marian 13 63
Transpusa, T: 0 1 2 3 4 5 6
Nume: Ion George Emil Tudor Petre Bogdan Marian
Varsta: 10 12 15 11 12 14 13
Punctaj joc: 52 64 71 55 57 62 63
Etichetele axelor, axes: [RangeIndex(start=0, stop=7, step=1),
Index(['Nume:', 'Varsta:', 'Punctaj joc:'], dtype='object')]
Tipurile de date, pe fiecare coloana, sunt:
Nume: object
Varsta: int64
Punctaj joc: int64
dtype: object
Obiectul DataFrame este vid? : False
Dimensiunea obiectului DataFrame, ndim= 2
Forma obiectului este: (7, 3)
Obiectul este:
Nume: Varsta: Punctaj joc:
346 Pandas

0 Ion 10 52
1 George 12 64
2 Emil 15 71
3 Tudor 11 55
4 Petre 12 57
5 Bogdan 14 62
6 Marian 13 63
Marimea obiectului DataFrame, size= 21
valorile DataFrame :
[['Ion' 10 52]
['George' 12 64]
['Emil' 15 71]
['Tudor' 11 55]
['Petre' 12 57]
['Bogdan' 14 62]
['Marian' 13 63]]
Primele doua randuri:
Nume: Varsta: Punctaj joc:
0 Ion 10 52
1 George 12 64
Ultimele doua randuri:
Nume: Varsta: Punctaj joc:
5 Bogdan 14 62
6 Marian 13 63

23.5 Indexarea și selectarea datelor în Pandas


Când se lucrează cu masive de date, sunt necesare frecvent operații de obținere a unor
subseturi de date. În acest scop se utilizează indexarea și slice-ingul cu operatorii potriviți
(“[]”, “:” și “.”). Totuși, nu întotdeauna metodele clasice pot fi utilizate, sau rezultatele nu
sunt cele mai bune.
Pentru a rezolva problemele apărute, în Pandas se oferă suplimentar trei tipuri de indecși care
pot fi obținuți cu ajutorul funcțiilor multi-axă loc() (de tip listă de etichete), iloc() (de tip
întreg).
Metoda loc() permite accesul pe baza etichetelor axei/axelor, care pot fi astfel:
- etichete de tip scalar, unice
- listă de etichete
- obiect slice
- tablou boolean
Domeniile acceptate de metoda loc() sunt determinați de operanzi separați prin virgulă (,), în
care primul operand indică rândul, iar al doilea indică coloana.
Metoda iloc() acceptă argumente întregi, liste de întregi, sau un domeniu de valori.
Observație. Versiunile mai noi de Pandas permit utilizarea și a argumentelor hibride (etichete
și întregi) cu funcția loc().
Exemplul 23.14 #indexare și selectare cu funcția loc()
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.rand(5,3), index = ['r1', 'r2', 'r3', 'r4',
'r5'],
columns = ['c1', 'c2', 'c3'])
print('Selectare toate randurile pentru o anumita coloana')
print(df.loc[:, 'c2'])
347 Pandas

print('Selectare toate randurile pentru mai multe coloane, date ca lista')


print(df.loc[:, ['c2', 'c3']])
print('Selectare cateva randuri, pentru mai multe coloane, date ca lista')
print(df.loc[['r2', 'r4', 'r5'], ['c1', 'c3']])
print('Selectare un domeniu de randuri, toate coloanele')
print(df.loc['r2':'r4'])
print('afisare tablou de valori logice, rezultate dupa testarea valorilor')
print(df.loc['r2']>0)

Output
Selectare toate randurile pentru o anumita coloana
r1 0.873672
r2 0.505380
r3 0.825728
r4 0.529087
r5 0.720059
Name: c2, dtype: float64
Selectare toate randurile pentru mai multe coloane, date ca lista
c2 c3
r1 0.873672 0.251228
r2 0.505380 0.036395
r3 0.825728 0.575903
r4 0.529087 0.809571
r5 0.720059 0.381581
Selectare cateva randuri, pentru mai multe coloane, date ca lista
c1 c3
r2 0.591144 0.036395
r4 0.650186 0.809571
r5 0.084623 0.381581
Selectare un domeniu de randuri, toate coloanele
c1 c2 c3
r2 0.591144 0.505380 0.036395
r3 0.725653 0.825728 0.575903
r4 0.650186 0.529087 0.809571
afisare tablou de valori logice, rezultate dupa testarea valorilor
c1 True
c2 True
c3 True
Name: r2, dtype: bool

Exemplul 23.15 #indexare și selectare cu funcția iloc()


import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.rand(5,5), index = ['r1', 'r2', 'r3', 'r4',
'r5'],
columns = ['c1', 'c2', 'c3', 'c4', 'c5'])
print('Selectare toate coloanele pentru un interval de randuri')
print(df.iloc[:3])
print('Selectare prin slice randuri si coloane')
print(df.iloc[2:4, 3:5])
print('Selectare prin slice randuri si coloane, pe baza de lista de
valori')
print(df.iloc[[1,3,4],[2,4]])
print(df.iloc[2:4, :])
print(df.iloc[:, 1:3])

Output
Selectare toate coloanele pentru un interval de randuri
c1 c2 c3 c4 c5
348 Pandas

r1 0.006860 0.363776 0.348627 0.506440 0.707572


r2 0.495859 0.166441 0.915761 0.353840 0.375922
r3 0.341788 0.927409 0.887660 0.681147 0.168176
Selectare prin slice randuri si coloane
c4 c5
r3 0.681147 0.168176
r4 0.606762 0.703131
Selectare prin slice randuri si coloane, pe baza de lista de valori
c3 c5
r2 0.915761 0.375922
r4 0.171756 0.703131
r5 0.394395 0.767937
c1 c2 c3 c4 c5
r3 0.341788 0.927409 0.887660 0.681147 0.168176
r4 0.736256 0.053526 0.171756 0.606762 0.703131
c2 c3
r1 0.363776 0.348627
r2 0.166441 0.915761
r3 0.927409 0.887660
r4 0.053526 0.171756
r5 0.987921 0.394395

23.6 Operații cu Serii și DataFrame-uri în Pandas


Pandas dispune de un număr mare de funcții și metode care permit orice prelucrare de date.
Tabelul 3. Funcții statistice în Pandas
Funcția Descriere
1 count() calculul numărului de elemente (not null)
2 sum() Suma valorilor
3 mean() Media valorilor
4 median() Mediana valorilor
5 mode() Modul valorilor
6 std() Deviația standard a valorilor
7 min() Valoarea minimă
8 max() Valoarea maximă
9 abs() Valoarea absolută
10 prod() Produsul valorilor
11 cumsum() Suma cumulativă
12 cumprod() Produsul cumulat
Observație. Deoarece DataFrame este o structură de date eterogenă, este posibil ca nu toate
operațiile să fie aplicabile pentru o configurație particulară de date. În caz de incompatibilitate
se pot genera excepții.
Observație. Funcțiile pot fi aplicate atât individual, cât și în bloc și prin intermediul funcției
describe(). Pentru a preciza data țintă, funcția describe() se poate utiliza cu opțiunea
include cu parametrii: object (pentru coloanele tip caracter) și number (coloanele
numerice).
Alte funcții
- Funcția de afișare a modificărilor sub formă procentuală este pct_change(). Funcția
compară fiecare element cu predecesorul său și calculează diferența afișând schimbarea în
formă procentuală.
349 Pandas

- Funcția de calcul a covarianței cov() calculează covarianța între obiecte de tip serie de date.
Dacă se aplică unui DataFrame, se calculează între două coloane precizate ale acestuia, sau
pentru toate coloanele, luate două câte două.
- Funcția corr() calculează corelația între două serii. În cazul unui DataFrame, se calculează
corelația între două coloane.
- Funcția reindex() face operația de reindexare, schimbând etichetelor rândurilor și
coloanelor unui DataFrame conform unor cerințe și criterii dorite.
- Operația de iterare a unui obiect depinde de tipul acestuia. Se face de regulă în cadrul unei
bucle for. În cazul seriilor, la fiecare iterație se obține un element, iar în cazul unui
DataFrame se obține un nume de coloană. Iterarea poate fi executată prin intermediul unor
metode: iteritems(), iterrows(), itertuples().
- Sortarea poate fi aplicată etichetelor axelor, sau valorilor elementelor utilizând funcțiile
sortindex() și sortvalues().

În Pandas se poate lucra pe date de tip text. Modificările unui text sunt asemănătoare cu cele
care se fac asupra șirurilor de date, pentru care există foarte multe funcții. Aproape toate
funcțiile dezvoltate pentru lucrul cu șiruri sunt aplicabile și în Pandas pentru lucrul cu datele
de tip text: lower(), upper(), len(), split(), replace(), find(), isupper(),
islower(), strip(),cat(sep=’’) , count(), repeat(), etc.

Observație. Așa cum s-a mai precizat, în Pandas se pot face prelucrări asemănătoare cu cele
permise în bazele de date SQL. De exemplu, comanda join() din Pandas, aplicată unui
DataFrame, poate avea ca parametri: left, right, outer, inner, cu efect asemănător cu
comanda similară din SQL.
De asemenea, se pot găsi multe alte similitudini și secvențe de comenzi Pandas echivalente cu
comenzi SQL (insert, delete, update, alter, etc.), printre care și cu comanda SELECT cu
diferite clauze (where, order by, group by, having, etc.).
Observație. În Pandas există trei metode de aplicare specifică a unei funcții de bibliotecă sau
utilizator. În cele trei cazuri funcția va opera la unul din cele trei niveluri:
- La nivel de tabel (“table wise”) se operează prin pipe(). De exemplu, dacă se dorește
adăugarea valorii 5 la toate elementele din dataframe-ul df, se apelează df.pipe(aduna, 5),
aduna(element1, element2) fiind o funcție utilizator cu doi parametri de intrare și ieșire
suma lor.
- La nivel de coloană sau rând (”row/column wise”) se operează prin apply().
- La nivel de element (”element wise”) se operează prin applymap().
Ferestre mobile
O fereastră mobilă (alunecătoare) este o secvență de valori ale datelor, cuprinzând de regulă
mai multe rânduri alăturate.
Ferestrele sunt de mai multe feluri, cea mai cunoscută fiind “rolling window” . Ferestrele
sunt utilizate de unele funcții ca sum, mean, median, variance, covariance,
correlation.

De exemplu, ”rolling mean” aplicat datelor [1, 2, 34, 4, 5] unde dimensiunea ferestrei este
egală cu 2 produce [1, 1.5, 2.5, 3.5, 4.5]. Dacă fereastra are o lungime mai mare de 1, este
posibil ca unele valori de la începutul secvenței de date rezultate să fie nedefinite (NaN).
350 Pandas

Operații I/O. Citirea și salvarea datelor în formatul csv


Un format standard foarte utilizat pentru stocarea și transferul datelor ca text este csv (comma
separated values).
Un text .csv poate să apară ca următorul fișier text ce oferă date despre salariați (cu numele
personal.csv):
Nr_crt, Nume, Vârsta, Profesia, Salariu
1, Ionescu Petre, 26, mecanic, 3000
2, Popescu Alex, 34, sofer, 2800
3, Georgescu Ion, 32, electrician, 3200
4, Radescu George, 28, operator, 3500
5, Toma Andrei, 29, paznic, 2500

Fișierele .csv sunt utilizate de regulă în Pandas ca mediu intermediar pentru intrările și ieșirile
de date cu alte aplicații, sau pentru stocare.
Pentru citirea datelor din fișierul .csv și conversia în tablou Numpy se utilizează funcția
read_csv, iar pentru salvarea lor se utilizează funcția to_csv().

Exemplul 23.16 #citirea datelor dintr-un fișier csv


import pandas as pd
df = pd.read_csv("personal.csv")
print(df)

Output
Nr_crt Nume Vârsta Profesia Salariu
0 1 Ionescu Petre 26 mecanic 3000
1 2 Popescu Alex 34 sofer 2800
2 3 Georgescu Ion 32 electrician 3200
3 4 Radescu George 28 operator 3500
4 5 Toma Andrei 29 paznic 2500

La datele citite se adaugă indexul implicit. Dacă se dorește ca la citire indexul să fie o coloană
din tabloul csv, se utilizează parametrul index_col:
Exemplul 23.17 #citire fișier .csv cu index precizat (ales dintre coloane)
import pandas as pd
df = pd.read_csv("personal.csv", index_col = ["Nr_crt"])
print(df)

Output
Nume Vârsta Profesia Salariu
Nr_crt
1 Ionescu Petre 26 mecanic 3000
2 Popescu Alex 34 sofer 2800
3 Georgescu Ion 32 electrician 3200
4 Radescu George 28 operator 3500
5 Toma Andrei 29 paznic 2500

Se observă că ieșirea afișată conține ca prim rând (nedorit) denumirile coloanelor (headerul)
din fișierul .csv. Pentru eliminarea acestui header, se poate face citirea utilizând argumentul
skiprows, prin care se sar n rânduri specificate.

Exemplul 23.18 #salt peste headerul fișierului csv


import pandas as pd
df = pd.read_csv("personal.csv", skiprows = 1)
351 Pandas

print(df)

Output
1 Ionescu Petre 26 mecanic 3000
0 2 Popescu Alex 34 sofer 2800
1 3 Georgescu Ion 32 electrician 3200
2 4 Radescu George 28 operator 3500
3 5 Toma Andrei 29 paznic 2500

Se poate atribui un nou antet/header la citirea fisierului csv.


Exemplul 23.19 # atribuire un nou antet/header pentru dataframe
import pandas as pd
import numpy as np
df = pd.read_csv("personal.csv", names = ['c1', 'c2', 'c3', 'c4', 'c5'])
print(df)

Output
c1 c2 c3 c4 c5
0 Nr_crt Nume Vârsta Profesia Salariu
1 1 Ionescu Petre 26 mecanic 3000
2 2 Popescu Alex 34 sofer 2800
3 3 Georgescu Ion 32 electrician 3200
4 4 Radescu George 28 operator 3500
5 5 Toma Andrei 29 paznic 2500

23.7 Generarea intervalelor temporale “dată-timp ”


În operațiile cu date în Pandas se utilizează frecvent serii de timp având indexul de tip
temporal, adică “dată și timp”.
Pentru obținerea unui index cu frecvență fixată exprimat sub forma unei combinații de dată și
timp se poate utiliza funcția pandas.data_range(). Funcția întoarce un obiect denumit
DatetimeIndex.

Sintaxa:
pandas.data_range(start=None, end=None, peiods=None, freq=None, tz=None,
name=None, **kwargs)

Semnificația argumentelor funcției este următoarea:


start = limita din stânga intervalului pentru valorile generate
end = limita din dreapta intervalului pentru valorile generate
periods = numărul de perioade (intervale) generate
freq = șir reprezentând frecvența în multipli de data/timp (de ex. 5H).
tz = numele timezone pentru DatetimeIndex localizat.
normalize = normalizare start/end în raport cu miezul-nopții înainte de generarea
intervalului de date
name = numele rezultat pentru DatetimeIndex.
closed = interval închis left/right, sau ambele
Se întoarce DatetimeIndex.
Exemplul 23.20 #obținerea unui index dată_timp
import pandas as pd
#ll-zz-aa
per1 = pd.date_range(start ='03-01-2022', end ='03-03-2022', freq ='4H')
352 Pandas

for index_td in per1:


print(index_td)

Output
2022-03-01 00:00:00
2022-03-01 04:00:00
2022-03-01 08:00:00
. . . . . . . . . . . . .
2022-03-02 12:00:00
2022-03-02 16:00:00
2022-03-02 20:00:00
2022-03-03 00:00:00

23.8 Vizualizarea datelor în Pandas cu Matplotlib


Vizualizarea datelor Pandas utilizând Matplotlib este simplă. Funcționalitatea de vizualizare
în Pandas este doar “un ambalaj” pentru metodele cunoscute din Matplotlib. De exemplu,
bar, hist, box, area și scatter sunt metode ale funcției plot().
Sunt disponibile foarte multe posibilități și variante pentru crearea de reprezentări grafice,
dintre care vor fi exemplificate în continuare următoarele:
- Afișarea unei serii simple.
- Afișarea seriilor conținute într-un dataFrame.
- Afișarea cu denumirea axelor.
- Afișarea sub formă de bare verticale sau orizontale.
- Afișarea unui dataFrame sub formă de histogramă.
- Afișarea de tip box, specifică reprezentării datelor bursiere.
- Afișarea densităților (distribuțiilor).
- Afișarea ariilor delimitate de graficele seriilor de date.
- Afișarea graficelor de împrăștiere (scatter).
- Afișarea prin puncte hexagonale.
- Afișarea tip “pie chart”.
-
Exemplul 23.21 # Afișarea unei serii simple
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

ts = pd.Series(np.random.randn(100),
index = pd.date_range('1/1/2022',
periods = 100))
ts = ts.cumsum()
ts.plot()
plt.show()
353 Pandas

Exemplul 23.22 # Afișarea seriilor conținute într-un dataframe.


import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
ts =
pd.Series(np.random.randn(100),
index = pd.date_range(

'1/1/2022', periods = 100))


df =
pd.DataFrame(np.random.randn(100,
4),
index = ts.index, columns =
list('ABCD'))
df = df.cumsum()
plt.figure()
df.plot()
plt.show()

Exemplul 23.23 #Definirea explicită a axei și reprezentarea în raport cu axa precizată


import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
df =
pd.DataFrame(np.random.randn(100,
2),columns =['B', 'C']).cumsum()
df['A'] =
pd.Series(list(range(len(df)))) #se
adauga coloana A (1 - 100)
#print(df['A'])
df.plot(x ='A', y ='B') #se
ploteaza din df3 coloana B, axa x
este coloana A
plt.show()

Exemplul 23.24 #Reprezentarea cu bare verticale


import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
df =
pd.DataFrame(np.random.randn(100,
5),index = pd.date_range(
'1/1/2022', periods = 100),
columns =['B', 'C', 'D', 'E',
'F']).cumsum()
#plt.figure(1)
#plt.plot(df)
df['A'] =
pd.Series(list(range(len(df))))
plt.figure(2)
df.iloc[5].plot.bar()
plt.axhline(0, color ='k')
plt.show()
354 Pandas

Exemplul 23.25 #reprezentarea histogramei


import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

df = pd.DataFrame({'x':
np.random.randn(200) + 1,
'y':
np.random.randn(200),
'z':
np.random.randn(200) - 1},
columns
=['x', 'y', 'z'])
df.plot.hist(alpha = 0.5)
plt.show()

Exemplul 23.26 #reprezentarea pie chart


import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({'Punctaj:' :
[52, 16, 71, 155, 57, 88, 33],
'Varsta:' : [10, 12, 15, 11, 12,
14, 13]},index =
['Tudor','George','Emil','Ion','Pe
tre','Bogdan','Marian'])
df.plot.pie(y='Punctaj:',
figsize=(6, 6))
plt.show()

23.9 Întrebări, exerciții și probleme


23.9.1 Utilizând funcția describe(), să se calculeze valorile statistice pentru următorul set de
date reprezentate ca dataFrame:
data = [[5, 15, 17, 13], [12, 14, 7, 22], [10, 20, 33, 23], [8, 34, 19, 55]]
R23.9.1
import pandas as pd
data = [[5,15,17,13], [12,14,7,22], [10,20,33,23], [8,34,19,55]]
df = pd.DataFrame(data)
print(df.describe())
Output
count 4.000000 4.000000 4.000000 4.000000
mean 8.750000 20.750000 19.000000 28.250000
std 2.986079 9.215024 10.708252 18.391574
min 5.000000 14.000000 7.000000 13.000000
25% 7.250000 14.750000 14.500000 19.750000
50% 9.000000 17.500000 18.000000 22.500000
355 Pandas

75% 10.500000 23.500000 22.500000 31.000000


max 12.000000 34.000000 33.000000 55.000000

23.9.2 Să se adauge o nouă coloană la un DataFrame în Pandas. În câte moduri se poate face?
R23.9.2 Adăugarea unei noi coloane la un dataframe se poate face prin:
Metoda 1. Declararea unei noi liste drept coloană a df. Lungimea noii liste trebuie să fie egală
cu indexul existent, altfel se va genera eroare.
Metoda 2. Utilizarea metodei DataFrame.insert(). Se oferă libertatea de a adăuga o
coloană în orice poziție, nu numai la sfârșit.
Metoda 3. Utilizarea metodei DataFrame.assign(). Această metodă va crea un nou dicționar
având noua coloană adăugată.
Metoda 4. Utilizarea unui dicționar. Atenție! Se utilizează valorile noii coloane drept cheie,
iar valorile existente vor fi valori în dicționarul creat.
#Datele initiale sunt sub forma unui dictionar de obiecte Series
d = {'Nume':
pd.Series(['Ion','George','Emil','Tudor','Petre','Bogdan','Marian']),
"Varsta": pd.Series([10, 12, 15, 11, 12, 14, 13]),
"Puncte" : pd.Series([52, 64, 71, 55, 57, 62, 63])}
df = pd.DataFrame(d)
#METODA 1
# Declararea unei liste ce va fi convertite intr-o coloana
Localitatea = ['Pitesti', 'Bucuresti', 'Iasi', 'Cluj','Arad', 'Brasov',
'Sibiu']
# Se atribuie numele 'Adresa' ca nume de coloana
df['Adresa'] = Localitatea
print(df)
#METODA 2
# Utilizarea functiei DataFrame.insert() pentru adaugarea unei coloane
df.insert(2, "Sex", ["M","M","M","M","M","M","M"], True)
print('\n',df)
# METODA 3
# Using 'Judet' ca nume de coloana adaugand o lista noua
df2 = df.assign(Judet = ['AG', 'B', 'IS', 'CJ','AR', 'BV', 'SB'])
print('\n',df2)
#METODA 4
Inaltime = {1.45:'Ion',1.71:'George',1.59:'Emil',1.52:'Tudor',
1.58:'Petre',1.68:'Bogdan',1.72:'Marian'}
# Atribuie numele coloanei
df2['Inaltime'] = Inaltime
print('\n',df2)

Output
Nume Varsta Puncte Adresa
0 Ion 10 52 Pitesti
1 George 12 64 Bucuresti
2 Emil 15 71 Iasi
3 Tudor 11 55 Cluj
4 Petre 12 57 Arad
5 Bogdan 14 62 Brasov
6 Marian 13 63 Sibiu

Nume Varsta Sex Puncte Adresa


0 Ion 10 M 52 Pitesti
1 George 12 M 64 Bucuresti
2 Emil 15 M 71 Iasi
356 Pandas

3 Tudor 11 M 55 Cluj
4 Petre 12 M 57 Arad
5 Bogdan 14 M 62 Brasov
6 Marian 13 M 63 Sibiu

Nume Varsta Sex Puncte Adresa Judet


0 Ion 10 M 52 Pitesti AG
1 George 12 M 64 Bucuresti B
2 Emil 15 M 71 Iasi IS
3 Tudor 11 M 55 Cluj CJ
4 Petre 12 M 57 Arad AR
5 Bogdan 14 M 62 Brasov BV
6 Marian 13 M 63 Sibiu SB

Nume Varsta Sex Puncte Adresa Judet Inaltime


0 Ion 10 M 52 Pitesti AG 1.45
1 George 12 M 64 Bucuresti B 1.71
2 Emil 15 M 71 Iasi IS 1.59
3 Tudor 11 M 55 Cluj CJ 1.52
4 Petre 12 M 57 Arad AR 1.58
5 Bogdan 14 M 62 Brasov BV 1.68
6 Marian 13 M 63 Sibiu SB 1.72

23.9.3 Să se creeze un DataFrame Panda dintr-o listă de liste. Lista de liste poate fi o filă din
carnetul de note ale unui elev.
R23.9.3
import matplotlib.pyplot as plt
import pandas as pd
Note = [['Mate', '3.04.22', 9], ['Fizica', '7.04.22', 8], ['Istorie',
'5.04.22', 8], ['Sport', '11.04.22', 10]]
df = pd.DataFrame(Note, columns = ['Disc.', 'Data', 'Nota'])
print(df, '\n')
df = df.transpose()
print("Transpusa df obtinut\n", df)

Output
Disc. Data Nota
0 Mate 3.04.22 9
1 Fizica 7.04.22 8
2 Istorie 5.04.22 8
3 Sport 11.04.22 10

Transpusa df obtinut
0 1 2 3
Disc. Mate Fizica Istorie Sport
Data 3.04.22 7.04.22 5.04.22 11.04.22
Nota 9 8 8 10

23.9.4 Să se creeze un Dataframe din aceeași sursă de date ca la exemplul anterior (carnetul
de note ale unui elev), dar datele sunt privite ca listă de tuple.
R23.9.4
import matplotlib.pyplot as plt
import pandas as pd

Note = [('Mate', '3.04.22', 9),


('Fizica', '7.04.22', 8),
('Istorie', '5.04.22', 8),
357 Pandas

('Sport', '11.04.22', 10)]


df = pd.DataFrame.from_records(Note, columns = ['Disc.', 'Data', 'Nota'])

Output
Disc. Data Nota
0 Mate 3.04.22 9
1 Fizica 7.04.22 8
2 Istorie 5.04.22 8
3 Sport 11.04.22 10

23.9.5 Să se sorteze un Dataframe după o coloană, sau indexul rândurilor.


R23.9.5 O metodă de sortare după o coloană a unui DataFrame impune ca în prealabil
coloana să se transforme în indexul rândurilor prin metoda set_index, apoi noului
DataFrame să se aplice metoda sort_index().=
import matplotlib.pyplot as plt
import pandas as pd

Catalog = [('Alex', 'Mate', '3.04.22', 9),


('Mihai', 'Fizica', '7.04.22', 8),
('Bogdan', 'Istorie', '5.04.22', 8),
('Alex', 'Sport', '11.04.22', 10),
('Mihai', 'Mate', '3.04.22', 9),
('Alex', 'Fizica', '7.04.22', 8),
('Bogdan', 'Istorie', '5.04.22', 8),
('Mihai', 'Sport', '11.04.22', 10),
('Bogdan', 'Mate', '3.04.22', 9),
('Mihai', 'Fizica', '7.04.22', 8),
('Alex', 'Istorie', '5.04.22', 8),
('Mihai', 'Sport', '11.04.22', 10)]
df = pd.DataFrame.from_records(Catalog, columns = ['Nume', 'Disc.', 'Data',
'Nota'])
print('DataFrame initial')
print(df, '\n')
#df = df.transpose()
#print("Transpusa df obtinut\n", df)
df = df.set_index('Nume')
print('DataFrame reindexat\n')
print(df)
sort_df = df.sort_index()
print('DataFrame sortat')
print(sort_df)

Output
DataFrame initial
Nume Disc. Data Nota
0 Alex Mate 3.04.22 9
1 Mihai Fizica 7.04.22 8
2 Bogdan Istorie 5.04.22 8
3 Alex Sport 11.04.22 10
4 Mihai Mate 3.04.22 9
5 Alex Fizica 7.04.22 8
6 Bogdan Istorie 5.04.22 8
7 Mihai Sport 11.04.22 10
8 Bogdan Mate 3.04.22 9
9 Mihai Fizica 7.04.22 8
10 Alex Istorie 5.04.22 8
11 Mihai Sport 11.04.22 10
358 Pandas

DataFrame reindexat
Disc. Data Nota
Nume
Alex Mate 3.04.22 9
Mihai Fizica 7.04.22 8
Bogdan Istorie 5.04.22 8
Alex Sport 11.04.22 10
Mihai Mate 3.04.22 9
Alex Fizica 7.04.22 8
Bogdan Istorie 5.04.22 8
Mihai Sport 11.04.22 10
Bogdan Mate 3.04.22 9
Mihai Fizica 7.04.22 8
Alex Istorie 5.04.22 8
Mihai Sport 11.04.22 10

DataFrame sortat
Disc. Data Nota
Nume
Alex Mate 3.04.22 9
Alex Sport 11.04.22 10
Alex Fizica 7.04.22 8
Alex Istorie 5.04.22 8
Bogdan Istorie 5.04.22 8
Bogdan Istorie 5.04.22 8
Bogdan Mate 3.04.22 9
Mihai Fizica 7.04.22 8
Mihai Mate 3.04.22 9
Mihai Sport 11.04.22 10
Mihai Fizica 7.04.22 8
Mihai Sport 11.04.22 10
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 24

SciPy
24.1 Introducere
SciPy (inițiat în 2001 de Travis Oliphant, pe atunci doctorant) este o colecție de algoritmi
matematici și funcții utile construite pe baza bibliotecii NumPy cu scopul de (1) a mări
capacitatea de manipulare, prelucrare și vizualizare a datelor în Python și de (2) a rezolva
probleme științifice și matematice.
Denumirea Scipy derivă din “Scientific Python”. Este o aplicație open source. Cea mai mare
parte a aplicației este scrisă în Python, dar există și părți scrise în C.
Atât NumPy, cât și SciPy sunt biblioteci Python utilizate pentru analiză matematică și numerică.
NumPy conține tipul de date ndarray (inclusiv matrice) și operațiunile de bază cu acesta, cum
ar fi sortarea, indexarea etc., în timp ce SciPy constă din rutine numerice mai complete. Deși
NumPy oferă o serie de funcții care pot ajuta la rezolvarea unor probleme de algebră liniară,
transformări Fourier etc., SciPy este biblioteca având de fapt versiuni dezvoltate ale acestor
funcții și multe altele în plus. În consecință, va fi necesar să se instaleze atât NumPy, cât și
SciPy, deoarece SciPy se bazează pe NumPy.
În consecință, combinația Python + Numpy + Scipy este un concurent puternic, open source,
pentru sisteme consacrate în calculul științific și tehnic ca MATLAB (sistem proprietar),
Octave, R, SciLab, etc.
Pentru concizie și comoditate, se presupune că pachetele principale (numpy, scipy și matplotlib)
au fost importate astfel:
>>>import numpy as np
>>>import matplotlib as mpl
>>>import matplotlib.pyplot as plt
>>>import scipy

Verificarea versiunii pachetului SciPy:


>>>print(scipy, __version__)
1.7.3

Subpachetele Scipy
Pachetul Scipy este constituit din următoarele subpachete corespunzătoare diferitelor domenii
științifice:
cluster – algoritmi pentru clustere
constants – conține constante fizice și matematice
fftpack – funcții de calcul pentru transformate Fourier rapide (vechi)
fft – funcții de calcul pentru transformate Fourier rapide (actualizat)
integrate – rezolvarea ecuațiilor diferențiale și integrale
interpolate – funcții spline pentru interpolareși netezire
io – funcții pentru operații de intrare – ieșire
linalg – funcții de calcul pentru algebra liniară
ndimage – procesarea imaginilor n-dimensionale
odr – metode de regresie distanta ortogonală
360 SciPy

optimize – rutine pentru optimizare și calculul soluțiilor ecuațiilor


signal – procesarea semnalelor
sparse – rutine pentru tratarea maricilor rare (sparse)
spatial – algoritmi și structuri de date spațiale
special – funcții speciale (airy, elliptic, bessel, gamma, beta, hypergeometric,
parabolic cylinder, mathieu, spheroidal wave, struve, kelvin)
stats – funcții și distribuții statistice
Pentru a ști ce funcții conțin subpachetele, acestea trebuie importate din Scipy, apoi se
utilizează comanda help(subpachet) pentru a le afișa.De exemplu:
>>>from scipy import special as sp
>>>help(sp)
Help on package scipy.special in scipy:

NAME
scipy.special

DESCRIPTION
========================================
Special functions (:mod:`scipy.special`)
========================================

.. currentmodule:: scipy.special
..........................................................

De asemenea, după importul subpachetului se pot obține lista rutinelor și descrierile lor, pe
scurt, folosind comanda dir() și print(scipy.subpachet.__doc__).
from scipy import linalg as lna
dir(lna)
['LinAlgError',
'LinAlgWarning',
'__all__',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
. . . . . . . . . . . . . . . . . . .
'_solve_toeplitz',
'_solvers',
'basic',
. . . . . . . . . . . . . . . . . . .
'cho_solve_banded',
'cholesky',
. . . . . . . . . . . . . . . . . . . .

Observație. Scipy conține un număr imens de rutine care implementează algoritmi și funcții.
Observație. Pentru transferul datelor în și din alte aplicații, scipy utilizează subpacketul io.
Acesta conține rutine de conversie ale fișierelor de date de tip MATLAB (de notat existența
unui subpachet suplimentar scipy.io.matlab), IDL, Matrix Marchet, Fortran, etc. De
asemenea, există rutine de conversie a fișierelur audio de tip wave.
361 SciPy

24.2 Aplicații Scipy


24.2.1 Funcții trigonometrice și exponențiale
Exemplul 24.1 #funcții trigonometrice și exponențiale
from scipy import special
a = special.exp10(3)
print(a)
b = special.exp2(3)
print(b)
c = special.sindg(90)
print(c)
d = special.cosdg(45)
print(d)

Output
1000.0
8.0
1.0
0.7071067811865475

24.2.2 Calcul integral


Scipy are rutine pentru rezolvarea tuturor tipurilor de integrale. Una dintre acestea se numește
quad() și rezolvă integrarea funcțiilor de o variabilă. Limitele pot fi ±∞ (± inf), în cazul
limitelor infinite.

Sintaxa: quad(func, a, b, args=(), full_output=0, epsabs=1.49e-08,


epsrel=1.49e-08, limit=50, points=None, weight=None, wvar=None, wopts=None,
maxp1=50, limlst=50)

Exercițiul 24.2 #integrala funcției de o variabilă


from scipy import special
from scipy import integrate
a= lambda x:special.exp10(x)
b = scipy.integrate.quad(a, 0, 1)
print(b)

Output
(3.9086503371292665, 4.3394735994897923e-14)

Primul argument al funcției quad() de mai sus este funcția de integrat, iar următoarele două
argumente sunt limitele de integrare. Rezultatul este un tuplu cu două valori: prima este
valoarea integrată a funcției, iar a doua este limita de eroare.
Pentru calculul integralei duble (integrala unei funcții de două variabile) se utilizează funcția
dblquad. Argumentele funcției dblquad sunt funcția de integrat și limitele dy (două variabile,
sau două constante) și dx (alte două variabile, sau alte două constante).
Exercițiul 24.3 #integrala funcției de două variabile
from scipy import integrate
a = lambda y, x: (x+1)*y**2+5
b = lambda x: 10
362 SciPy

c = lambda x: -10
print(integrate.dblquad(a, 0, 5, b, c))
Output
(-12166.666666666668, 1.350771346627274e-10)

Scipy oferă și alte funcții pentru calculul integralelor: triple, de ordinul n, etc. etc. Ele pot fi
studiate folosind comanda help().

24.2.3 Algebră liniară

Problemele de algebră liniară se referă la rezolvarea sistemelor de ecuații algebrice liniare


folosind vectori și matrice.
Scipy este rapid în găsirea soluțiilor, fiind bazat pe utilizarea bibliotecilor ATLAS LAPACK și
BLAS. Deși Scipy poate utiliza și biblioteca numpy.linalg, viteza mai mare de rezolvare
recomandă utilizarea bibliotecii scipy.linalg, care oferă și un număr mai mare de funcții.
- Calculul inversei și determinantului unei matrici.
Calculul inversei matricii x se face cu funcția linalg.inv(x).
Calculul determinantului matricii x se face cu funcția linalg.det().
Sintaxa: det(a, overwrite_a=False, check_finite=True), unde parametrii sunt:
a = o matrice pătrată a(N, N);
overwrite_a = opțional, boolean, permite rescrierea matricii a
check_finite = verificarea matricii a că are numai numere finite

Exemplul 24.4 #Calculul inversei unei matrice și a determinantului său


import numpy as np
from scipy import linalg
A = np.array([[1,2], [4,3]])
B = linalg.inv(A)
print('Matricea inversă:')
print(B)
print('Calculul determinantului:')
C = linalg.det(A)
print(C)

Output
Matricea inversă:
[[-0.6 0.4]
[ 0.8 -0.2]]
Calculul determinantului:
-5.0

- Calculul valorilor proprii ale unei matrice


Pentru calculul valorile și vectorilor proprii sunt disponibile două funcții:eigh și eigs. Funcția
eigs permite găsirea valorilor proprii ale matricilor pătrate nesimetrice reale sau complexe, iar
funcția eigh permite același lucru pentru matricile real-simetrice sau hermitian-complexe.
Exemplul 24.5 # calculul valorilor și vectorilor proprii pentru o matrice reală
from scipy import linalg
import numpy as np
A = np.array([[1,2,3,4],
[4,3,2,1],
363 SciPy

[1,4,6,3],
[2,3,2,5]])
x, y = linalg.eigh(A)
print('valorile proprii:', x)
print('vectorii proprii:', y)

Output
valorile proprii: [-2.53382695 1.66735639 3.69488657 12.17158399]
vectorii proprii: [[ 0.69205614 0.5829305 0.25682823 -0.33954321]
[-0.68277875 0.46838936 0.03700454 -0.5595134 ]
[ 0.23275694 -0.29164622 -0.72710245 -0.57627139]
[ 0.02637572 -0.59644441 0.63560361 -0.48945525]]
24.2.4 Funcții de interpolare
Subpachetul scipy.interpolate pentru realizarea interpolării în SciPy se referă la funcții de
o variabilă (interp1d), sau de mai multe variabile (interp2d).
Exemplul 24.6 #interpolare 1D
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
x = np.arange(5, 20)
y = np.exp(x/2.0)
f = interpolate.interp1d(x, y)
x1 = np.arange(5, 20)
y1 = f(x1)
plt.plot(x, y, 'o', x1, y1, '--')
plt.show()

24.2.5 Funcții de optimizare


Modulul scipy.optimize conține:
- un număr de algoritmi de optimizare cu și fără constrângeri dintre cei mai cunoscuți (metoda
gradientului conjugat al lui Newton, BFGS, Nelder_Mead, etc.),
- rutine de optimizare globale (differential_evolution, etc.),
- rutine de minimizare prin metoda celor mai mici pătrate și potrivire curbe (least_squares,
curve_fit, etc.),
- rutine pentru minimizări scalare ți rezolvarea de ecuații (minimize_scalar, root_scalar),
- rutine pentru rezolvarea ecuațiilor de mai multe variabile (cu metodele Powell, Levenberg-
Marquardt, etc.).
Functia Rosenbrock (rosen) este utilizată frecvent pentru testarea algoritmilor de optimizare
bazați pe gradienți. În exemplul următor se va prezenta minimizarea funcției Rosenbrock
utilizând metoda Nelder-Mead. Funcția Rosenbrock de două variabile (denumită și funcția
banană, datorită aspectului său) are expresia cea mai generală:
rosen(x,y)= a*(1-x)**2 + b*((y-x**2))**2

Frecvent se utilizează valorile a = 1 și b =100.


Exemplul 24.7 #Reprezentarea grafică a funcției Rosenbrock
import numpy as np
from scipy import optimize
364 SciPy

import matplotlib.pyplot as plt


from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-1, 1, 50)
X, Y = np.meshgrid(x, x)
ax = plt.subplot(111, projection='3d')
ax.plot_surface(X, Y, optimize.rosen([X, Y]))
plt.show()

Output

Exemplul 24.8 #minimizarea funcției Rosenbrock cu metoda Nelder-Mead


# Functia Rosenbrock este definită în SciPy (optimize), astfel:
#rosen(x)
# sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)

>>> from scipy import optimize


>>> X = 0.1 * np.arange(10)
>>> optimize.rosen(X)
76.56

# aplicarea metodei Nelder-Mead


>>> from scipy import optimize
>>> x = [2.4, 1.7, 3.1, 2.9, 0.2]
>>> m = optimize.minimize(optimize.rosen, x, method = 'Nelder-Mead')
>>> print(m)
final_simplex: (array([[0.96570182, 0.93255069, 0.86939478, 0.75497872,
0.56793357],
[0.96572026, 0.93264154, 0.86936943, 0.75499743, 0.56793092],
[0.96574802, 0.93263174, 0.86948823, 0.75502334, 0.56791589],
[0.96575679, 0.93261393, 0.86937052, 0.75502138, 0.5679214 ],
[0.96575237, 0.93259073, 0.86937829, 0.7548972 , 0.56793349],
[0.96567428, 0.93255106, 0.86942025, 0.75496516, 0.56793388]]),
array([0.08332508, 0.08332508, 0.08332568, 0.08332607, 0.08332695,
0.08332754]))
fun: 0.08332507501465103
message: 'Optimization terminated successfully.'
nfev: 260
nit: 150
status: 0
success: True
365 SciPy

x: array([0.96570182, 0.93255069, 0.86939478, 0.75497872,


0.56793357])

24.2.6 Transformata Fourier


În general, Transformata Fourier (TF) se referă la descompunerea unei funcții matematice în
funcții trigonomerice simple (ce formează o bază). În practică, pe scurt, TF este o unealtă de
punere în corespondență a unei funcții a cărei valoare variază în timp, denumită semnal, cu o
mulțime de numere, denumite frecvențe.
Domeniul principal de utilizare al TF este procesarea semnalelor (electrice, dar care pot fi și de
altă natură) care variază în timp. TF face o transformare din reprezentarea în domeniul timp în
domeniul frecvență. În domeniul timp se reprezintă valoarea amplitudinii la un anumit moment
de timp, iar în domeniul frecvență se reprezintă puterea semnalului pentru fiecare frecvență în
parte, semnalul fiind descompus în componente frecvențiale (spectrul). De notat că suma
componentelor spectrale reprezintă puterea semnalului exprimată în domeniul frecvență și este
egală cu puterea semnalului calculată în domeniul timp.
Exemple de semnale care se modifică în timp, periodic sau aperiodic: semnale audio, video,
tensiunea de rețea de 220V, surse de semnal senzorial (temperatură, presiune, forță, vibrații,
etc.), etc.
Exemple de frecvențe (frecvența fiind definită ca valoarea inversului perioadei T de repetiție a
unui semnal, f = 1/T): 1Hz (o oscilație pe secundă), 50Hz (tensiunea de rețea), 102,4MHz poate
fi frecvența unui post de radio, 3GHz poate fi frecvența de lucru a procesorului unui laptop PC,
etc.
TF se poate trata fie analogic, fie digital (TFD, în engleză DFT). În biblioteca SciPy.fft sunt
rutine de calcul pentru TFD. O altă denumire interschimbabilă cu TFD este Transformata
Fourier Rapidă (Fast Fourier Transform, FFT, - deși nu tocmai exact: FFT este doar un algoritm
de calcul, nu o metodă generală).
Funcția fft acceptă la intrare atât valori întregi, cât și complexe. O creștere a vitezei de
prelucrare se poate obține prin rutina rfft care primește la intrare numai valori întregi.
În legătură cu DFT sunt alte două transformări conținute în pachetul fft, DCT (Discret Cosine
Transform) și DST (Discret Sine Transform), care au multe aplicații, inclusiv în procesarea
imaginilor.
Transformatele Fourier discretă și inversă ale unei secvențe reale sau complexe se pot obține
cu funcția fft, respectiv ifft, din modulul fft. Acest modul, mai nou și actualizat, a înlocuit
modulul fftpack.
De asemenea, un set de rutine pentru calculul transformatei Fourier există și în Numpy. Totuși.
subpachetul SciPy.fft este superior și acestuia.
Exemplul 24.9 #Transformata fft directă și ifft inversă
import numpy as np
from scipy.fft import fft, ifft
x1 = np.array([0,1,2,3])
y1 = fft(x1)
print('Transf. Fourier directa:',y1)
x2 = np.array([6.-0.j, -2.+2.j, -2.-0.j, -2.-2.j])
y2 = ifft(x2)
print('Transf. Fourier inversa:',y2)
366 SciPy

Output
Transf. Fourier directa: [ 6.-0.j -2.+2.j -2.-0.j -2.-2.j]
Transf. Fourier inversa: [0.+0.j 1.+0.j 2.+0.j 3.+0.j]

Exemplul 24.10 #Transformata fft a unor semnale electrice (sin(t) și cos(t))


import numpy as np
from scipy.fft import fft, fftfreq, fftshift
import matplotlib.pyplot as plt
t = np.arange(256)
sp = fftshift(fft(np.sin(t)))
sp1 = fftshift(fft(np.cos(t)))
freq = fftshift(fftfreq(t.shape[-1]))
plt.figure(1)
#plt.plot(freq, sp.real, freq, sp.imag)
plt.subplot(2,2,1)
plt.plot(freq, sp.real)
plt.subplot(2,2,2)
plt.plot(freq, sp.imag)
plt.subplot(2,2,3)
plt.plot(freq, sp1.real)
plt.subplot(2,2,4)
plt.plot(freq, sp1.imag)
plt.show()

Output

24.2.7 Procesarea semnalelor


Subpachetul Scipy.signal conține rutine pentru procesarea semnalelor, aceasta însemnând
analiza, sinteza și modificarea unor semnale de orice fel (electrice, mecanice, acustice, optice,
sunete, imagini, etc.).
Funcțiile existente în subpachet pot fi utilizate pentru proiectarea, filtrarea sau interpolarea
semnalelor 1D și 2D.
Domeniile de utilizare ale funcțiilor de procesare a semnalelor sunt:
- Convoluția semnalelor
- Filtrarea semnalelor, proiectarea filtrelor (inclusuv de tipul IIR, în stilul Matlab)
- Sisteme liniare continue în timp
- Sisteme liniare în timp discret
- Reprezentarea sistemelor LTI (Sistele Liniare Invariante în Timp)
367 SciPy

- Generarea formelor de undă (weiveforms)


- Funcții fereastră
- Wavelets
- Analiză spectrală
- Transformata z
Exemplul 24.11 #Funcții pentru generarea unor semnale
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
#semnal triunghiular ('dinti de fierastrau)
plt.figure(1)
t = np.linspace(0, 1, 500)
plt.plot(t, signal.sawtooth(2 * np.pi * 5 * t))
plt.title('Semnal triunghiular')
# impuls gausian
plt.figure(2)
t = np.linspace(-1, 1, 2 * 100, endpoint=False)
i, q, e = signal.gausspulse(t, fc=5, retquad=True, retenv=True)
plt.plot(t, i, t, q, t, e, '--')
plt.title('Semnal gaussian')
#O forma de unda de 10 Hz eșantionata cu 500 Hz timp de 1 secunda
plt.figure(3)
t = np.linspace(0, 1, 500, endpoint=False)
plt.plot(t, signal.square(2 * np.pi * 10 * t))
plt.ylim(-2, 2)
plt.title('Semnal dreptunghiular')
#impuls modulat in durata cu sinusoida
plt.figure(4)
sig = np.sin(2 * np.pi * t)
pwm = signal.square(2 * np.pi * 30 * t, duty=(sig + 1)/2)
plt.subplot(2, 1, 1)
plt.plot(t, sig)
plt.title('Semnalul modulator')
plt.subplot(2, 1, 2)
plt.plot(t, pwm)
plt.ylim(-1.5, 1.5)
plt.tight_layout()
plt.title('Semnal modulat in durata cu sinusoida')
plt.show()

Output
368 SciPy

Un alt exemplu poate fi generarea unui semnal modulat în frecvență utilizând funcția
sweep_poly(t, poly, phi=0). Această funcție generează o funcție cosinusoidală a cărei
frecvență instantanee variază în timpul t cu frecvența dată de polinomul poly(t).
Exemplul 24.12 #generarea unui semnal modulat cu funcția sweep_poly
# functia modulatoare a frecventei: p(t) = -1.5*t**2 + 2.25*t + 3
# intervalul 0 <= t <= 6.
import numpy as np
from scipy.fft import fft, fftfreq, fftshift
from scipy.signal import sweep_poly
import matplotlib.pyplot as plt
p = np.poly1d([-1.5, 2.25, 3.0]) #construieste polinomul p(t)
#cu coeficientii -1.5, 2.25 si 3.01
t = np.linspace(0, 6, 5001)

w = sweep_poly(t, p) #construieste functia sin/cos modulata


#cu polinomul p(t)

# reprezentarea grafica
plt.figure(1)
plt.plot(t, p(t), 'r', label='f(t)')
plt.legend()
plt.xlabel('t')
plt.title('Reprezentarea polinomului')

plt.figure(2)
plt.plot(t, w)
plt.xlabel('t')
plt.title("Modulare polin. a unei cosinusoide " +
"cu frecv. p(t) = -1.5*t**2 + 2.25*t + 3.01")

plt.figure(3)
#Calcul Transformata Fourier
sp = fftshift(fft(w))
freq = fftshift(fftfreq(t.shape[-1]))
plt.plot(freq, sp, 'b', label='fft')

plt.legend()
plt.xlabel('f')
plt.show()

Output
369 SciPy

Răspunsul unui sistem digital la semnalul impuls unitar și la treapta unitară


Scipy conține un set de rutine pentru analiza sistemelor LIT (Liniare și Invariante în Timp).
Răspunsul la impulsul și treapta unitare pentru un sistem LIT de ordinul al doilea (de tip FTJ)
având funcția de transfer H(s) = 1/(s2 + As + B) se prezintă în exemplul următor.
Exemplul 24.13 Raspunsul FTJ (Filtrul Trece Jos) de ordinul al doilea la impuls
from scipy import signal
import matplotlib.pyplot as plt
#sistem = signal.lti([1.0], [1.0, 2.0, 1.0])
#sistem = ([1.0], [1.0, 2.0, 1.0])
sistem = ([1.0], [1.0, 1.75, 3.0])
t, y1 = signal.impulse(sistem)
plt.plot(t, y1)
t, y2 = signal.step(sistem)
plt.plot(t, y2)
plt.xlabel('Timp [s]')
plt.ylabel('Amplitudine')
plt.title('Răspunsul la impuls si treaptă pentru un FTJ de ordinul 2')
plt.grid()
plt.show()
370 SciPy

Output

Sistemul LIT (liniar și invariant în timp) va fi un filtru Butterworth (răspuns maximum plat în
banda de trecere).
Exemplul 24.13 #răspunul filtrului Butterworth la impulsul unitar și treapta unitară
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
#filtru de ordinul 4
#numarul de esantioane/puncte de calcul al raspunsului n = 20
filtrul_butter = signal.dlti(*signal.butter(4, 0.5))
#butter intoarce functia de transfer ca raport de polinoame
#filtrul_butter este o instanta a functiei de transfer
plt.figure(1)
plt.title('Raspunsul la impuls')
t, y = signal.dimpulse(filtrul_butter, n=20)
plt.step(t, np.squeeze(y))
plt.grid()
plt.xlabel('n=20 [esantioane]')
plt.ylabel('Amplitudine')
plt.figure(2)
plt.title('Raspunsul la treapta')
filtrul_butter1 = signal.dlti(*signal.butter(4, 0.5))
t1, y1 = signal.dstep(filtrul_butter1, n=20)
plt.step(t1, np.squeeze(y1))
plt.grid()
plt.xlabel('n=20 [esantioane]')
plt.ylabel('Amplitudine')
plt.show()

Output
371 SciPy

24.2.8 Modulul Spatial Data Structures and Algorithms

Datele spațiale constau practic din obiecte care sunt alcătuite din linii, puncte, suprafețe etc.
Pachetul scipy.spatial al SciPy poate calcula diagrame Voronoi, triangulații etc. folosind
biblioteca Qhull. De asemenea, conține implementări KDTree pentru interogări de punct de
tipul cel mai apropiat vecin.

Exemplu de algoritm spațial: triangulația Delaunay


Triangulația unui set de puncte este o problemă foarte bine cunoscută și utilizată în multe
domenii de cercetare științifică dar și în industria de entertainment (scanare 3D, algoritmi de
găsire a căii optime în jocuri, etc).
O triangulație T(P) a unui set de puncte P în spațiul Euclidian este o mulțime de arce E astfel
încât:
1. Nu există 2 arce care se intersectează într-un punct care nu este în P.
2. Arcele din E împart acoperitoarea convexă a lui P în triunghiuri.
Triangulația a unui set de puncte P din plan este de tip Delaunay (DT(P)) dacă și numai dacă
cercul circumscris oricărui triunghi din DT(P) nu conține alt punct din interiorul lui P.
Exemplul 24.14 #Triangulația Delauny
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Delaunay
P = np.array([[0, 1], [1, 1], [1, 0],[0, 0], [1.5, 1.5], [0.6,1.2]])
a = Delaunay(P) #Obiect Delaunay, P sunt puncte
print(a)
print(a.simplices)
plt.triplot(points[:,0], points[:,1], a.simplices)
plt.plot(points[:,1], points[:,0], 'o')
plt.show()

Output
<scipy.spatial.qhull.Delaunay object at 0x000001DEA1AD4EB0>
[[1 2 4]
[5 1 4]
[5 3 2]
[3 5 0]
[1 5 2]]
372 SciPy

24.3 Întrebări, exerciții și probleme


24.3.1 Utilizând SciPy, să se calculeze permutări, combinații și aranjamente de n obiecte luate
câte k, unde n și k pot să fie atât valori întregi individuale, cât și vectori (liste).

R24.3.1
import numpy as np
from scipy.special import comb, perm
k = np.array([2, 3])
n = np.array([4, 4])
#se asociaza pt. calcul: n1-k1, n2-k2, ...
#exact = True => intregi, altfel virgula mobila (float point)
print('permutari n=', n, ' cate k=', k, ':', perm(n, k, exact=False))
print('permutari 4 cate 2:', perm(4, 2, exact=True))
print('combinatii n=', n,' luate cate k=', k, ':', comb(n, k, exact=False))
print('combinatii 4 luate cate 2:', comb(4, 2, exact=True))
print('aranjamente 4 luate cate 2:', comb(4, 2, exact=True, repetition=True))
Output
permutari n= [4 4] cate k= [2 3] : [12. 24.]
permutari 4 cate 2: 12
combinatii n= [4 4] luate cate k= [2 3] : [6. 4.]
combinatii 4 luate cate 2: 6
aranjamente 4 luate cate 2: 10

24.3.2 Sa se găsească minimul funcției:


F(t) = t*np.exp(-t/15) + 10*np.sin(t) + (-3*t+5),

utilizând algoritmul de optimizare BFGS.


R24.3.2
import matplotlib.pyplot as plt
from scipy import optimize
import numpy as np

def function(t):
return t*np.exp(-t/15) + 10*np.sin(t) + (-3*t+5)

t = np.linspace(-17, +2)

fig, ax = plt.subplots()
#ax.plot()
ax.margins(0.2, 0.2) #controlul marginilor
373 SciPy

plt.plot(t, function(t))
plt.show()
#algoritmul de optimizare BFGS
optimize.fmin_bfgs(function, x0=0)

Output

Optimization terminated successfully.


Current function value: -2.191972
Iterations: 4
Function evaluations: 12
Gradient evaluations: 6

24.3.3 să se rezolve ecuația polinomială


p[0] * x**n + p[1] * x**(n-1) + ... + p[n-1]*x + p[n]

ai cărei coeficienți sunt conținuți în tabloul p = [3.5, -7, 8.2, 2.2, 1],
R24.3.3 Se poate utiliza funcția numpy.roots(p). De asemenea se poate utiliza funcția mai
generală:
scipy.optimize.fsolve(func, x0, args=(), fprime=None, full_output=0,
col_deriv=0, xtol=1.49012e-08, maxfev=0, band=None, epsfcn=None, factor=100,
diag=None),

care poate fi utilizată pentru orice fel de ecuație, nu numai polinomială.


24.3.4 Să se reprezinte funcția sinus cardinal, notată sinc(x) = (sin(x* )/x*) utilizând
biblioteca SciPy.
R24.3.4 Funcția sinus cardinal, sinc(x), este utilizată în multe domenii, în special în electronică
și comunicații. Pentru x = 0, are la limită valoarea 1.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 101)
y = np.sinc(x)
plt.plot(x, y)
#print(y)

Output
374 SciPy

24.3.5 Să se genereze o o formă de undă utilizând funcția chirp. Semnalul chirp este un
cosinus cu frecvența modulată.
R24.3.5
import numpy as np
from scipy.signal import chirp
import matplotlib.pyplot as plt
t = np.linspace(5, 15, 500) #timpul
w = chirp(t, f0=4, f1=2, t1=5, method='linear')
#f0 = frecventa la t = 0
#f1 este frecventa la momentul t1
#methode = metoda de baleiere
#alternative la linear: quadratic, logaritmic, hyperbolic
#alti parametri mai sunt: phi = offsetul de faza,
#vertex_zero (pt quadratic)
plt.plot(t, w)
plt.title("Semnal Chirp liniar")
plt.xlabel('Timpul in secunde)')
plt.show()

Output
375 SciPy

24.3.6 Să se genereze o secvență de semnal modulat, necesar pentru testarea TFD. Să se


calculeze spectrul.
R24.3.6
import numpy as np
from matplotlib import pyplot as plt
from scipy.fft import fft, fftfreq, fftshift

SAMPLE_RATE = 50 # Herti
DURATA = 2 # Secunde

def generate_sine_wave(freq, sample_rate, duration):


x = np.linspace(0, duration, sample_rate * duration, endpoint=False)
plt.figure(1)
plt.stem(x)
frequencies = x * freq
# 2pi faza este in radiani
y = np.sin((2 * np.pi) * frequencies)+np.sin((4 * np.pi) *
frequencies)+np.sin((12 * np.pi) * frequencies)
# plt.figure(2)
# plt.stem(y)
return x, y

# Se genereaza o secventa sinusoida de 2Hz, care dureaza 5s


# Frecventa de esantionare a secventei este 20Hz
x, y = generate_sine_wave(2, SAMPLE_RATE, DURATION)
plt.figure(2)
plt.stem(x, y)
#plt.show()

# Transformata Fourier
sp = fftshift( fft(y) )
freq1 = fftshift( fftfreq(x.shape[-1]) )
#plt.figure()
#plt.plot(freq, sp.real, freq, sp.imag)

plt.figure(3)
plt.subplot(1,2,1)
plt.plot(freq1, sp.real)
plt.subplot(1,2,2)
plt.plot(freq1, sp.imag)
plt.show()

Output
376 SciPy
Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 25

Procesarea imaginilor în Python


25.1 Unelte pentru proocesarea imaginilor în Python
În Python sunt disponibile mai multe instrumente pentru procesarea imaginilor, mai precis
pentru: redimensionare, decupare, rotire, segmentare, clasificare, extragere de caracteristici,
recunoașterea obiectelor din imagine, restaurare, etc.
Aceste instrumente sunt disponibile sub forma unor rutine în biblioteci care se pot importa.
Cele mai importante și mai cunoscute, scrise integral în Python (cu excepția OpenCV-Python)
sunt:
• Matplotlib și Numpy. În Numpy, o imagine este un tablou numpy de pixeli (puncte); din
acest motiv se pot face ușor operații asupra pixelilor. Este bine documentat.
• Pillow (Python Imaging Library). PIL este o bibliotecă open-source scrisă în Python a
cărei dezvoltare s-a oprit în 2011, fiind continuată ulterior prin (“fork”) Pillow.
• Scipy.ndimage. Scipy este un pachet software bazat pe Numpy. Conține submodulul
scipy.ndimage care conține funcții operând pe tablouri n-dimensionale Numpy. Funcțiile se
referă la filtrarea liniară și neliniară, morfologia binară, interpolare B-spline, măsurări ale
obiectelor, etc. Este bine documentat.
• Scikit-image/skimage. Are modulul principal denumit skimage (de aici denumirea dată
frecvent pachetului), fiind un pachet open-source care se bazează pe scipy.ndimage pentru
realizarea sistemului de rutine pentru procesarea imaginii. Realizează operații de calitate,
poate face prelucrări superioare bibliotecii Pillow, dar poate fi utilizat și de începători. Este
produs de o comunitate de voluntari activi, fiind bine documentat.
Observație. Denumirea Scikit provine din prescurtarea “scipy toolkit”.
• OpenCV-Python (Open Source Computer Vision Library). OpenCV-Python este un API
al Python-ului pentru OpenCV. Este rapid (OpenCV este scris în C/C++) și ușor de folosit,
bun pentru procesări intensive. Probabil este cea mai bine apreciată și utilizată bibliotecă.
Documentare în OpenCV-Python-Guide.

Alte sisteme mai cunoscute sunt:


• SimpleCV. Este o bibliotecă open-source, un subset al OpenCV, mai ușor de învățat și
utilizat, recomandată pentru începători.
• Mahotas. Este o bibliotecă având o interfață cu Python, scrisă în C/C++, cu funcții rapide
(filtrare, operații morfologice, detecția punctelor și descriptorilor de interes, etc.).
• Simple ITK (Insight Segmentation and Registration Toolkit). Este o bibliotecă open-
source, cross-platform, un strat simplificat al ITK. Permite rapid-prototyping, operații de
filtrare, segmentarea imaginii, etc. Scrisă în C, disponibilă în Python, are multe exemple în
Jupiter Notebook.
• Pgmagick. Este o interfață (wrapper) Python pentru biblioteca Graphics Magick, permite
foarte multe operații pe formate de imagini (disponibilă pe Github).
• Pycairo. Este un set de legături cu sistemul Cairo (disponibil pe Github).
378 Elemente de procesare a imaginilor în Python

25.2 Modelul imaginii utilizat în procesarea digitală


25.2.1 Tipuri de imagini digitale

În continuare se face referire doar la tipurile de imagine din categoria “raster” sau “bitmap”,
nu și din categoria “vectoriale”. Tipurile “raster” curent utilizate sunt:

Binar. Pixelii imaginii sunt fie albi, fie negri. Fiecărui pixel îi corespunde un bit în memorie.
Grayscale. Fiecare pixel reprezintă o nuanță de gri, codificată cu valori între 0 (negru) și 255
(alb). Fiecare pixel poate fi reprezentat prin 8 biți în memorie. Alte convenții de gri sunt
utilizate în imagistica medicală.
RGB (True Color). Fiecare pixel are o culoare specifică, fiind descrisă de o combinație de
cantități de roșu, verde și albastru. Dacă fiecare componentă de culoare are corespondent un
domeniu 0 – 255, atunci pot fi reprezentate în total 2563 = 16.777.216 de combinații
însemnând culori distincte. Numărul total de biți necesari pentru un pixel este 24; astfel de
imagini sunt denumite “imagini pe 24 de biți”. Imaginea poate fi considerată ca fiind o stivă
de 3 matrice reprezentând roșul, verdele și albastrul.
Indexat. Culorile utilizate în imagine reprezintă doar un mic set selecționat din cele peste 16
milioane de culori posibile. Pentru ușurința memorării și prelucrării fișierelor, imaginile au
asociate o hartă/dicționar de culori (color map, sau color pallete), care este doar o listă simplă
a tuturor culorilor utilizate. Fiecare pixel are o valoare care nu este o culoare (ca pentru o
imagine RGB), ci un index la o culoare din lista de culori specifică acelei imagini.
Pentru simplitate și ușurință este convenabil ca o imagine să conțină 256 sau mai puține
culori, astfel ca indexul să poată fi memorat pe 1 octet. În consecință, a apărut formatul GIF
(și altele) care permite 256 culori sau mai puțin pentru o imagine, obținându-se o dimensiune
redusă a fișierului care conține imaginea.
25.2.2 Convenții de reprezentare a culorilor în imaginile digitale
Spațiul culorilor este o cale de a reprezenta canalele de culoare compunând o imagine. Există
multe spații de culoare și fiecare dintre ele are o structură aparte. Numărul lor mare (peste
100) se datorează faptului că fiecare nou spațiu de culoare apărut a fost destinat unor aplicații
cu cerințe diferite. Dintre acestea, câteva spații de culoare sunt mai frecvent întâlnite: RGB,
CMYK, HSV/HSL, YcrYCb, Lab.
RGB (red, green, blue), utilizat pentru imagini video, este considerat un spațiu de culoare
“aditiv”, culorile fiind imaginate ca produse din proiectarea unor cantități de roșu, verde și
albastru pe un fond negru, figura 2.
Spațiul de culoare RGB se poate reprezenta în 3D ca un cub al culorilor, cu trei axe
ortogonale RGB, figura 1.
Exemple: red = (255, 0, 0), orange = (255, 128, 0), pink = (255, 153, 255).
Problemele convenției RGB sunt amestecul culorilor cu luminanța și perceperea sensibilă a
uniformităților.
379 Elemente de procesare a imaginilor în Python

(1,1,1) white
B
(0,1,1) Cyan (0,0,1) Blue

G
(1,0,1) Magenta
(1,1,0) Yellow

(0,0,0) Green

R
(0,0,0) Black (1,0,0) Red

Fig. 1 “Cubul” culorilor pentru modelul RGB

CMYK (cyan, magenta, yellow, black), utilizat în lumea imprimantelor color, este de
asemenea un spațiu de culoare, culorile fiind considerate proiectate pe un fond alb. Este
considerat un spațiu de culoare “substractiv” deoarece cerneala reține lumina care altfel ar fi
reflectată. De aici provine denumirea de “substractiv”, deoarece cerneala “extrage” culorile
roșu, verde și albastru din lumina albă: (white) minus (red) = cyan, (white) minus (green) =
magenta, (white) minus (blue) = yelow, figura 3.

Fig. 2 Modelul RGB (culori pe fond negru) Fig. 3 Modelul CMYK (culori pe fond alb)
HSV (Hue, Saturation, Value) memorează informația de culoare într-o reprezentare cilindrică
a punctelor RGB de culoare (fig. 4). Reprezentarea în spațiul HSV încearcă să descrie culorile
așa cum sunt percepute de ochiul uman. Valorile Hue sunt între 0 și 179, Saturation între 0 și
255 și Value între 0 și 255. Se utilizează în special în procesarea imaginii, în aplicații de
segmentare, deoarece accentuează contrastul. Se mai consideră: Hue = lungimea de undă
dominantă (dominant wavelength), Saturation = puritatea (gradul de culoare = purity), Value
= intensity.
380 Elemente de procesare a imaginilor în Python

Fig. 4 Modalități de reprezentare a spațiului culorilor HSV

Lab (Lightness, a = (green <---> red), b = (blue <--->yellow)). O imagine codată Lab are un
strat/canal pentru grayscale și împachetate trei straturi de culoare în două straturi. Alte
denumiri pentru Lab sunt CIEL, CIE Lab.
Observație. Spațiul culorilor Lab este destul de diferit de spațiul RGB. În RGB informația de
culoare conține și informația de strălucire. Pe de altă parte, spațiul Lab are canalul L care este
independent de informația de culoare, conținând numai strălucirea.
YCrCb (Y = luminanța, Cr = R – Y, Cb = B – Y). O imagine codată YCrCb separă luminanța
(un strat/canal) de informația de crominanță (două straturi/canale). O altă denumire a acestui
spațiu de culoare este YUV, care s-a păstrat doar ca extensie de fișier corespunzând
formatului.

Canalele (benzile) de culoare în spațiile de culori


Valorile din spațiile de culori sunt denumite “canale”, sau “benzi” de culori (uneori
“straturi”). De exemplu, în spațiul de culori RGB, Red, Green și Blue sunt canale ale imaginii.
Canalele pot fi diferite în funcție de modul (în terminologia uzuală) de reprezentare (spațiul
de culoare).
Culorile văzute într-o imagine sunt rezultatul compunerii culorilor specifice canalelor din
spațiul de culori.
Imaginile pot fi convertite dintr-un spațiu de culori în alt spațiu de culori utilizând funcții de
conversie. Pentru a converti o imagine din spațiul de culoare RGB în spațiul Grayscale se
utilizează modul “L”. De asemenea, există și alte moduri:
− ‘1’, modul 1 pixel (binar)
− ‘L’ (8-biți pixeli, black & white, “grayscale”)
− ‘P’ (8-biți pixeli, mapat pe oricare alt mod utilizând o paletă de culori
− ‘RGB’ (3x8-biți pixeli, “true color”)
− ‘RGBA’ (4x8-biți pixeli, “true color”, cu canalul A mască de transparență)
− ‘CMYK’ (4x8-biți pixeli, culori separate)
− ‘YCbCr’ (3x8-biți pixeli, “color video format”)
− ‘I’ (32-biți întreg cu semn pixeli)
− ‘F’ (32-biți floating point pixeli)
Exemplul 25.1 #Conversia unei imagini din RGB în Grayscale
from PIL import Image
381 Elemente de procesare a imaginilor în Python

img = Image.open("image.png")
grayscale = img.convert("L")
grayscale.show()

Adâncimea imaginii
Definiție. Prin “adâncimea imaginii”, sau “adâncimea culorii” (color depth) se înțelege
numărul de pixeli prin care se reprezintă culorile în imagine. De exemplu adâncimea de 1 bit
implică 2 culori, 2 biți implică 4 culori, iar 8 biți implică 256 culori.
Canalul alfa (transparency)
Transparența este o proprietate disponibilă doar în anumite formate grafice: GIF, PNG, BMP,
TIFF, TGA și JPG 2000 sub forma unui canal alfa, sau altfel denumit: “culoare transparentă”.
Celelalte formate grafice ignoră transparența, sau o tratează ca pe un “extra-format”.
Definiție. Canalul de transparență este format din pixeli cărora li se asociază culoarea
fundalului (screen background) pe care sunt plasați. În consecință, pixelii transparenți nu sunt
vizibili pe ecran.
Ca aplicații ale transparenței pot fi date următoarele exemple:
- O imagine care nu este dreptunghiulară poate fi completată pentru a dobândi forma
rectangulară dorită, cu pixeli transparenți. De asemenea, se poate completa o imagine cu
goluri (“găuri”).
- În afișarea unui text, se poate înlocui un caracter nedisponibil cu un simbol transparent, în
locul căruia se afișează culoarea backgroundului.
De asemenea, se poate utiliza și transparența parțială (de exemplu, în formatele PNG, sau
TIFF) pentru un canal alfa. În locul unui pixel care poate fi total transparent, sau total
netransparent, se poate utiliza un set de 254 niveluri de transparență parțială care să permită
trecerea treptată de la fundal (background) la vizibil (foreground).

25.2.3 Formate de imagine


GIF (Graphics Interchange Format) a fost introdus în 1987 pentru memorarea imaginilor
indexate pe 8 biți, permițând astfel o paletă de 256 de culori distincte pentru o imagine, alese
din spectrul RGB pe 24 biți. Formatul nu este potrivit pentru redarea fotografiilor, ci pentru
text și imagini simple (de ex., de tip siglă). Imaginile GIF sunt comprimate fără pierdere de
informație cu algoritmul Lempel-Ziv-Welch (LZV).
382 Elemente de procesare a imaginilor în Python

PNG (Portable Network Graphics) a fost dezvoltat pentru a înlocui și a îmbunătăți formatul
GIF. PNG suportă imagini tip rastru indexate, sau nu, bazate pe o paletă de culori RGB pe 24
biți, sau RGBA pe 32 de biți. De asemenea, suportă imagini grayscale cu sau fără canal alfa
(transparență) și full color non paletă bazat pe RGB sau RGBA.
PNG permite reprezentarea culorilor atât pe 8 cât și pe 16 biți, astfel:
- 24 biți true-color (8 biți pe canal);
- 48 biți true-color (16 biți pe canal);
- 64 biți – adăugarea canalului alfa (transparența).
Spre deosebire de GIF care permite animații, PNG este un format pentru o singură imagine.
Pentru animații a fost creată varianta de format APNG (Animated PNG).
Printre avantajele PNG sunt:
- portabilitatea (referitor la sisteme de operare și browsere);
- reprezentarea imaginilor true-color, inclusiv cu canal alfa, indexate color și grayscale;
- comprimarea fără pierderi, având în consecință o lizibilitatea mai bună a textului și
vizualizarea mai bună a detaliilor.
- transfer progresiv (în browser apare la început o imagine aproximativă care se îmbunătățește
în timp).
Dezavantajul cel mai important este dimensiunea mare a fișierelor pentru imaginile mari cu
foarte multe detalii.
JPEG (Joint Photografic Expert Group), sau JPG, este destinat reprezentării eficiente (cu o
rată mare de compresie), a imaginile tip rastru, mari, sau cu foarte multe detalii, de înaltă
rezoluție. Algoritmul de compresie este cu pierderi. În cazul mai multor etape de decodare –
recodare, pierderile de informație se acumulează, iar calitatea imaginii scade. Este foarte
utilizat în internet, deși comprimarea imaginii cu pierderi îngreunează uneori citirea textului.
Observație. Formatul JPEG nu suportă canalul de transparență și nici animația.
TIFF (Tagged Image File Format). Este un format de imagine rastru complex care poate
suporta aproape toate tipurile de reprezentări de imagine. Deși acceptă compresia cu pierderi,
este de obicei folosit ca format de imagine fără pierderi. Este des utilizat pentru operații în
vederea tipăririi.

25.3 Citirea și salvarea unei imagini


Citirea unei imagini în ecosistemul Python se poate face în mai multe moduri, utilizând
bibliotecile:
pillow, scikit-image, opencv, matplotlib, scipy (ndimage)
În mediul de lucru Python (IPython) se pot folosi funcții preluate din bibliotecile enumerate,
dacă au fost importate.
Exemple: Matplotlib
Matplotlib acceptă imagini png și jpg.
Exemplul 25.2 # citirea utilizând matplotlib
383 Elemente de procesare a imaginilor în Python

# spyder Output
# formatul de imagine = .png
import matplotlib.image as mpimage
image_filename = 'imag.jpg'
im = mpimage.imread(image_filename)
print(im.shape)
import matplotlib.pyplot as plt
plt.imshow(im)

Exemple: Pillow

Modulul Pillow oferă funcțiile open() și show() pentru a deschide și afișa direct pe display o
imagine. Pentru afișarea directă cu show(), Pillow face conversia imaginii în formatul png și
o stochează temporar într-un buffer, apoi o afișează (Metoda I). Dar, prin conversia în
formatul png este posibilă pierderea unor proprietăți ale imaginii, datorită restricțiilor
formatului png. Se recomandă ca această metodă să fie evitată.
Dacă se dorește evitarea afișării cu pierderi, se poate transforma imaginea într-un array
numpy, care apoi va fi afișat cu matplotlib.pyplot (Metoda II).
Exemplul 25.3 # afișarea directă a imaginilor png (Metoda I)
format png
from PIL import Image
img = Image.open("imag1.png")
img.show(img)

Exemplul 25.4 # citirea unei imagini utilizând pillow. (Metoda a II-a)


import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
imag_name ='imag2.jpg'
img=np.array(Image.open(imag_name))
print(img.shape)
plt.figure()
plt.imshow(img)

Exemplul 25.5 #citirea directă cu atributul split() (Metoda III)


import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
384 Elemente de procesare a imaginilor în Python

image_filename ='mozart.jpg'
mg=Image.open(image_filename)
plt.figure(1)
plt.subplot(141)
#imaginea originala
plt.imshow(mg)
#splitarea in trei imagini grayscale
r,g,b = mg.split()
plt.subplot(142)
plt.imshow(r)
plt.subplot(143)
plt.imshow(g)
plt.subplot(144)
plt.imshow(b)
#fuzionarea in alta ordine a celor trei canale
mg2 = Image.merge('RGB', (g,b,r))
plt.figure(2)
plt.imshow(mg2)

Se observă, în ultima imagine, pierderea de informație, față de imaginea originală.

Salvarea imaginii în Pillow se face cu metoda Image.save(), sub numele și formatul


specificat. Dacă nu se specifică explicit un format, salvarea se va face utilizând ca format
extensia numelui, în măsura în care e posibil (corespunde unui format acceptat).
Se poate utiliza în loc de un nume de fișier, un obiect fișier, în acest caz fiind obligatorie
precizarea formatului. Obiectul fișier se va deschide în modul binar și va implementa
metodele write, seek și tell.
Sintaxa: Image.save(fp, format=None, **params)
Exemplul 25.6 # citirea, redimensionarea și salvarea imaginii
noua_dim = (100, 50)
img = Image.open(r"primaria_pitesti.jpg")
print("Dimensiunea originala a imaginii")
print(img.size)
img.show(img)
385 Elemente de procesare a imaginilor în Python

# redimensionarea imaginii
r_img = img.resize(noua_dim, resample = Image.BILINEAR)
# redim_img => Destinatie
r_img.save("redim_img.jpg")
# Open noua imagine
img = Image.open(r"redim_img.jpg")
img.show(img)
print("\nNoua dimensiune a imaginii, dupa salvare")
print(img.size)

Output
Dimensiunea originala a imaginii
(1036, 562)

Noua dimensiune a imaginii, dupa salvare


(100, 50)

Exemple: Scipy – Ndimage

Instrucțiunea de citire este ndimage.imread, cu sintaxa:


scipy.ndimage.imread(fname, flatten=False, mode=None)

Rezultatul execuției instrucțiunii este un ndarray reprezentând imaginea citită.


name = numele fișierului sau obiectul fișier ce va fi citit
flatten = dacă este True, atunci straturile de culori se vor aplatiza într-un singur strat
grayscale
mode = șir reprezentând modul de reprezentare (conversie) al imaginii, de exemplu ‘RGB’.
Valorile șirului mode sunt cele cunoscute: (‘1’, ‘L’, ‘P’, ‘RGB’, ‘RGBA’, ‘CMYK’, ‘YcbCr’,
‘I’, ‘F’).
Se primește un suport limitat din partea bibliotecii PIL pentru modurile speciale , inclusiv
‘LA’ (‘L’ cu alpha), ‘RGBX’ (true color with padding) and ‘RGBa’ (true color cu alfa
premultiplicat).
Cînd se translatează o imagine color în Black & White, (mode ‘L’, ‘I’ or ‘F’), biblioteca
utilizează următoarea formulă de transformare a luminanței/luma ITU-R 601-2 :
L = R * 299/1000 + G * 587/1000 + B * 114/1000
Cînd flatten este True, imaginea este convertită utilizând modul ‘F’. Cînd mode nu este None
și flatten este True, imaginea este mai întâi convertită în acord cu mode, apoi rezultatul este
netezit (flattened) utilizând modul ‘F’.
Modulul ndimage vine cu un set de imagini care pot fi utilizate pentru exemplificare.
386 Elemente de procesare a imaginilor în Python

Exemplul 25.7 #citirea cu face() a unor imagini preparate pentru exemplificare


from scipy import misc
import matplotlib.pyplot as plt
img = misc.face()
plt.imshow(img)
plt.show()
#statistici
print(img.mean(), img.max(), img.min())

Output

110.16274388631184 255 0

Exemple: Scikit-image/skimage

Scikit-image (modulul skimage) este o colecție de algoritmi pentru procesarea imaginii, bazat
pe scipy.ndimage.
Se instalează cu comanda:
python -m pip install --user scikit-image

Exemplul 25.8 # citirea si salvarea unei imagini utilizând scikit-image (skimage)


from skimage import io
image_filename = 'img5.jpg'
skim = io.imread(image_filename)
print(skim.shape)
io.imshow(skim)
io.imsave('mozart.png', skim) #si conversie jpg -> png
#io.imshow(skim)

Output
(4000, 3000, 3)
387 Elemente de procesare a imaginilor în Python

Exemple: Opencv-Python

Instalarea openCV (CV2) se face cu comanda pip install opencv-python


Pentru citirea imaginilor se utilizează metoda cv2.imread(). Metoda încarcă o imagine
dintr-un fișier specificat. Dacă imaginea nu poate fi citită (lipsă fișier, lipsă permisiuni,
format invalid sau nesuportat), metoda întoarce o matrice vidă.
Sintaxa: cv2.imread(path, flag)
path: un șir reprezentând calea de încărcare a imaginii ce va fi citită (dacă nu se află în
directorul implicit de lucru).
flag: Indicator specificând modul în care imaginea va fi citită. Valoarea sa implicită este
cv2.IMREAD_COLOR
cv2.IMREAD_COLOR: Specifică încărcarea unei imagini color. Transparența culorii va fi
ignorată. Este indicatorul implicit (se poate înlocui cu 1).
cv2.IMREAD_GRAYSCALE: Specifică încărcarea unei imagini în modul grayscale (se
poate înlocui cu 0).
cv2.IMREAD_UNCHANGED: Specifică încărcarea unei imagini color, inclusiv canalul
alfa/transparența (se poate înlocui cu -1).
Pentru salvarea imaginilor se utilizează metoda cv2.imwrite.
Sintaxa: cv2.imwrite(filename, image)
filename: Un șir reprezentând numele fișierului care trebuie să includă un format de
imagine, ca: .jpg, .png, etc.
image: Imaginea care trebuie salvată.
Instrucțiunea întoarce true dacă operația s-a desfășurat cu succes.

Exemplul 25.9 #citirea și salvarea în opencv-python


import cv2
image_filename = 'intrare_primarie.jpg'
imag = cv2.imread(image_filename)
print(imag.shape)
cv2.imshow('image', imag)

Output
(385, 167, 3)

(550, 1100, 3)
388 Elemente de procesare a imaginilor în Python

25.4 Operații cu imagini în Pillow


25.4.1 Crearea unei imagini
O imagine nouă se poate crea cu metoda Image.new(), cu precizarea modului, dimensiunii și
a culorii.
Dimensiunea este stabilită prin tuplul (lățime, înălțime), unitatea de măsură fiind pixelul.
Culoarea este dată ca o valoare unică pentru imaginile cu o singură bandă și ca un tuplu
pentru imaginile multibandă. Se pot utiliza de asemenea numele culorilor. Dacă se omite
culoarea, imaginea este completată cu zero (negru). Culoarea poate fi neinițializată (None),
lucru util dacă se dorește desenarea, sau lipirea unei alte imagini.
Sintaxa:
Image.new(mode, size), sau Image.new(mode, size, color)
mode = modul (de exemplu RGB, etc.)
size = un tuplu în pixeli (width, height)
color = culoarea dorită (implicit negru). Este un număr întreg sau real pentru imaginile cu o
singură bandă. Este un tuplu pentru imaginile multibandă.
Se întoarce un obiect imagine.

Exemplul 25.9
from PIL import Image
img1 = Image.new(mode="RGB", size=(200, 200))
img1.show()
img2 = Image.new(mode = "RGB", size = (200, 200), color = (128, 155, 255))
img2.show()

Output

25.4.2 Afișarea dimensiunii (lățime, înălțime) și formatul unei imagini


Exemplul 25.10 #afișare dimensiune și format
img = Image.open(“mozart.jpg”)
print(img.size) #dimensiunea
print(img.format) #formatul

Output
(1100, 550)/
JPEG
389 Elemente de procesare a imaginilor în Python

25.4.3 Afișarea modului de culoare (spațiul de culoare) al unei imagini


Atributul “mod” informează tipul și adâncimea de culoare (numărul de pixeli). Modurile sunt
următoarele:
1 = 1 pixel, black & white
L = 8 biți, greyscale
P = 8 bixeli, utilizează paletă de culori (indexare)
RGB = 3x8 pixeli, true color
RGBA = 4x8 pixeli, true color cu mască de transparență
Exemplul 25.11 #afișare spațiul de culoare
img = Image.open(“mozart.jpg”)
print(img.mode)

Output
RGB

25.4.4 Rotirea unei imagini


Pentru rotirea unei imagini cu un anumit unghi, în jurul centrului său, în sens invers acelor de
ceasornic, se utilizează metoda rotate(). După rotirea imaginii, secțiunile care nu mai au
pixeli se vor completa cu negru (pentru imagii non-alfa), sau vor deveni transparente (pentru
imaginile care suportă transparența).
Sintaxa:
new_object = PIL.Image.Image.rotate(image_object, angle, resample=0,
expand=0)
sau:
new_object = image_object.rotate(angle, resample=0, expand=0)

Exemplul 25.12 #rotirea unei imagini


from PIL import Image
import PIL
im1 = Image.open(r'intrare_primarie.jpg')
# rotire cu 45 grade ccw (counter clockwise,
# sens invers ceas)
im1 = im1.rotate(45, PIL.Image.NEAREST, expand =
1)
im1.show()

25.4.5 Răsturnarea unei imagini


Imaginea va fi răsturnată, sau rotită cu câte 90 grade
Sintaxa: transpose(degree)
Pentru răsturnarea imaginii se utilizează constantele FLIP_TOP_BOTTOM și
FLIP_LEFT_RIGHT
Exemplul 25.13 #Răsturnarea unei imagini
from PIL import Image
original_img = Image.open('intrare_primarie.jpg')
390 Elemente de procesare a imaginilor în Python

# Transpunerea/răsturnarea vertical a unei imagini


vertical_img = original_img.transpose(method=Image.FLIP_TOP_BOTTOM)
vertical_img.save("mozart_vertical.jpg")
vertical_img.show()
original_img.close()
vertical_img.close()

Output

25.4.6 Redimensionarea unei imagini

Metoda Image.resize() întoarce o copie redimensionată a imaginii. Procesul de


redimensionare are loc pe baza interpolării, modificându-se calitatea imaginii, fie la mărire
(upscaling), fie la micșorare (downscaling). Din acest motiv, metoda resize() trebuie
folosită cu atenție, utilizînd valori potrivite pentru parametrul resample.

Sintaxa: Image.resize(size, resample=0)


Parametri:
- size: este un tuplu (width, heigh) prin care se precizează lățimea și înălțimea imaginii
noi
- resample: este un filtru de reeșantionare opțional. Poate fi unul din următorii parametri:
PIL.Image.NEAREST (utilizează algoritmul nearest neighbour) – implicit;
PIL.Image.BILINEAR (utilizează algoritmul interpolării liniare);
PIL.Image,BICUBIC (utilizează algoritmul de interpolare spline cubic);
PIL.Image.LANCZOS (un filtru de subeșantionare de înaltă calitate).
Exemplul 25.14 #redimensionarea unei imagini
from PIL import Image
im = Image.open(r'img_parc3.jpg')
# Dimensiunea originală in pixeli
width, height = im.size
print('original:', 'latimea:', width, 'inaltimea:', height)
# Coordonatele de decupare
left = 1000
top = height / 5
right = 1600
bottom = 3 * height / 5
# Decuparea imaginii cu dimensiunile de mai sus
391 Elemente de procesare a imaginilor în Python

# fara schimbarea imaginii originale


im1 = im.crop((left, top, right, bottom))
newsize = (250, 350)
im1 = im1.resize(newsize)
im1.show()

Output
original: latimea: 4000 inaltimea: 3000

25.4.7 Compunerea și fuziunea imaginilor


Funcția Image.merge() este utilizată pentru a unifica (fuziona) un set de imagini de tip
multibandă (multicanal) într-o nouă imagine multibandă. Întoarce un obiect imagine (în
memorie).
Observație. Pentru divizarea unei imagini în benzi individuale se utilizează Image.split().
Sintaxa: PIL.Image.merge(mode, bands)

Parametri:
mode = reprezintă modul utilizat pentru imaginea de ieșire.
bands = este o secvență conținând câte o singură imagine “single-band” pentru fiecare bandă
din imaginea de ieșire. Toate benzile trebuie să aibă aceeași dimensiune.
Exemplul 25.15 #fuzionarea imaginilor, imaginile fiind chiar benzile de culoare ale
aceleiași imagini
# se importă clasa Image
from PIL import Image
# se creează obiectul
image = Image.open(r"parc_centru.jpg")
image.load()
image.show()
# Se divide imaginea originala in benzi individuale
r, g, b, = image.split()
r.show()
g.show()
b.show()
# fuziunea in diverse ordine a canalelor.
im1 = Image.merge('RGB', (g, b, r))
im1.show()
392 Elemente de procesare a imaginilor în Python

im2 = Image.merge('RGB', (b, g, r))


im2.show()
im3 = Image.merge('RGB', (r, b, g))
im3.show()

Imaginea originală

Banda r Banda g Banda b

Fuziunea g,b,r Fuziunea b,g,r Fuziunea r,g,b


Se observă în imaginile de fuziune diferența față de original, deși rezultatul ar fi trebuit să fie
identic. Diferența se datorează pierderilor de informație în urma procesării, ca urmare a
conversiilor interne în formatul png, imaginea originală fiind de tip jpg.
393 Elemente de procesare a imaginilor în Python

Pentru compunerea imaginilor sunt utilizate frecvent funcțiile: composite(), blend(),


copy() și paste().

Funcția PIL.Image.composite() creează o imagine compusă utilizând o mască de


transparență. Masca este o altă imagine care rămâne transparentă când se compune imaginea.
Imaginile, inclusiv masca, trebuie să aibă aceeași dimensiune.
Sintaxa: PIL.Image.composite(imagine1, imagine2, masca)

Parametri:
imagine1 = prima imagine (destinația).
imagine2 = a doua imagine din compunere, cu aceeași dimensiune ca prima.
masca = o imagine mască. Aceasta poate avea modul I, L, sau RGBA și trebuie să fie de
aceeași dimensiune ca primele două.
Funcția Image.blend() “amestecă” două imagini în mod ponderat.
Sintaxa: PIL.Image.blend(imagine1, imagine2, alfa)

Parametri:
imagine1 = prima imagine (destinația).
imagine2 = a doua imagine din compunere, cu aceeași dimensiune ca prima.
alfa = factorul de interpolare (ponderea imaginilor în “amestec”). Pentru alfa = 0 se
întoarce o copie a primei imagini, pentru alfa = 1 se întoarce o copie a celei de a doua
imagini.
Funcțiile Image.copy() și Image.paste() permit ca o copie a unei imagini să se poată lipi
pe o altă imagine. Dacă modurile celor două imagini nu se potrivesc, atunci imaginea de lipit
se va converti in modul imaginii destinație. În loc de o imagine, sursa poate fi un întreg sau
un tuplu conținând valori de pixeli, asfel atribuind regiunii de lipit o culoare dată.
Sintaxa: Image.paste(imagine, cadrul = None, masca = None)

Parametri:
imagine() = imaginea destinație pentru imaginea sursă obținută cu copy().
cadrul = este None (echivalent cu (0,0)), sau un tuplu dublu precizând colțul stânga sus (sau
cvadruplu, precizând cele patru colțuri), al regiunii în care se va lipi imaginea sursă.
masca = dacă este prezentă, lipirea se va efectua doar în regiunea corespunzătoare măștii. Se
pot utiliza imagini având modurile “1”, “L”, “LA”, “RGBA” or “RGBa”, cu observația că,
dacă este prezentă banda de transparență alfa, aceasta va fi utilizată drept mască. Dacă masca
este 255, imaginea va fi copiată așa cum este, dacă masca = 0, valorile pixelilor nu se
modifică. Dacă valoarea este între 0 și 1, se va face amestecul celor două imagini, inclusiv
canalele alfa dacă sunt prezente.

25.4.8 Decuparea unei imagini


Decuparea (cropping, crop) este procesul de selectare și extragere a unei părți dintr-o
imagine. Metoda crop() permite decuparea unei porțiuni dreptunghiulare dintr-o imagine.
Sintaxa: PIL.crop(box = None)
394 Elemente de procesare a imaginilor în Python

Parametri: box este un 4-tuplu definind colțurile (left, upper, right, bottom/lower) în
coordonate în pixeli ((x1, y1, x2, y2), unde x1 < x2, y1 <y2) .

Exemplul 25.16 #decuparea imaginilor


from PIL import Image
# Se deschide imaginea in modul RGB
im = Image.open(r"biserica1.jpg")
im.show()
#dimensiunile imaginii originale
latimea, inaltimea = im.size
print(latimea, inaltimea)
# Precizarea coordonatelor (punctelor) de decupare
left = 250 #x1
top = inaltimea / 4 #y1
right = 700 #x2
bottom = 4 * inaltimea / 4 #y2
# Decuparea se face fara a afecta dimensiunea imaginii originale
im1 = im.crop((left, top, right, bottom)) #(x1, y1, x2, y2)
# Afisarea zonei decupate
im1.show()
395 Elemente de procesare a imaginilor în Python

25.5 Întrebări, exerciții și probleme


25.4.1 Care este diferența dintre scikit-image și scikit-learn ?
R25.4.1 Scikit-learn este o bine-cunoscută bibliotecă de machine learning pentru Python
(prescurtarea Scikit vine din Scipy Toolkit). Este o bibliotecă open-source care utilizează
Numpy pentru algoritmi performanți pentru operații de algebră și masive de date. Se
integrează foarte bine cu alte biblioteci Python: Matplotlib, Numpy, Pandas, SciPy, etc.
Scikit-learn conține proceduri și funcții pentru algoritmi de clasificare, regresie, clusterring,
etc.
25.4.2 Să se realizeze conversia unei imagini din format RGB în formatele RGBA, L și 1.
25.4.2 Se utilizează funcția convert().
from PIL import Image, ImageFilter
image = Image.open(r"\...\casa_cultura.jpg")
image.load()
image.show()
benzi=image.getbands()
print("Benzi RGB:", benzi)

image1 = image.convert("RGBA")
image1.show()
benzi1=image1.getbands()
print("Benzi RGBA:", benzi1)

image2 = image.convert("L")
image2.show()
benzi2=image2.getbands()
print("Benzi L:", benzi2)

image3 = image.convert("1")
image3.show()
benzi3=image3.getbands()
print("Benzi 1:", benzi3)
Output

Benzi: ('R', 'G', 'B') Benzi:('R','G', 'B', 'A') Benzi: ('L',) Benzi: ('1',)

25.4.3 În câte moduri se pot detecta muchiile obiectelor din imagini utilizând Python?
R25.4.3 Detectarea muchiilor este o tehnică prin care se determină marginile obiectelor în
cadrul imaginilor. Baza tehnicii este detectarea discontinuității în luminanță.
396 Elemente de procesare a imaginilor în Python

Detectarea muchiilor este utilă deoarece foarte multă informație despre forma obiectelor și
conținutul imaginii se află în muchiile obiectelor din imagine.
Pentru exemplificare, se arată cum funcționează unul dintre cei mai cunoscuți algoritmi de
detectare a muchiilor: algoritmul Sobel. Acest operator este construit din două nuclee simple
de convoluție de 3 * 3 pixeli. Al doilea nucleu Ny este versiunea rotită cu 90o a nucleului Nx.
Ca urmare a aplicării procesului de convoluție se obțin două versiuni ale imaginii:
1 0 −1 1 2 1
𝐺𝑥 = [2 0 −2] × 𝑀𝑎𝑡𝑟𝑖𝑐𝑒𝑎 𝑖𝑚𝑎𝑔𝑖𝑛𝑒 𝐺𝑦 = [ 0 0 0 ] × 𝑀𝑎𝑡𝑟𝑖𝑐𝑒𝑎 𝑖𝑚𝑎𝑔𝑖𝑛𝑒
1 0 −1 −1 −2 −1

Valorile pixelilor din cele două imagini se compun pentru obținerea valorii finale ale
pixelilor, corespunzător gradientului de convoluție 2D:
𝐺 = 𝑠𝑞𝑟𝑡(𝐺𝑥2 + 𝐺𝑦2 )
Deoarece în Python sunt disponibile mai multe biblioteci și aplicații de procesarea imaginilor,
numărul de metode și variante de cod scrise pentru detectarea muchiilor este foarte mare, de
exemplu: Canny, Sobel, Laplacian, Scharr, Prewitt, Roberts. În continuare se prezintă doar
câteva dintr-un număr foarte mare de posibilități.
Metoda I
Algoritmul constă în convoluția imaginii cu un nucleu special de forma unei matrice.
Matricea utilizată de funcția FIND_EDGES din Pillow este următoarea:
( -1, -1, -1,
-1, 8, -1,
-1, -1, -1
)
from PIL import Image
from PIL import ImageFilter
image = Image.open("E:\...\...\...\primarie.jpg") #obiectul imagine
#aplicarea metodei FIND_EDGES de detectare a muchiilor
imageWithEdges = image.filter(ImageFilter.FIND_EDGES)
image.show() #afisare imagine originala
imageWithEdges.show() #afisare imagine cu muchiile detectate
# Detecția muchiile cu FIND_EDGES (Pillow)

Metoda a II-a
import cv2
from skimage import feature, filters, io
397 Elemente de procesare a imaginilor în Python

import matplotlib.pyplot as plt


img = cv2.imread("E:\A__MOZAIC_cartea-04-05-2022\CARTE_NOUA_LimbEcoPython-
04-05-2022\Cap25-ProcImag\IMAGINI DE TEST\primarie.jpg", 0)
edges_canny = feature.canny(img) # Metoda Canny
edges_sobel = filters.sobel(img) # Metoda Sobel
#edges_laplace = filters.laplace(img) # Metoda Laplacian
#edges_scharr = filters.scharr(img) # Metoda Scharr
#edges_prewitt = filters.prewitt(img) # Metoda Prewitt
#edges_roberts = filters.roberts(img) # Metoda Roberts
print(edges_canny.shape)
# io.imshow(edges_canny)
plt.figure(1)
plt.imshow(edges_canny)
plt.figure(2)
plt.imshow(edges_sobel)
plt.show()

25.4.4 Să se lipească o imagine conținând un text pe o altă imagine, precizând poziția.


25.4.4 Se utilizează funcția paste().
# se importă clasa Image
from PIL import Image
# se creează obiectul
image = Image.open("img_parc.jpg")
image.load()
image.show()
image1 = Image.open("text.jpg")
image2=image1.copy()
image2.show()
image.paste(image2, (1000, 1000))
image.show()

Imaginea originală Imaginea lipită Imaginea rezultată


Eugen Diaconescu Limbajul și ecosistemul de programare Python

Capitolul 26

Administrarea ecosistemului Python


26.1 Instalarea Python utilizând distribuția Anaconda
Informațiile prezentate în acest capitol pot deveni inactuale deoarece întreg sistemul Python
cu toate legăturile sale se află într-o evoluție destul de dinamică.
Python poate fi instalat individual (prezentarea la începutul cărții) sau prin intermediul unei
distribuții, cea mai cunoscută fiind Anaconda. În acest ultim caz, odată cu Python mai sunt
instalate și alte aplicații care sunt fie componente ale ecosistemului Python, fie sunt în strânsă
legătură cu acesta.
399 Administrarea ecosistemului Python

Dintre componentele afișate, unele pot fi lansate direct (launch), iar restul trebuie instalate. De
asemenea, mai pot fi adăgate și alte componente utile dezvoltării aplicațiilor.
Denumirile componentelor vizibile în cadrul de lucru de mai sus, sunt:
- CMD.exe (prompt) – Terminalul linie de comandă al Windowsului, lansat de Anaconda.
- Datalore – Analiză de date online cu asistență inteligentă la codare de la JetBrain.
- IBM Watson Studio Cloud – Instrument profesional pentru IA, utilizat in special pentru
cercetare științifică.
- JupyterLab – instrumentul principal al proiectului Jupiter.
- Jupyter Notebook – varianta inițială (încă utilizată) a proiectului Jupiter.
- PowerShell Prompt – este un terminal PowerShell al sistemului Windows, lansat de
Anaconda.
- QtConsole – Este o consolă compatibilă Qt, urmașă a vechii console IPython (deși numele
sunt foarte similare, Python și IPython sunt lucruri complet diferite. IPython a fost un terminal
în linie de comandă, interactiv pentru Python. I, din IPython provine de la “Interactiv”)).
- Spyder – Scientific Python Development EnviRonment. Mediu de dezvoltare utilizând
Python adaptat pentru cercetarea științifică.
- Glueviz – Este o bibliotecă Python open-sursă pentru analiza legăturilor dintre seturile de
date înrudite.
- Orange3 – Este un sistem open-sursă de vizualizare a datelor și prelucrări de tip machine
learning și data mining. Utilizează programarea vizuală.
- PyCharm Professional – System de dezvoltare avansat pentru utilizarea Python și a altor
procesoare software, inclusiv din ecosistemul Python.
- RStudio – Este un mediu de integrare de dezvoltare (IDE) pentru limbajul R, dedicat
calculului statistic și reprezentărilor sale grafice. RStudio se poate rula fie în browser, fie
desktop.
400 Administrarea ecosistemului Python

26.2 Configurarea Python


După instalare, poate fi necesară configurarea. În general, configurarea se referă la stabilirea
sau citirea unor variabile ale sistemului de operare sau ale mediului de lucru Python, astfel încât
să se creeze posibilitatea găsirii directoarelor și fișierelor conținând resursele Python (procesor,
biblioteci, etc.), sau să se simplifice lucrul cu acestea.
Configurarea Python depinde și diferă în funcție de sistemul de operare (Linux/Ubuntu, sau
Windows, etc.).
Observație. Setarea greșită, sau modificarea neglijentă în timpul lucrului a variabilelor de
mediu poate conduce la erori nedorite sau chiar la compromiterea aplicației.
Asupra variabilelor de mediu se poate acționa atât prin comenzi specifice, sau instrumente ale
sistemului de operare, cât și din mediul Python, prin intermediul comenzilor sale dispinibile în
unele din modulele sale (os, sys, pathlib).
În funcție de sistemul de operare, versiunea Python utilizată și de asemenea și de contextul de
lucru creat (prin utilitarele și mediile de dezvoltare IDE utilizate), pot apărea diferite variabile
de mediu, printre care cele mai importante sunt (se obțin cu comanda print(os.environ)) în
Windows : HOME, HOMEPATH, LOCALAPPDATA, OS, PATH, PATHEXT,
PROGRAMDATA, PROGRAMFILES, PROGRAMFILES(X86), PUBLIC, PYTHONPATH,
USERNAME, etc. Fiecare variabilă conține o listă cu una sau mai multe căi de acces la diverse
foldere.
În cazul Python prezintă interes variabilele:
- PATH (sau path) – conține calea de căutare în sistem a interpretorului Python.
- PYTHONPATH – conține calea de căutare a modulelor Python (pentru importuri).
- Alte variabile în funcție de modul de organizare al proiectului/aplicației.
PATH. Variabila sys.path conține lista de directoare în care sistemul de operare caută
executabilele, când acestea sunt invocate fără precizarea căii. În mod normal ar trebui să
conțină calea unde se află fișierele python (Linux), sau python.exe (Windows). PATH nu este
parcurs dacă se lansează Python din directorul în care a fost instalat, sau prin intermediul IDE-
ului său.
Se poate obține conținutul variabilei PATH prin comanda:
>>>print(os.environ['PATH'])
C:\Program Files\Python310\Scripts\;C:\Program Files\Python310\;. . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

PYTHONPATH. Este o variabilă de mediu cu rol similar cu PATH, fiind consultat de


interpretorul Python atunci când caută un modul pentru import . Căile din listă sunt separate de
(:) în Linux și (;) în Windows. Conținutul său este adăugat la sys.path când aceasta este
importată.
Se poate obține conținutul variabilei PYTHONPATH prin comanda:
print(os.environ['PYTHONPATH'])
. . . . . . . . . . . . . . . . .

Observație. Cunoașterea și manipularea corectă a variabilelor de mediu prin intermediul


modulelor specializate os, sys, pathlib, etc., conferă flexibilitate și simplifică efortul
de realizare a proiectelor în Python și ecosistemul Python.
401 Administrarea ecosistemului Python

(Se reamintesc comenzile în sistemele de operare Linux, respectiv Windows de modificare a


unei variabile de mediu:
export PYTHONPATH=/cale/bib/srsapp:$PYTHONPATH

set PYTHONPATH=%PYTHONPATH%;C:\cale\bib\srsapp
)

Linia shebang
Caracterele “#!” sunt denumite în jargonul programatorilor ”shebang” sau ”hashbang”. Ele sunt
folosite în general pe prima line a unui program pentru a preciza calea către procesorul software
ce trebuie lansat în execuție.
În cazul Python , linia completă #!/usr/bin/python3 (sau #!/usr/bin/env python3)
indică adresa de lansare a interpretorului Python în sistemul de operare Linux (Ubuntu).
Fișierul care conține linia este considerat un script, iar interpretorul îl va executa.
În Windows linia nu are efect, este interpretată drept un comentariu.

26.3. Utilizarea pachetelor


26.3.1 Pachete Python
Python dispune de două tipuri de pachete:
system packages, pachete care sunt parte ale sistemului standard de biblioteci Python.
site packages, biblioteci “third party”, aplicații importante care dezvoltă ecosistemul Python.
În general nu prezintă prea mare importanță locația din calculator unde se instalează pachetele
de sistem, dar nu este indiferentă locația în care se instalează pachetele utilitare produse de
diverși, având legături (dependențe) de pachetele sistem ale Python, deoarece pot apărea
interferențe nedorite.
Locația bibliotecilor sistem se poate afla cu comenzile următoare, din consola Python:
>>> import sys
>>> sys.prefix
sys.prefix
'C:\\Program Files\\Python310'

26.3.2 Unelte de instalare a pachetelor Python


Programe utilitare
Pentru instalarea diverselor componente ale ecosistemului Python se pot utiliza uneltele
software cunoscute sub numele “Package Managers”.
Dacă obiectivul este instalarea unor componente de sistem “low level” se utilizează: Distutils,
Setuptools (considerat un standard) , Distribute (derivat din Setuptools) și Distutils2.
Pentru instalarea, de exemplu, de biblioteci având legături/dependențe cu Python se pot folosi
administratori de pachete “high level”, dintre care cei mai cunoscuți sunt pip și conda. Altele
mai sunt Chocolatey, easy_install, etc.
402 Administrarea ecosistemului Python

Pachetele Python pot fi găsite și instalate de administratorii de pachete (packet managers) de


pe site-urile Source Distributions (sdist, format sursă) , Wheels (format pre-built, mai rapid),
ambele prezente în PyPI (Python Package Index).
pip este cel mai cunoscut administrator de pachete (din 2008), instalând din PyPI prin
specificarea unui nume de pachet (numele softului urmat optional de versiunea sa). Este
considerat “standard package manager” pentru Python.
conda (vine din distribuția Anaconda), este managerul de pachete recomandat în special pentru
începători, deoarece oferă tot mediul, inclusiv dependențele bibliotecilor într-o singură
instalare cu sandbox, inclusiv Python și pip. De remarcat utilizarea Conda ca manager de
pachete, mediu și dependențe și pentru alte limbaje.
pipenv este un alt utilitar care unifică administrarea pachetelor și a mediului virtual.

26.3.4 Chestiuni legate de organizarea spațiului de lucru


Modulele sys și os oferă o mulțime de funcții built-in care pot fi utilizate pentru efectuarea
unor operații în legătură cu sistemul de operare utilizat.
EXEMPLE
#funcții sys
a)
#sys.path contine o lista de directoare in care Python va cauta modulele
#importate
>>>import sys
>>>print(sys.path) #afisare continut variabila cale de acces Python
['', 'C:\\Program Files\\Python310\\Lib\\idlelib', 'C:\\Program
Files\\Python310\\python310.zip', . . . . . . . . . . . . . .

b)
>>>sys.version #afisare versiune Python
'3.10.1 (tags/v3.10.1:2cd268a, Dec 6 2021, 19:10:37) [MSC v.1929 64 bit
(AMD64)]'

c)
#sys. version_info este un tuplu indicând numarul versiunii. Tuplul
#contine cele cinci componente ale numarului versiunii: major, minor,
#micro, releaselevel și serial.
>>>sys.version_info
sys.version_info(major=3, minor=10, micro=1, releaselevel='final',
serial=0)

d)
>>>sys.int_info #informatii despre intregi in Python: max, min, etc.
sys.int_info(bits_per_digit=30, sizeof_digit=4)

>>>sys.float_info #informatii despre float in Python: max, min, etc.


sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308,
min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15,
mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)

e)
>>>sys.implementation #informatii despre implementarea Python
namespace(name='cpython', cache_tag='cpython-310',
version=sys.version_info(major=3, minor=10, micro=1, releaselevel='final',
serial=0), hexversion=50987504)
403 Administrarea ecosistemului Python

f)
>>>sys.byteorder #ordinea octetilor: little endian, big endian
'little'

g)
>>>sys.getsizeof(1234) #dimensiunea in octeți a reprezentarii numarului
28
sys.getsizeof("abcdef") #dimensiunea in octeți a reprezentarii sirului
55

h)
#sys.argv
import sys
print("Acesta este numele programului:", sys.argv[0])
print("Lista de argumente:", str(sys.argv))

Output
Acesta este numele programului: E:/1Python_Work/Test2/ex_tst_argv.py
Lista de argumente: ['E:/1Python_Work/Test2/ex_tst_argv.py']

#funcții os
i)
>>> os.getcwd() #dupa lansare Python
'C:\\Program Files\\Python310'
>>>os.getcwd() #in cadrul sesiunii de lucru
'E:\\1Python_Work\\Test2'
j)
>>os.listdir() #dupa lansare Python
['DLLs', 'Doc', 'include', 'Lib', 'libs', 'LICENSE.txt', 'NEWS.txt',
'python.exe', 'python.pdb', 'python3.dll', 'python310.dll',
'python310.pdb', 'pythonw.exe', 'pythonw.pdb', 'Scripts', 'tcl', 'temp',
'Tools', 'vcruntime140.dll', 'vcruntime140_1.dll']
>>> os.listdir() #in cadrul sesiunii de lucru
['Test3', 'z.py']
k)
>>>os.getpid()
6584
l)
>>>print(os.environ) #Listeaza continutul variabilelor de mediu
environ({'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA':
'C:\\Users\\USER\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program
Files\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files
(x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common
. . . . . . . . . . . . . . . . . . . . . . . . . .

26.4 Administrarea dependențelor aplicațiilor


26.4.1 Conceptul de mediu virtual
Definiție. În Python, un mediu virtual este un mediu izolat care permite instalarea doar a
pachetelor necesare pentru utilizare de către o anumită aplicație, în loc de a instala toate
pachetele.
404 Administrarea ecosistemului Python

Utilitatea mediilor virtuale poate fi ilustrată printr-un exemplu simplu. Se presupune ca


Aplicația1 folosește biblioteca Bib.(versiunea 1), iar Aplicația2 folosește biblioteca
Bib.(versiunea 2). Dacă bibliotecile sunt instalate în același locație comună
/<python_cale>/lib (sau oricare alt nume de locație), se ajunge în situația că una din aplicații
este “upgradată” în mod nedorit.
Odată ce o aplicație a fost instalată și funcționează cu o versiune a procesorului software și un
anumit set de biblioteci având versiuni precizate, orice modificare a acestora poate face ca
aplicația să se oprească sau să producă rezultate nedorite. Se mai spune că o aplicație
funcționează corect doar cu un set stabilit de dependențe funcționale (procesoare software,
biblioteci, căi de acces, configurații, setări, etc.)
În toate situațiile, un mediu virtual poate crește siguranța executării aplicației prin izolarea
tuturor componentelor sale software de mediul global de lucru sau de alte medii virtuale.
Bibliotecile instalate, căile și setările vor fi utilizate de aplicație doar în cadrul strict al acelui
mediu virtual.
Altfel spus, mediile virtuale separă dependențele proiectelor, ajutând la evitarea conflictelor de
versiune între pachete și componentele run-time ale Python.
Observație. Un mediu virtual poate fi asimilat cu un “sandbox”, adică un mediu izolat (replică
a unui mediu de operare), unde se pot rula, observa și aprecia cum se comportă programele la
execuție. În general, un sandbox are scop testarea unei aplicații suspecte de conținut malware,
astfel încât să se evite alterarea sau contaminarea întregului sistem, inclusiv rețeaua. Dacă ceva
neașteptat sau nedorit se întâmplă, va fi afectat doar sandbox-ul.

23.4.2 Crearea unui mediu virtual


Pentru creearea unor medii virtuale, în Python sunt disponibile în mod curent două unelte
software (cel mai cunoscute și utilizate):
- venv. Este disponibil implicit în versiunile Python, începând cu 3.3+ (este un pachet mai nou
ca virtualenv, dar se găsește acum în biblioteca standard). Acesta instalează pip și setuptools
în versiunile Python, începând cu 3.4. Deși oferă numai un set din facilitățile virtualenv, sub
alte aspecte este actualizat, sau superior și se recomandă să fie utilizat.
- virtualenv. Se instalează separat (acceptă și versiuni mai vechi de Python). Suportă versiunile
Python începând cu 2.7+ și versiunile 3.3+. De asemenea instalează automat pip, setuptools și
wheel în spațiul virtual creat, în funcție de versiunea Python potivită. Este cel mai utilizat, dar
se recomandă să fie evitat.
De asemenea, mai sunt disponibile pyvenv, pyvirtualenvwrapper (depreciate), pipenv
(administrează automat mai multe medii virtuale separate) și pyenv, care oferă facilități
suplimentare sau combină diferite atribute ale mediilor virtuale.
Observație. După ce a fost instalat un mediu virtual, acesta trebuie activat.
Observație. După activarea mediului virtual, se semnalizează această stare prin modificarea
prompterului Python cu numele directorului mediului virtual.
Comenzile necesare pentru activare sunt:
Pentru venv:
>>>python3 -m venv <DIR>
>>>source <DIR>/bin/activate
405 Administrarea ecosistemului Python

Pentru virtualenv:
>>>virtualenv <DIR>
>>>source <DIR>/bin/activate

Observație. Pentru dezactivarea mediului virtual se utlizează comanda:


>>> deactivate

Observație. Se mai spune că un mediu virtual este doar “un mediu Python într-un folder”:

26.5 Întrebări, exerciții și probleme


26.5.1 Să se vizualizeze la consolă toate funcțiile modulelor sys, os și pathlib.
R26.5.1
Se utilizează comenzile dir(os), dir(sys) și dir(pathlib).
26.5.2 Cum se poate face adăugarea unui folder la variabila căilor de acces?
R26.5.2
Metoda 1
import sys
#sys.path.insert(0, 'adresa completă a directorului de adăugat(incluzând
calea)')
print(sys.path)
sys.path.insert(0, 'E:\1Python_Work\Test3')
print(sys.path)

Output
['E:/A_Python-work', 'C:\\Program Files\\Python310\\Lib\\idlelib', . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
['E:\1Python_Work\\Test3', 'E:/A_Python-work', . . . . . . . . . . . . . .
. . . . . . . . . . . .
Metoda 2
import sys
sys path.append('adresa completă a directorului de adăugat(incluzând
calea)')

26.5.3 Cum se poate face ștergerea unui folder din variabila căilor de acces ?
R26.5.3
#stergere temporara !
print(sys.path)
sys.path.remove('E:/1Python_Work/Test3')
print(sys.path)

Output
['E:/1Python_Work/Test3', 'E:/A_Python-work', 'C:\\Program
Files\\Python310\\Lib\\idlelib', . . . . . . . . .

['E:/A_Python-work', 'C:\\Program Files\\Python310\\Lib\\idlelib', . . . .


. . . . . . . . . . . . . . . . .
406 Administrarea ecosistemului Python

26.5.3 Cum se poate afla calea sau numele folderului de lucru curent ?
#A numele folderului unde se află python.exe
>>>import sys
>>>print (sys.executable)
C:\Program Files\Python310\pythonw.exe
#B numele folderului de lucru curent
#metoda 1
>>>import os
>>>print(os.getcwd())
E:\A_Python_work

#metoda 2
>>>import pathlib
>>>print(pathlib.Path(__name__).parent.resolve())
E:\A_Python_work
#Metoda 3
>>>import os
>>>print(os.path.abspath(__name__))
E:\A_Python_work\__main__
#Metoda 4
>>>print(os.path.dirname(__file__))
E:/A_Python-work

26.5.4 Cum se face schimbarea directorului Python de lucru curent în Linux/Ubuntu:


R2.5.4
>>>import os
>>>os.chdir(folder_nou)

26.5.5 Să se măsoare durata de execuțuie a unui program Python, de exemplu calculul


factorialului numerelor 10, 100 și 1000.
R26.5.5 Se utilizează modulul time.
import time
startTime = time.time()
#...................................................
# Programul a carui durata de executie se determina
#...................................................
def factorial(x):
"""Functia calculeaza
factorialul unui numar intreg"""
if x == 1:
return 1
else:
return (x * factorial(x-1))
nr = 1000
print("Factorialul lui ", nr, "este:\n", factorial(nr))
#.......................................................
endTime = time.time()
totalTime = endTime - startTime
print("Timpul total de executie este = ", totalTime)

Output
Factorialul lui 10 este:
407 Administrarea ecosistemului Python

3628800
Timpul total de executie este = 0.04639744758605957
Factorialul lui 100 este:
933262154439441526816992388562667004907159682643816214685929638952175999932
299156089414639761565182862536979208272237582511852109168640000000000000000
00000000
Timpul total de executie este = 0.04635953903198242
Factorialul lui 1000 este:
402387260077093773543702433923003985719374864210714632543799910429938512398
62902059204420848696940480047998861019719605. . . . . . . . . . . . . .
Timpul total de executie este = 0.1249992847442627

26.5.6 Să se scrie, în sistemul de operare Linux, un script Python pentru a executa un program
C/C++.
R26.5.6 Soluția problemei constă în utilizarea subproceselor. Se scrie mai întâi un program în
C/C++, care se verifică și se execută corect.
#include <iostream>
using namespace std;
int main(){
cout<<"Hello World\n";
return 1;
}

Scriptul Python de executare a programului C/C++ va arăta astfel:


import subprocess
subprocess.call(["g++", "hello_world.cpp"])
tmp=subprocess.call("./a.out")
print "printing result"
print tmp

Output
$ python main.py
Hello World
printing result
1
Eugen Diaconescu Limbajul și ecosistemul de programare Python

GLOSAR
Concepte și termeni utili pentru începători (în engleză și română)
A
Algoritm = un set de reguli, bine-definite, pentru soluționarea unei probleme, într-un număr finit de
pași.
Alias = “un alt nume”. O locație de memorie (sau în general o variabilă) poate fi referită de mai mulți
identificatori diferiți, denumiți alias. Dacă una din variabilele alias se modifică prin atribuirea unei
noi valori, atunci se poate ca și ceilalți identificatori alias să-și modifice valoarea.
Application Program Interface (API) = o listă de elemente care definește schema de interacționare
între două aplicații. Componentele listei sunt comune și recunoscute de două sau mai multe aplicații.
Argument = parametru de intrare al unei funcții
B
Batch = mod de lucru în care comenzile Python sunt reunite secvențial într-un fișier text (cu
terminația .py. .cpp, etc.), analizate din punct de vedere sintactic, transformate într-un fișier obiect
(compilate) și apoi sunt executate în grup (etapa run-time). Este un mod de lucru opus interpretării, în
care comenzile sunt executate în secvență, câte una la un moment dat.
Binding = legare, legătură
Bug = o eroare într-un program
C
Calificare = legarea obiectului A de obiectul B prin notația B.A și denumită “B califică A”, sau “A
este calificat de B” (ex.: math.sqrt, math califică sqrt).
Cycle = o cale într-un graf care începe și sfârșește în același nod.
Clasa de memorarare a variabilelor = atribut al unei variabile care determină modul de alocare a
spațiului de memorie și durata de viață corespunzătoare acesteia.
Class (POO) = un prototip definit de utilizator pentru un obiect care definește un set de atribute care
caracterizează obiectul. Atributele sunt date membre (variabile ale clasei și variabile ale instanței) și
metode, accesate prin notația cu punct.
Colon = două puncte (:). Semicolonul este simbolul punct și virgulă (;).
Compilare = proces de conversie a unui modul program din formă sursă în formă obiect binar
executabil de mașină.
Compilator = program software care transformă un program sursă (text) în program obiect (binar).
Numele simbolice din programul sursă sunt înlocuite cu adresele actuale unde sunt memorate datele.
Pentru a deveni executabil, uneori programul obiect mai trebuie să fie legat de alte module binare și
eventual transformat din nou în alt format. În timpul execuției (run-time) rămân de regulă fixate
numele, tipul și adresa locației de memorie, schimbându-se doar datele stocate în locațiile de
memorie.
Concatenare = alipire de șiruri
D
Data member (POO) = o variabilă a clasei sau variabilă a instanței care memorează date asociate cu o
clasă și obiectele sale.
Debugging = procesul de găsire și corectare a unor erori (bugs).
409 Glossar

Docstring = un șir aflat (sau care începe) pe prima linie a definiției unei funcții sau a definiției unui
modul, după antet.
Dot notation = utilizarea operatorului punct (.) pentru accesarea metodelor și atributelor unui obiect,
de exemplu: nume_obiect.nume_atribut.
Dunder = abreviere pentru “double underscore” ( __ ).
E
Expresie = un set de variabile și constante (operanzi) conectate prin operatori.
F
Fișier = colecție de elemente identificabile denumite înregistrări, stocate pe un suport de informație
(de regulă extern).
Fișier text = un fișier conținând caractere organizate pe linii (rânduri) terminate cu un caracter
(newline), sau două (newline, carriage-return).
Fișier binar = fișier ale cărui elemente sunt în reprezentarea internă a calculatorului.
Fișier sursă = un fișier text, scris într-un limbaj de programare.
Floating-point = tipul numeric reprezentând numerele care au parte fracționară și sunt reprezentate în
memoria calculatorului prin convenția “virgulă mobilă”, prin care virgula nu ocupă o poziție fixă.
Funcție = secvență de instrucțiuni denumită și subrutină sau procedură. Primește la intrare un set de
parametri denumiți argumente și întorce de regulă rezultatul execuției prin intermediul unei
instrucțiuni return.
G
Global = se referă la domeniul de vizibilitate al unei variabile, extins atât la interiorul cât și la
exteriorul funcțiilor (domeniul de vizibilitate al variabilei este întreg programul sursă).
Graf = o mulțime V de noduri (valori) și o mulțime E de arce care conectează nodurile.
Gramatică = regulile care definesc un limbaj.
H
Handler = mâner, manetă. De regulă este un număr care se asociază unic cu o entitate software pentru
identificarea acesteia. De exemplu, identificatorul unui fișier stabilit prin instrucțiunea de deschidere a
acestuia.
Handling = tratare, manevrare, prelucrare aplicată unui obiect software.
Header = antet.
Higher order function = funcție de ordin superior. De regulă poate primi ca argument o altă funcție.
Hash function = orice funcție care poate fi utilizată pentru maparea (punerea în corespondență) datelor
de dimensiuni arbitrare la (cu) valori de dimensiuni fixe. Valorile returnate de o funcție hash se
numesc valori hash sau coduri hash, Valorile sunt de obicei utilizate pentru indexarea unui tabel (sau
dicționar) cu dimensiuni fixe numit tabel hash (hashtable). Funcțiile hash și tabelele lor hash asociate
sunt utilizate în aplicațiile de stocare și recuperare a datelor pentru a accesa datele într-un timp mic și
aproape constant de recuperare și necesită o cantitate de spațiu de stocare doar fracțional mai mare
decât spațiul total necesar pentru date sau înregistrări. Hashing-ul este o formă de acces la date,
eficientă din punct de vedere al spațiului de stocare și calcul, care evită timpul de acces neliniar al
listelor ordonate și neordonate și cerințele de stocare adesea exponențiale ale accesului direct. Un
aspect de interes este unicitatea asocierii codurilor hash cu datele.
Hashtabil = un obiect este hashtabil dacă are asociată (există) o funcție care întoarce întotdeauna
aceeași valoare pentru obiectul respectiv.
410 Glossar

Hashtable = tabel de acces la date construit pe baza utilizării unei funcții de hashing pentru generarea
codurilor de acces.
I
Immutable data = valoarea datei nu poate fi modificată (tuple, șiruri).
Index = o valoare întreagă care referă elementele unui tablou sau enumerări.
Inheritance (POO) = moștenire. Transferul caracteristicilor unei clase către alte clase derivate din ea.
Instance (POO) = un obiect concretizat (instanțiat, individualizat, realizat) dintr-o clasă.
Instantiation (POO) = crearea unei instanțe (obiect) a unei clase.
Interpreter = interpretor, procesor software care execută pas cu pas instrucțiunile unui program,
întorcând rezultatul execuției fiecărei instrucțiuni și facilitând astfel procesul de depanare și punere la
punct al programului.
Iteration = iterație, (1) un proces care se repetă, (2) un singur pas al unei bucle de prelucrare.
J
JSON (JavaScript Object Notation) = un format standard open-source, pentru schimbul datelor, care
utilizează text pentru memorarea și transmiterea datelor de tip obiect constând din perechi atribut-
valoare și date de tip tablou. Servește la la înlocuirea XML în unele situații. Este derivat din
JavaScript.
K
Keyword = cuvânt cheie, care face parte din vocabularul de bază de comenzi al procesorului.
L
Language = limbaj, mulțimea șirurilor de simboluri care respectă regulile unei gramatici.
Lifetime = durata de viață a obiectelor. Dacă spațiul de memorie pentru o variabilă este alocat
dinamic, pe stivă sau în registrele calculatorului, se spune că variabila este dinamică. Dacă spațiul de
memorie este alocat static, în zona de memorie permanentă a programului, se spune că variabila este
statică. În Python, variabilele locale sunt dinamice, variabilele globale sunt statice.
Lint, linting = este procesul de analizare statică a unei secvențe de program pentru descoperirea
anticipată unor posibile erori de tip sintactic, erori de stil sau construcții suspicioase.
Liskov substitution principle (POO) = principiu referitor la obiecte introdus de Barbara Liskov în
1988, conform căruia dacă un obiect de tipul S este un subtip al obiectului de tip T, pe care îl extinde,
atunci substituirea lui T cu S nu ar trebui să perturbe funcționarea programului.
M
Magic methods = funcții de tip dunder: __init__(), etc., fiind apelabile indirect (transparent).
Memoisation = tehnică de reducere a timpului de calcul în cazul rulării unor algoritmi de tip recursiv,
prin memorizarea unor rezultate parțiale și refolosirea lor în cazul repetării execuției algoritmului.
Mapping type = un tip de dată formată dintr-o colecție de chei asociate cu valori. În Python singurul
tip de astfel de dată este dicționarul.
Markdown = limbaj de marcare (asemănător HTML), utilizat intensiv în proiectul Jupiter-lab.
Method (POO) = un tip special de funcție (metodă) definită în cadrul definiției clasei.
Mutable data = valoarea unei date mutabile poate fi modificată (liste, dicționare).
N
Namespace = spațiu al numelor.
411 Glossar

Naming collision = o situație în care două sau mai multe nume aparținând unui spațiu de nume sunt
ambigue.
nbsp (non breaking space) = cod inserat în paginile tip .html care de regulă ignoră spațiile în număr
mai mare ca 1, pentru a reprezenta spații suplimentare (&nbsp&).
Nod = un element al unei liste, sau graf, sau arbore care conține atât datele cât și un pointer la
elementul următor al structurii de date.
Non-local = în Python se pot defini funcții în funcții. Ca urmare apare domeniul non-local, în afară de
domeniile local și global. Declarația unei variabile non-locale poate apărea, de exemplu, în cadrul
unei funcții apelate de o altă funcție.Variabila de nivel inferior non-locală poate fi locală la nivel
superior.
O
Object (POO) = o instanță (obiect) unică a unei structuri de date de tip clasă. Un obiect
conține date membre (variabile clasă și variabile instanță) și metode.
Off the shelf = gata de folosință, de pe raft
(Function) Overloading (POO) = atribuirea mai multor moduri de comportare unei funcții
(supraîncărcarea funcției). Operațiile ce pot fi efectuate depind de tipul obiectelor și argumentelor
implicate.
(Operator) Overloading (POO) = atribuirea mai multor funcționalități unui anumit operator
(supraîncărcarea operatorului).
P
Parametru (formal/nume argument) = nume al unei entități generice utilizate în definiția unei funcții
(nume argument). Valoarea parametrului nu este precizată în momentul definiției.
Parametru (effectiv/actual) = entitate care particularizează entitatea generică desemnată de un
parametru formal al unei funcții în momentul apelului acesteia.
Pattern = schemă, șablon, formă
Parse, parser = analiză, analizor
R
Reference = referință, un alt nume dat unei variabile sau unui obiect. Referința nu creează alte entități
distincte în memorie față de original. Uneori, deși originalul a fost șters din memorie, acesta mai poate
fi accesat în continuare prin intermediul referinței.
Recursive call = apelul unei funcții la ea însăși
Read–Eval–Print Loop (REPL) = un mediu al unui limbaj de programare, interactiv, care primește o
intrare, o execută (evaluează) și întoarce rezultatul către utilizator. Apoi se reia ciclul cu o nouă
intrare.
S
Scientific stack = stiva științifică este formată din Python, Matplotlib, Numpy, Pandas, Scipy, Scikit-
learn și alte biblioteci. Permite procesarea și vizualizarea datelor cu caracter științific și tehnic.
Scope = domeniul de vizibilitate al numelui N, corespunzător al unei entități E, este spațiul
programului în care N desemnează entitatea E. Se referă în general la domeniul de vizibilitate al
variabilelor în interiorul și exteriorul funcțiilor.
Semicolon = punct și virgulă (;)
Sequence = orice fel de tip de dată care constă într-o colecție ordonată de elemente, fiecare element
fiind identificat de un index.
412 Glossar

Side effect = efect lateral, o schimbare a stării unui program (o modificare a valorii unei variabile,
etc.) care apare la apelul unei funcții. In general este neprevăzut, iar consecințele nedorite.
Statement = declarație, instrucțiune
Shell = coajă, carapace, scoică. În software se utilizează cu sensul de cadru, mediu de lansare a
comenzilor sau programelor, cu utilități minimale de editare în linie și posibil cu meniu foarte simplu
de dezvoltare
SOA (Service-Oriented Architecture) = se referă la o aplicație care e făcută din componente conectate
în cadrul unei rețele
Sort = un proces de organizare a unei colecții de date în ordine ascendentă, sau descendentă.
Stack = stivă, un tip abstract de date (căruia îi corespunde o zonă de memorie) în care cel mai recent
element inserat este primul element extras sau regăsit. Această proprietate se numește “ultimul intrat,
primul ieșit”, sau last-in, first-out (LIFO).
Data structure = structură de date, o construcție definită într-un limbaj de programare pentru
memorarea unei colecții de date.
Stride, strides = reprezintă numărul de octeți care trebuie săriți în memorie pentru a ajunge la
următorul element al tabloului
T
Traverse = traversare, o iterare prin elementele unei colecții cu executarea unor operații similare
asupra fiecăruia.
Tree = arbore, un graf conectat fără bucle.
Token = un element de bază în structura sintactică a unui program, similar cuvântului într-un limbaj,
termen.
Tuple = abstractizarea unei secvențe: single, double, triple, quadruple, quintuple, sextuple, septuple,
octuple, ..., n-tuple, .... În limbajele de programare, un tuplu este în general o listă de elemente între
paranteze rotunde ( ), separate prin virgulă. Un tuplu este în general nemutabil (elementele sale sunt
fixate și nu mai pot fi modificate), dar poate fi și mutabil dacă elementele și valorile lor mai pot fi
schimbate.
Type = o categorie de valori numerice sau nenumerice.
U
Ufunc = funcție utilizator, sau funcție universală.
V
Variable = variabila, în limbajele de programare este un nume simbolic asociat cu o locație din
memoria calculatorului identificată de o adresă. Locația de memorie conține o cantitate cunoscută sau
necunoscută de informație denumită valoare. Separarea numelui de conținut permite ca numele
variabilei să fie folosit independent de valoarea exactă a informației. Se mai spune că numele
variabilei este o referință (referă) valoarea stocată în locația din memorie. Numele variabilei poate fi
legat de valoarea stocată la momentul run-time și conținutul locației poate fi modificat în timpul
execuției programului (variabilă mutabilă). Dacă schimbarea este interzisă, se spune că variabila este
imutabilă, deși în acest caz denumirea de variabilă este improprie.
(Class) Variable (POO) = o variabilă care este partajată de toate instanțele clasei. Variabilele clasei
sunt definite în cadrul clasei, dar în afara oricărei metode a clasei. De regulă, variabilele clasei nu sunt
utilizate la fel de frecvent ca variabilele instanței.
413 Glossar

(Global) Variable = variabilă globală; se referă la o variabilă definită în exteriorul funcției. Dacă în
funcție se dorește modificarea variabilei, se declară variabila ca fiind „global”. Dacă se dorește doar
să se citească, nu este necesar.
(Instance) Variable (POO) = o variabilă definită în cadrul unei metode și care aparține unei instanțe
curente a clasei.
Strongly typed variable = în cazul unei variabile strongly typed (de tip tare) nu este permis ca tipul
acesteia să se schimbe prin conversii implicite. Schimbarea tipului se poate face numai prin conversii
explicite.
Virtual Environment = în Python, un mediu izolat care permite să fie instalate pachete doar pentru o
anumită aplicație.
W
Web scrapping = extragerea unor secvențe de informație din mai multe pagini web (de exemplu,
prețul unui anumit produs).
Widget (graphical widget) = este o componentă software a unei interfețe grafice, ca de exemplu un
buton, o casetă de afișare sau o bară derulantă. Mai este denumită “element grafic de control” și
permite citirea, scrierea sau editarea directă, printr-o metodă specifică, a unei informații în cadrul
unei aplicații software. Widgeturile se găsesc reunite în colecții (toolkit) sau biblioteci (library) ca:
Windows Presentation Foundation, GTK, etc. Facilitează reutilizarea componentelor software.
Wrapper = învelitoare, ambalaj.
Whitespace = orice caracter care produce mutarea cursorului fără imprimarea unui caracter vizibil.
X
XML, eXtensible Markup Language = este un format pentru un “limbaj de descriere”, care permite
reprezentarea datelor structurate cu ajutorul unor marcaje.
Eugen Diaconescu Limbajul și ecosistemul de programare Python

BIBLIOGRAFIE

Cursuri și tutoriale IT - inclusiv Python

https://www.tutorialsteacher.com https://data-flair.training/blogs/
https://domeniulit.com/ https://tutorialsteacher.com
https://medium.com/ https://journaldev.com
https://dotnettutorials.net https://techdecodetutorials.com/
https://w3schools.com https://zetcode.com
https://tutorialspoint.com https://itvoyagers.in/
https://udemy.com https://w3resource.com/
https://www.datacamp.com/ https://www.afterhoursprogramming.com/
https://www.coursera.org https://riptutorial.com/
https://www.sololearn.com/learning https://jobtensor.com/
https://www.techbeamers.com/ https://stackabuse.com/
https://simplivlearning.com/ https://people.duke.edu/
https://www.edx.org https://www.edureka.co/
https://hackr.io/ https://coderslegacy.com
https://bitdegree.org https://note.nkmk.me/
https://www.codecademy.com/
https://www.afterhoursprogramming.com/
https://open.edx.org (fondat de Harvard și MIT
în 2012)

Cursuri și tutoriale - dedicate Python (limbaj + ecosistem)

https://python.org https://askpython.com
https://realpython.com/ https://python.hotexamples.com/
https://docs.scipy.org https://pillow.readthedocs.io/
https://docs.spyder-ide.org/ https://scikit-image.org/
https://learnPython.org https://pynative.com/
http://inventwithpython.com/ https://holypython.com/
https://pythonspot.com/ https://www.practicepython.org/
https://pythonexamples.org https://seaborn.pydata.org/
https://pythonguides.com/ https://problemsolvingwithpython.com/
https://www.pythonistacafe.com/ https://pythontic.com/
https://tutorial.djangogirls.org/
https://pythonprogramming.net
https://python-course.eu
415 Bibliografie

Cărți
1. Vlad Tudor, Curs de programare în Python 3 – Fundamente pentru începători, vol. 1, Ed. L&S Soft,
2020, 215 pag.
2. Doru Anastasiu Popescu, Python 3 – Noțiuni Fundamentale, Culegere de Probleme, Ed. L&S Soft,
2020, 222 pag.
3. Swaroop C H, A Byte of Python, free book, Self-publishing, 2013
4. Allen B Downey, Think Python, How to think like a computer scientist, Green Tea Press, 2ed, 2012
5. Eric Matthes, Python Crash Course: A Hands-On, Project-Based Introduction to Programming, No
Starch Press, 2016, 2ed
6. Zed A. Shaw, Learn Python 3 the Hard Way,
7. Programming Language Academy, Python Workbook – Learn how to quickly and effectively program
with exercises, projects, and solutions, 2010
8. Paul Barry, Head First Python, O’Reilly Media, Inc., 2nd edition, 2011
9. Mark Pilgrim, Dive Into Python 3, Apress, 2009-2011
10. Michael Driscoll, Python 101, 2nd Edition, Leanpub, 2020, 760pag
11. Dan Bader, Python Tricks: The Book , 2017, 299pag
12. Luciano Ramalho, Fluent Python, O’Reilly, 2015, 766 pag
13. Tarek Ziadé and Michal Jaworski, Expert Python Programming, Packt Publishing, 2008, 372 pag
14.Doug Hellmann, The Python 3 Standard Library by Example, Pearson Education, 2017, 1454 pag
15.Peter Wentworth, Jeffrey Elkner, Allen B. Downey and Chris Meyers, How to Think Like a
Computer Scientist: Learning with Python 3 Documentation, 3rd Edition, 2019, 367 pag
16.Mark Lutz, Learning Python, Fifth Edition, O’Reilly, 2013, 1594 pag
17.Paul Deitel, Harvey Deitel, Python for Programmers, Pearson, 2019, 810pag
18.David Beazley, Brian K. Jones, Python Cookbook, O’Reilly, 2015, 706 pag
19.Dusty Philips, Python 3 Object-Oriented Programming, Third Edition, Packt, 2018, 456 pag
20.Nigel George, Build a Website with Django 3, GNW Independent Publishing, 2019
21.John E. Grayson, Python and Tkinter Programming, Manning, 2000
22.John Hunter, Darren Dale, Eric Firing, Michael Droettboom, Matplotlib, Release 3.1.1, 2019, 2354
pag
23.Sandro Tosi, Matplotlib for Python Developers, Packt Publishing, 2009, 307 pag
24.Ivan Idris, NumPy Beginner’s Guide, Packt Publishing, 2013, 310 pag
25.Numpy Community, NumPy User Guide, Release 1.17, 2019, 156 pag
26.Matt Harrison, Learning the Pandas Library, Treading on Python Series, 2016, 208 pag
27.Stack Overflow contributors, Learning Pandas, Free eBook, https://riptutorial.com/ebook/pandas,
172 pag
28.Sandipan Dey, Hands-ON Image Processing with Python, Packt Publishing, 2018, 155 pag
29.Jason M. Kinser, Image Operators – Image processing in Python, CRC Press, 2019, 366 pag
30.R. Chityala, S. Pudipeddi, Image Processing and Acquisition using Python, Second Edition, CRC
Press, 2021, 453 pag
31.Gaël Varoquaux, Emmanuelle Gouillart, Olav Vahtras, Valentin Haenel, Nicolas P. Rougier, et al.,
Scipy Lecture Notes: One document to learn numerics, science, and data with Python. Zenodo,
2015, 368 pag
32.SciPy Community, SciPy Reference Guide, Release 0.16.0, 2015, 1603 pag

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