Programarea Calculatoarelor
Curs Complet de Programarea Calculatoarelor¶
De la fundamente la tehnici avansate — cu exemple în Python (și comparații C/C++/Java)¶
Cuprins¶
Partea I — Fundamente
- Introducere în programare și gândire algoritmică
- Variabile, tipuri de date și operatori
- Structuri de control — decizii și bucle
- Funcții și modularizare
- Șiruri de caractere (Strings)
- Structuri de date liniare — Liste și Tablouri
- Tupluri, Mulțimi și Dicționare
- Lucrul cu fișiere
- Tratarea excepțiilor și robustețea programelor
Partea II — Intermediar
10. Recursivitate
11. Algoritmi de sortare
12. Algoritmi de căutare
13. Complexitate computațională (notația Big-O)
14. Structuri de date fundamentale — Stive, Cozi, Liste înlănțuite
15. Arbori și grafuri — noțiuni de bază
16. Programare orientată pe obiecte — fundamente
Partea III — Avansat
17. OOP avansat — moștenire, polimorfism, compoziție
18. Programare funcțională în Python
19. Generatoare, iteratoare și comprehensions
20. Expresii regulate (Regex)
21. Module, pachete și ecosistemul Python
22. Introducere în testare și debugging
23. Bune practici și stil de cod
PARTEA I — FUNDAMENTE¶
1. Introducere în programare și gândire algoritmică¶
1.1 Ce este un program?¶
Un program este o secvență de instrucțiuni precise care descriu unui calculator cum să rezolve o problemă. Calculatorul execută instrucțiunile exact așa cum sunt scrise, fără interpretare, fără „bun simț” — ceea ce scriem este ceea ce obținem.
1.2 Ce este un algoritm?¶
Un algoritm este o descriere finită, clară și neambiguă a pașilor necesari pentru a rezolva o problemă. Algoritmul este independent de limbajul de programare — este ideea, metoda de rezolvare.
Proprietățile unui algoritm:
- Finitudine — se termină după un număr finit de pași
- Determinism — fiecare pas este precis definit
- Intrare — primește zero sau mai multe date de intrare
- Ieșire — produce cel puțin un rezultat
- Efectivitate — fiecare pas poate fi executat în timp finit
1.3 Pseudocod¶
Pseudocodul descrie algoritmul într-un limbaj informal, structurat, independent de sintaxă:
ALGORITM: Verifică dacă un număr este prim
CITEȘTE n
DACĂ n < 2 ATUNCI
SCRIE "Nu este prim"
STOP
divisor ← 2
CÂT TIMP divisor * divisor <= n EXECUTĂ
DACĂ n MOD divisor == 0 ATUNCI
SCRIE "Nu este prim"
STOP
divisor ← divisor + 1
SCRIE "Este prim"
1.4 Primul program Python¶
# hello.py — primul program
print("Hello, World!")
# Programul Python se execută linie cu linie, de sus în jos.
# print() este o funcție care afișează text pe ecran.
# Textul între ghilimele este un string (șir de caractere).
Comparație — Hello World în alte limbaje:
// C
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
// Java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Observație: Python elimină ceremonia (include-uri, declarație de clasă, funcție main, punct și virgulă, acolade). Aceasta îl face ideal pentru învățarea conceptelor de programare fără a fi distras de sintaxă.
1.5 Cum funcționează Python¶
Codul sursă (.py) → Interpretor Python → Bytecode (.pyc) → Python VM → Execuție
Python este:
- Interpretat (nu compilat direct în cod mașină)
- Dinamic tipizat (tipul variabilei se determină la runtime)
- Puternic tipizat (nu face conversii implicite periculoase)
- Indentat semnificativ (indentarea definește blocurile de cod)
2. Variabile, tipuri de date și operatori¶
2.1 Variabile¶
O variabilă este un nume asociat cu o valoare stocată în memorie. În Python, nu declarăm tipul — acesta se deduce automat.
# Atribuire — se creează variabila și i se asociază o valoare
varsta = 21
nume = "Ana"
pi = 3.14159
este_student = True
# Python este dinamic tipizat:
x = 10 # x este int
x = "text" # acum x este str (permis în Python, INTERZIS în C/Java)
# Reguli denumire:
# - Poate conține litere, cifre, underscore
# - NU poate începe cu cifră
# - Case-sensitive (Nume ≠ nume)
# - Convenție: snake_case (nu camelCase)
# - Evită cuvintele rezervate (if, for, class, return, etc.)
numar_studenti = 30 # ✅ corect (snake_case)
_variabila_privata = 42 # ✅ corect (convenie: pseudo-privat)
2x = 10 # ❌ SyntaxError (începe cu cifră)
Comparație C/Java — tipare statice:
int varsta = 21; // C: tipul TREBUIE declarat, nu se schimbă
String nume = "Ana"; // Java: idem
varsta = "text"; // EROARE de compilare în C/Java
2.2 Tipuri de date fundamentale¶
# === Numerice ===
a = 42 # int (întreg, precizie arbitrară în Python!)
b = 3.14 # float (virgulă mobilă, ~15 cifre semnificative)
c = 2 + 3j # complex (parte reală + parte imaginară)
# Precizia arbitrară a int-urilor Python (vs. overflow în C):
big = 2 ** 1000 # Funcționează perfect! (în C, ar fi overflow)
print(len(str(big))) # 302 cifre
# === Boolean ===
d = True # bool (True sau False)
e = False
# Valori "falsy" în Python: False, 0, 0.0, "", [], {}, (), None, set()
# Totul altceva este "truthy"
# === String ===
f = "Hello" # str (secvență de caractere, IMUTABIL)
g = 'World' # ghilimele simple sau duble — echivalent
h = """Text
pe mai multe
linii""" # triple quotes
# === NoneType ===
i = None # Absența unei valori (echivalent null în Java, NULL în C)
# Verificare tip:
print(type(a)) # <class 'int'>
print(type(f)) # <class 'str'>
print(isinstance(a, int)) # True
2.3 Conversii de tip (casting)¶
# Conversii explicite:
x = int("42") # str → int: 42
y = float("3.14") # str → float: 3.14
z = str(42) # int → str: "42"
w = int(3.99) # float → int: 3 (TRUNCHIERE, nu rotunjire!)
r = round(3.99) # Rotunjire: 4
b = bool(0) # int → bool: False
b2 = bool("text") # str → bool: True (string nevid = truthy)
# Erori de conversie:
# int("abc") # ValueError: invalid literal
# int("") # ValueError
2.4 Operatori¶
# === Aritmetici ===
print(7 + 3) # 10 (adunare)
print(7 - 3) # 4 (scădere)
print(7 * 3) # 21 (înmulțire)
print(7 / 3) # 2.3333... (împărțire REALĂ — mereu returnează float!)
print(7 // 3) # 2 (împărțire ÎNTREAGĂ — câtul)
print(7 % 3) # 1 (modulo — restul)
print(7 ** 3) # 343 (ridicare la putere)
# Atenție: / în Python 3 returnează MEREU float
# În C/Java: 7 / 3 == 2 (împărțire întreagă dacă ambii sunt int!)
# === Comparație ===
print(5 == 5) # True (egal)
print(5 != 3) # True (diferit)
print(5 > 3) # True
print(5 < 3) # False
print(5 >= 5) # True
print(5 <= 3) # False
# Comparații înlănțuite (unic Python!):
x = 5
print(1 < x < 10) # True (echivalent cu: 1 < x and x < 10)
print(1 < x < 3) # False
# === Logici ===
print(True and False) # False
print(True or False) # True
print(not True) # False
# Short-circuit evaluation:
# 'and' returnează prima valoare falsy, sau ultima dacă toate sunt truthy
# 'or' returnează prima valoare truthy, sau ultima dacă toate sunt falsy
print(0 and 5) # 0 (0 este falsy → se oprește)
print(3 and 5) # 5 (3 este truthy → continuă, returnează ultimul)
print(0 or 5) # 5 (0 falsy → continuă, 5 truthy → returnează)
print("" or "default") # "default" (pattern util!)
# === Pe biți (Bitwise) ===
print(5 & 3) # 1 (AND pe biți: 101 & 011 = 001)
print(5 | 3) # 7 (OR pe biți: 101 | 011 = 111)
print(5 ^ 3) # 6 (XOR pe biți: 101 ^ 011 = 110)
print(~5) # -6 (NOT pe biți: complement față de 2)
print(5 << 1) # 10 (shift stânga: 101 → 1010)
print(5 >> 1) # 2 (shift dreapta: 101 → 10)
# === Atribuire compusă ===
x = 10
x += 5 # x = x + 5 → 15
x -= 3 # x = x - 3 → 12
x *= 2 # x = x * 2 → 24
x //= 5 # x = x // 5 → 4
x **= 3 # x = x ** 3 → 64
# Nota: Python NU are ++ sau -- (x++ nu există, folosim x += 1)
# === Identitate și apartenență ===
a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a is b) # True (ACELAȘI obiect în memorie)
print(a is c) # False (obiecte diferite, chiar dacă valorile sunt egale)
print(a == c) # True (valori egale)
print(2 in a) # True (elementul 2 se află în lista a)
print(5 not in a) # True
2.5 Citirea datelor de la utilizator¶
# input() citește MEREU un string
nume = input("Cum te cheamă? ")
varsta_str = input("Câți ani ai? ")
varsta = int(varsta_str) # Conversie explicită
# Sau pe scurt:
varsta = int(input("Câți ani ai? "))
# Citirea mai multor valori pe aceeași linie:
a, b = input("Introduceți a și b: ").split() # split() împarte după spații
a, b = int(a), int(b)
# Sau cu map:
a, b = map(int, input("a b: ").split())
3. Structuri de control — decizii și bucle¶
3.1 Instrucțiunea if / elif / else¶
nota = int(input("Nota: "))
if nota >= 9:
calificativ = "Excelent"
elif nota >= 7:
calificativ = "Bine"
elif nota >= 5:
calificativ = "Suficient"
else:
calificativ = "Insuficient"
print(f"Calificativ: {calificativ}")
# Python folosește INDENTAREA (4 spații) pentru a delimita blocurile.
# Nu are acolade {} ca C/Java — indentarea este OBLIGATORIE și semnificativă.
Comparație C:
if (nota >= 9) {
printf("Excelent");
} else if (nota >= 7) {
printf("Bine");
} // ...acoladele sunt obligatorii pentru blocuri de mai multe linii
# Operatorul ternar (conditional expression):
status = "major" if varsta >= 18 else "minor"
# Echivalent C/Java: status = (varsta >= 18) ? "major" : "minor";
# Match/case (Python 3.10+) — echivalentul switch:
command = input("Comandă: ")
match command:
case "start":
print("Pornire...")
case "stop":
print("Oprire...")
case "status":
print("Stare: activ")
case _:
print("Comandă necunoscută")
3.2 Bucla while¶
# while — repetă CÂT TIMP condiția este adevărată
count = 5
while count > 0:
print(f"Numărătoare inversă: {count}")
count -= 1
print("Lansare!")
# Citire repetată până la input valid:
while True:
try:
n = int(input("Introduceți un număr pozitiv: "))
if n > 0:
break # Ieșire din buclă
except ValueError:
pass # Ignoră eroarea, reia bucla
print(f"Ați introdus: {n}")
# while cu else (specific Python — se execută dacă bucla NU a fost întreruptă cu break):
n = int(input("n: "))
d = 2
while d * d <= n:
if n % d == 0:
print(f"{n} nu este prim (divide cu {d})")
break
d += 1
else:
# Se execută DOAR dacă while s-a terminat normal (fără break)
print(f"{n} este prim")
3.3 Bucla for¶
# for în Python iterează peste o secvență (nu este for clasic cu contor!)
# Iterare peste un range:
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10): # 2, 3, 4, ..., 9
print(i)
for i in range(0, 20, 3): # 0, 3, 6, 9, 12, 15, 18 (pas 3)
print(i)
for i in range(10, 0, -1): # 10, 9, 8, ..., 1 (numărătoare inversă)
print(i)
# Iterare peste o colecție:
fructe = ["măr", "pară", "cireașă"]
for fruct in fructe:
print(fruct)
# Cu index (enumerate):
for index, fruct in enumerate(fructe):
print(f"{index}: {fruct}")
# Iterare peste caractere:
for char in "Python":
print(char, end=" ") # P y t h o n
# for cu else:
for x in range(2, 20):
if x % 7 == 0:
print(f"Primul multiplu de 7: {x}")
break
else:
print("Nu s-a găsit multiplu de 7 în interval")
Comparație — for clasic C/Java:
// C/Java: for cu contor explicit
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
// Python: range(5) generează aceeași secvență, dar sintaxa e diferită
3.4 break, continue, pass¶
# break — ieșire imediată din bucla cea mai interioară
for i in range(100):
if i == 5:
break # Oprește la i=5
print(i) # Afișează: 0 1 2 3 4
# continue — sare la următoarea iterație
for i in range(10):
if i % 2 == 0:
continue # Sare peste numerele pare
print(i) # Afișează: 1 3 5 7 9
# pass — instrucțiune goală (placeholder)
for i in range(10):
pass # Nu face nimic (util pentru structuri incomplete)
def functie_neimplementata():
pass # Placeholder — fără pass, ar fi SyntaxError
3.5 Bucle imbricate¶
# Tabel de înmulțire:
for i in range(1, 11):
for j in range(1, 11):
print(f"{i*j:4d}", end="")
print() # Linie nouă după fiecare rând
# Triunghi de caractere:
n = 5
for i in range(1, n + 1):
print("*" * i)
# *
# **
# ***
# ****
# *****
# Matrice — parcurgere:
matrice = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
for rand in matrice:
for element in rand:
print(f"{element:3d}", end="")
print()
4. Funcții și modularizare¶
4.1 Definirea și apelarea funcțiilor¶
# Funcția = bloc de cod reutilizabil, cu un nume, care realizează o sarcină specifică
def salut(nume):
"""Afișează un mesaj de salut personalizat.""" # Docstring
print(f"Salut, {nume}!")
salut("Ana") # Apel: Salut, Ana!
salut("Mihai") # Apel: Salut, Mihai!
# Funcție cu valoare returnată:
def aduna(a, b):
"""Returnează suma a două numere."""
return a + b
rezultat = aduna(3, 5) # rezultat = 8
print(aduna(10, 20)) # 30
# Funcție fără return explicit returnează None:
def afiseaza(mesaj):
print(mesaj)
x = afiseaza("Test")
print(x) # None
4.2 Parametri și argumente¶
# Parametri cu valoare implicită:
def salut(nume, salutare="Bună"):
print(f"{salutare}, {nume}!")
salut("Ana") # Bună, Ana!
salut("Ana", "Salutare") # Salutare, Ana!
# Argumente poziționale și keyword:
def profil(nume, varsta, oras):
print(f"{nume}, {varsta} ani, din {oras}")
profil("Ana", 21, "Cluj") # Pozițional
profil(oras="Cluj", nume="Ana", varsta=21) # Keyword (ordinea nu contează)
profil("Ana", oras="Cluj", varsta=21) # Mix (poziționale PRIMUL)
# *args — număr variabil de argumente poziționale (tuplu):
def suma(*numere):
total = 0
for n in numere:
total += n
return total
print(suma(1, 2, 3)) # 6
print(suma(10, 20, 30, 40)) # 100
# **kwargs — număr variabil de argumente keyword (dicționar):
def config(**optiuni):
for cheie, valoare in optiuni.items():
print(f"{cheie} = {valoare}")
config(host="localhost", port=8080, debug=True)
# Combinare completă (ordinea contează!):
def f(a, b, *args, cheie="default", **kwargs):
print(f"a={a}, b={b}, args={args}, cheie={cheie}, kwargs={kwargs}")
f(1, 2, 3, 4, cheie="x", extra=True)
# a=1, b=2, args=(3, 4), cheie=x, kwargs={'extra': True}
4.3 Scope (domeniul de vizibilitate)¶
# Python are regula LEGB: Local → Enclosing → Global → Built-in
x = "global" # Variabilă globală
def outer():
x = "enclosing" # Variabilă în funcția exterioară
def inner():
x = "local" # Variabilă locală
print(x) # "local"
inner()
print(x) # "enclosing"
outer()
print(x) # "global"
# Modificarea variabilei globale din funcție necesită 'global':
counter = 0
def incrementeaza():
global counter
counter += 1
incrementeaza()
print(counter) # 1
# Modificarea variabilei enclosing necesită 'nonlocal':
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
4.4 Funcții ca obiecte de primă clasă¶
# Funcțiile pot fi atribuite variabilelor, pasate ca argumente, returnate din alte funcții
def aplica(functie, valoare):
return functie(valoare)
def dublu(x):
return x * 2
def patrat(x):
return x ** 2
print(aplica(dublu, 5)) # 10
print(aplica(patrat, 5)) # 25
# Funcții lambda (anonime, pe o singură expresie):
triplu = lambda x: x * 3
print(triplu(5)) # 15
# Lambda-urile sunt utile ca argumente:
numere = [3, 1, 4, 1, 5, 9]
print(sorted(numere)) # [1, 1, 3, 4, 5, 9]
print(sorted(numere, reverse=True)) # [9, 5, 4, 3, 1, 1]
studenti = [("Ana", 9.5), ("Ion", 8.0), ("Maria", 9.8)]
print(sorted(studenti, key=lambda s: s[1])) # Sortare după notă
print(sorted(studenti, key=lambda s: s[1], reverse=True)) # Descrescător
5. Șiruri de caractere (Strings)¶
5.1 Operații fundamentale¶
s = "Hello, World!"
# Indexare (0-based):
print(s[0]) # 'H' (primul caracter)
print(s[-1]) # '!' (ultimul caracter)
# Slicing [start:stop:step]:
print(s[0:5]) # 'Hello'
print(s[7:]) # 'World!'
print(s[:5]) # 'Hello'
print(s[::2]) # 'Hlo ol!' (din 2 în 2)
print(s[::-1]) # '!dlroW ,olleH' (inversare)
# Lungime:
print(len(s)) # 13
# Concatenare și repetare:
print("Hello" + " " + "World") # Hello World
print("Ha" * 3) # HaHaHa
# Stringurile sunt IMUTABILE:
# s[0] = 'h' # TypeError: 'str' object does not support item assignment
s = 'h' + s[1:] # Creăm un string NOU: 'hello, World!'
5.2 Metode importante¶
s = " Hello, World! "
# Transformări:
print(s.strip()) # 'Hello, World!' (elimină spații la capete)
print(s.lower()) # ' hello, world! '
print(s.upper()) # ' HELLO, WORLD! '
print(s.title()) # ' Hello, World! '
print(s.replace("World", "Python")) # ' Hello, Python! '
# Căutare:
print(s.find("World")) # 9 (indexul primei apariții, -1 dacă nu e găsit)
print(s.count("l")) # 3
print("World" in s) # True (verificare apartenență)
print(s.startswith(" H"))# True
print(s.endswith("! ")) # True
# Splitare și join:
csv_line = "Ana,21,Cluj,Student"
campuri = csv_line.split(",") # ['Ana', '21', 'Cluj', 'Student']
print(" | ".join(campuri)) # 'Ana | 21 | Cluj | Student'
text = "Linia 1\nLinia 2\nLinia 3"
linii = text.splitlines() # ['Linia 1', 'Linia 2', 'Linia 3']
# Verificări:
print("123".isdigit()) # True
print("abc".isalpha()) # True
print("abc123".isalnum()) # True
print(" ".isspace()) # True
5.3 Formatare stringuri¶
nume = "Ana"
varsta = 21
medie = 9.567
# F-strings (Python 3.6+ — RECOMANDAT):
print(f"Numele: {nume}, Vârsta: {varsta}")
print(f"Media: {medie:.2f}") # 9.57 (2 zecimale)
print(f"Număr: {42:08d}") # 00000042 (padding cu zerouri)
print(f"{'stânga':<20}") # Aliniere stânga
print(f"{'dreapta':>20}") # Aliniere dreapta
print(f"{'centrat':^20}") # Aliniere centru
print(f"{1000000:,}") # 1,000,000 (separator mii)
print(f"Debug: {varsta = }") # Debug: varsta = 21 (Python 3.8+)
# Metoda .format():
print("Numele: {}, Vârsta: {}".format(nume, varsta))
print("Media: {:.2f}".format(medie))
# Operator % (stil C, legacy):
print("Numele: %s, Vârsta: %d, Media: %.2f" % (nume, varsta, medie))
6. Structuri de date liniare — Liste și Tablouri¶
6.1 Lista — structura de date fundamentală¶
# Lista = secvență ordonată, mutabilă, eterogenă
numere = [10, 20, 30, 40, 50]
mixt = [1, "doi", 3.0, True, None, [5, 6]] # Tipuri diferite
gol = []
# Acces:
print(numere[0]) # 10
print(numere[-1]) # 50
print(numere[1:4]) # [20, 30, 40]
# Modificare (listele SUNT mutabile):
numere[0] = 99
print(numere) # [99, 20, 30, 40, 50]
numere[1:3] = [200, 300] # Înlocuire slice
6.2 Operații pe liste¶
# Adăugare:
lista = [1, 2, 3]
lista.append(4) # [1, 2, 3, 4] — adaugă la final
lista.insert(0, 0) # [0, 1, 2, 3, 4] — inserează la index
lista.extend([5, 6]) # [0, 1, 2, 3, 4, 5, 6] — adaugă mai multe
# Ștergere:
lista.remove(3) # Șterge PRIMA apariție a valorii 3
del lista[0] # Șterge elementul de la indexul 0
ultim = lista.pop() # Scoate și returnează ultimul element
al_doilea = lista.pop(1) # Scoate și returnează elementul de la index 1
lista.clear() # Golește lista
# Căutare:
numere = [3, 1, 4, 1, 5, 9, 2, 6]
print(numere.index(4)) # 2 (indexul primei apariții)
print(numere.count(1)) # 2 (de câte ori apare)
print(4 in numere) # True
print(7 not in numere) # True
# Sortare:
numere.sort() # Sortare in-place (modifică lista originală)
numere.sort(reverse=True) # Sortare descrescătoare
sortata = sorted(numere) # Returnează o COPIE sortată (originalul intact)
# Inversare:
numere.reverse() # In-place
inversata = list(reversed(numere)) # Copie inversată
# Funcții utile:
print(len(numere)) # Lungimea
print(min(numere)) # Minimul
print(max(numere)) # Maximul
print(sum(numere)) # Suma
6.3 List comprehensions¶
# Comprehension = mod concis de a crea liste noi din secvențe existente
# Patratele numerelor de la 0 la 9:
patrate = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Cu filtrare (if):
pare = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Cu transformare condiționată (if-else):
etichete = ["par" if x % 2 == 0 else "impar" for x in range(5)]
# ['par', 'impar', 'par', 'impar', 'par']
# Comprehension imbricat (matrice → listă plată):
matrice = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [elem for rand in matrice for elem in rand]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Creare matrice identitate 3×3:
identitate = [[1 if i == j else 0 for j in range(3)] for i in range(3)]
# [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
6.4 Copiere liste — shallow vs. deep¶
import copy
original = [1, [2, 3], 4]
# Referință (NU copie — ambele variabile pointează la ACEEAȘI listă):
ref = original
ref[0] = 99
print(original) # [99, [2, 3], 4] — originalul MODIFICAT!
# Shallow copy (copiază lista, dar NU sub-listele):
shallow = original.copy() # sau: list(original) sau original[:]
shallow[0] = 1 # Nu afectează originalul
shallow[1][0] = 999 # AFECTEAZĂ originalul! (sub-lista e partajată)
print(original) # [99, [999, 3], 4]
# Deep copy (copie completă, recursivă):
deep = copy.deepcopy(original)
deep[1][0] = 0 # Nu afectează originalul
print(original[1][0]) # 999 (neschimbat de deep copy)
7. Tupluri, Mulțimi și Dicționare¶
7.1 Tupluri (Tuples)¶
# Tuplu = secvență ordonată, IMUTABILĂ
coordonate = (3, 4)
culori = ("rosu", "verde", "albastru")
singleton = (42,) # Virgula e necesară pentru tuplu cu 1 element!
# Acces (ca la liste):
print(culori[0]) # "rosu"
print(culori[-1]) # "albastru"
# Imutabilitate:
# culori[0] = "galben" # TypeError!
# Unpacking (destructurare):
x, y = coordonate # x=3, y=4
a, *rest, z = (1, 2, 3, 4, 5) # a=1, rest=[2, 3, 4], z=5
# Swap elegant:
a, b = 10, 20
a, b = b, a # Acum a=20, b=10
# Folosire ca chei de dicționar (listele NU pot fi chei):
distante = {
("București", "Cluj"): 450,
("București", "Iași"): 400,
}
7.2 Mulțimi (Sets)¶
# Mulțime = colecție NEORDONATĂ de elemente UNICE
fructe = {"măr", "pară", "cireașă", "măr"} # Duplicatele se elimină automat
print(fructe) # {'măr', 'pară', 'cireașă'}
# Creare din alte structuri (eliminare duplicate):
numere = [1, 3, 2, 3, 1, 4, 2, 5]
unice = set(numere) # {1, 2, 3, 4, 5}
# Operații mulțimi:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # {1, 2, 3, 4, 5, 6} Reuniune
print(a & b) # {3, 4} Intersecție
print(a - b) # {1, 2} Diferență
print(a ^ b) # {1, 2, 5, 6} Diferență simetrică
# Verificări:
print(3 in a) # True (verificare O(1) — foarte rapid!)
print({1, 2} <= a) # True (submulțime)
# frozenset = mulțime imutabilă (poate fi cheie de dict sau element de set):
fs = frozenset([1, 2, 3])
7.3 Dicționare (Dictionaries)¶
# Dicționar = colecție de perechi cheie-valoare (hashmap)
student = {
"nume": "Ana",
"varsta": 21,
"note": [9, 10, 8],
"activ": True
}
# Acces:
print(student["nume"]) # "Ana"
print(student.get("email", "N/A")) # "N/A" (valoare implicită dacă cheia lipsește)
# Modificare / Adăugare:
student["email"] = "ana@example.com" # Adaugă cheie nouă
student["varsta"] = 22 # Modifică valoare existentă
# Ștergere:
del student["activ"]
email = student.pop("email", None) # Scoate și returnează, None dacă lipsește
# Iterare:
for cheie in student: # Iterează peste chei
print(cheie, student[cheie])
for cheie, valoare in student.items(): # Iterează peste perechi
print(f"{cheie}: {valoare}")
for valoare in student.values(): # Doar valorile
print(valoare)
# Dict comprehension:
patrate = {x: x**2 for x in range(10)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
# Numărare frecvențe (pattern clasic):
text = "abracadabra"
frecvente = {}
for char in text:
frecvente[char] = frecvente.get(char, 0) + 1
print(frecvente) # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
# Sau cu Counter:
from collections import Counter
print(Counter(text)) # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
8. Lucrul cu fișiere¶
8.1 Citire și scriere¶
# Scriere într-un fișier:
with open("output.txt", "w", encoding="utf-8") as f:
f.write("Linia 1\n")
f.write("Linia 2\n")
f.write(f"Rezultat: {42}\n")
# 'with' asigură ÎNCHIDEREA automată a fișierului, chiar dacă apare o eroare.
# Fără 'with', ar trebui: f = open(...) ... f.close()
# Citire completă:
with open("output.txt", "r", encoding="utf-8") as f:
continut = f.read() # Tot fișierul ca un singur string
print(continut)
# Citire linie cu linie:
with open("output.txt", "r") as f:
for linie in f: # Eficient (nu încarcă tot fișierul în memorie)
print(linie.strip()) # strip() elimină '\n' de la final
# Citire toate liniile într-o listă:
with open("output.txt") as f:
linii = f.readlines() # ['Linia 1\n', 'Linia 2\n', ...]
# Append (adăugare la final fără a șterge conținutul existent):
with open("output.txt", "a") as f:
f.write("Linia adăugată\n")
# Moduri de deschidere:
# "r" — citire (implicit, eroare dacă fișierul nu există)
# "w" — scriere (creează sau SUPRASCRIE)
# "a" — append (adaugă la final)
# "x" — creare exclusivă (eroare dacă fișierul există deja)
# "rb" — citire binară
# "wb" — scriere binară
8.2 Lucrul cu CSV¶
import csv
# Scriere CSV:
studenti = [
{"nume": "Ana", "nota": 9.5},
{"nume": "Ion", "nota": 8.0},
{"nume": "Maria", "nota": 9.8},
]
with open("studenti.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["nume", "nota"])
writer.writeheader()
writer.writerows(studenti)
# Citire CSV:
with open("studenti.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
print(f"{row['nume']}: {row['nota']}")
# Lucrul cu JSON:
import json
data = {"nume": "Ana", "note": [9, 10, 8], "activ": True}
# Scriere JSON:
with open("data.json", "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Citire JSON:
with open("data.json", "r") as f:
data = json.load(f)
print(data["nume"]) # "Ana"
9. Tratarea excepțiilor și robustețea programelor¶
# Excepțiile sunt erori care apar la RUNTIME
# try / except / else / finally:
try:
numar = int(input("Număr: "))
rezultat = 100 / numar
except ValueError:
print("Input invalid — trebuie un număr întreg")
except ZeroDivisionError:
print("Nu se poate împărți la zero")
except Exception as e:
print(f"Eroare neașteptată: {e}")
else:
# Se execută DOAR dacă NU a apărut nicio excepție
print(f"Rezultat: {rezultat}")
finally:
# Se execută MEREU (indiferent dacă a fost excepție sau nu)
print("Operație terminată")
# Ridicarea excepțiilor:
def factorial(n):
if not isinstance(n, int):
raise TypeError(f"Expected int, got {type(n).__name__}")
if n < 0:
raise ValueError(f"Factorial nu e definit pentru numere negative: {n}")
if n <= 1:
return 1
return n * factorial(n - 1)
# Excepții custom:
class NotaInvalidaError(Exception):
def __init__(self, nota, message="Nota trebuie să fie între 1 și 10"):
self.nota = nota
self.message = message
super().__init__(self.message)
def seteaza_nota(nota):
if not 1 <= nota <= 10:
raise NotaInvalidaError(nota)
return nota
Ierarhia excepțiilor Python (parțial):
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── ValueError
├── TypeError
├── KeyError
├── IndexError
├── FileNotFoundError
├── ZeroDivisionError
├── AttributeError
├── ImportError
├── RuntimeError
├── StopIteration
└── OSError
├── FileExistsError
├── PermissionError
└── ConnectionError
PARTEA II — INTERMEDIAR¶
10. Recursivitate¶
10.1 Conceptul¶
O funcție recursivă se apelează pe sine însăși. Fiecare apel rezolvă o subproblemă mai mică, până la un caz de bază care se rezolvă direct.
def factorial(n):
"""n! = n × (n-1) × ... × 1, cu 0! = 1"""
if n <= 1: # Caz de bază
return 1
return n * factorial(n - 1) # Pas recursiv
# Execuție:
# factorial(4) = 4 * factorial(3)
# = 4 * 3 * factorial(2)
# = 4 * 3 * 2 * factorial(1)
# = 4 * 3 * 2 * 1
# = 24
def fibonacci(n):
"""Al n-lea termen Fibonacci (0, 1, 1, 2, 3, 5, 8, ...)."""
if n <= 0: return 0
if n == 1: return 1
return fibonacci(n - 1) + fibonacci(n - 2)
# ATENȚIE: fibonacci recursiv naiv are complexitate O(2^n)!
# Soluție: memoizare (cache):
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_fast(n):
if n <= 0: return 0
if n == 1: return 1
return fibonacci_fast(n - 1) + fibonacci_fast(n - 2)
print(fibonacci_fast(100)) # Instant! (fără cache: imposibil de calculat)
10.2 Exemple clasice¶
# Turnurile Hanoi:
def hanoi(n, sursa, dest, aux):
if n == 1:
print(f"Mută disc 1 de pe {sursa} pe {dest}")
return
hanoi(n - 1, sursa, aux, dest)
print(f"Mută disc {n} de pe {sursa} pe {dest}")
hanoi(n - 1, aux, dest, sursa)
hanoi(3, "A", "C", "B")
# Căutare binară recursivă:
def cautare_binara(lista, target, low=0, high=None):
if high is None:
high = len(lista) - 1
if low > high:
return -1 # Negăsit
mid = (low + high) // 2
if lista[mid] == target:
return mid
elif lista[mid] < target:
return cautare_binara(lista, target, mid + 1, high)
else:
return cautare_binara(lista, target, low, mid - 1)
# Parcurgere directoare (recursiv):
import os
def lista_fisiere(cale, indent=0):
for nume in sorted(os.listdir(cale)):
cale_completa = os.path.join(cale, nume)
print(" " * indent + nume)
if os.path.isdir(cale_completa):
lista_fisiere(cale_completa, indent + 1)
11. Algoritmi de sortare¶
# === Bubble Sort — O(n²) ===
def bubble_sort(lst):
n = len(lst)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if lst[j] > lst[j + 1]:
lst[j], lst[j + 1] = lst[j + 1], lst[j]
swapped = True
if not swapped:
break # Optimizare: oprire dacă e deja sortat
return lst
# === Selection Sort — O(n²) ===
def selection_sort(lst):
n = len(lst)
for i in range(n):
min_idx = i
for j in range(i + 1, n):
if lst[j] < lst[min_idx]:
min_idx = j
lst[i], lst[min_idx] = lst[min_idx], lst[i]
return lst
# === Insertion Sort — O(n²), bun pentru liste aproape sortate ===
def insertion_sort(lst):
for i in range(1, len(lst)):
key = lst[i]
j = i - 1
while j >= 0 and lst[j] > key:
lst[j + 1] = lst[j]
j -= 1
lst[j + 1] = key
return lst
# === Merge Sort — O(n log n), stabil ===
def merge_sort(lst):
if len(lst) <= 1:
return lst
mid = len(lst) // 2
left = merge_sort(lst[:mid])
right = merge_sort(lst[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# === Quick Sort — O(n log n) mediu, O(n²) worst case ===
def quick_sort(lst):
if len(lst) <= 1:
return lst
pivot = lst[len(lst) // 2]
left = [x for x in lst if x < pivot]
middle = [x for x in lst if x == pivot]
right = [x for x in lst if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
12. Algoritmi de căutare¶
# === Căutare liniară — O(n) ===
def cautare_liniara(lista, target):
for i, element in enumerate(lista):
if element == target:
return i
return -1
# === Căutare binară — O(log n) — necesită listă SORTATĂ ===
def cautare_binara(lista, target):
low, high = 0, len(lista) - 1
while low <= high:
mid = (low + high) // 2
if lista[mid] == target:
return mid
elif lista[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
# Cu modulul bisect:
import bisect
lista = [10, 20, 30, 40, 50]
pos = bisect.bisect_left(lista, 30) # 2 (indexul unde se află/s-ar insera)
bisect.insort(lista, 35) # Inserează menținând sortarea
13. Complexitate computațională (notația Big-O)¶
13.1 Clasele de complexitate¶
Timp Exemplu Notație
─────────────────────────────────────────────────────────────────
Constant Acces element din array O(1)
Logaritmic Căutare binară O(log n)
Liniar Căutare liniară, parcurgere O(n)
Linearitmic Merge sort, sorted() Python O(n log n)
Pătratic Bubble sort, selecție O(n²)
Cubic Înmulțire matrice naivă O(n³)
Exponențial Fibonacci recursiv naiv O(2ⁿ)
Factorial Permutări, TSP brute-force O(n!)
13.2 Complexitatea operațiilor Python¶
Structură Operație Complexitate
──────────────────────────────────────────────
list index [i] O(1)
list append O(1) amortizat
list insert(0, x) O(n) (shift-ează toate elementele)
list x in list O(n)
list sort() O(n log n) — Timsort
list len() O(1)
dict d[key] O(1) mediu
dict d[key] = val O(1) mediu
dict key in d O(1) mediu
dict del d[key] O(1) mediu
set x in s O(1) mediu
set add O(1) mediu
set reuniune O(len(s1) + len(s2))
str s[i] O(1)
str s1 + s2 O(len(s1) + len(s2))
str s1 in s2 O(len(s1) * len(s2))
14. Structuri de date fundamentale — Stive, Cozi, Liste înlănțuite¶
14.1 Stiva (Stack) — LIFO¶
# Stiva cu lista Python:
stack = []
stack.append(1) # push
stack.append(2)
stack.append(3)
print(stack.pop()) # pop → 3 (ultimul intrat, primul ieșit)
print(stack[-1]) # peek (vârf fără a scoate)
# Implementare cu clasă:
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def pop(self):
if self.is_empty():
raise IndexError("Pop from empty stack")
return self._items.pop()
def peek(self):
if self.is_empty():
raise IndexError("Peek at empty stack")
return self._items[-1]
def is_empty(self):
return len(self._items) == 0
def __len__(self):
return len(self._items)
# Aplicație: verificare paranteze echilibrate
def paranteze_valide(s):
stack = []
perechi = {')': '(', ']': '[', '}': '{'}
for char in s:
if char in "([{":
stack.append(char)
elif char in ")]}":
if not stack or stack[-1] != perechi[char]:
return False
stack.pop()
return len(stack) == 0
print(paranteze_valide("({[]})")) # True
print(paranteze_valide("([)]")) # False
14.2 Coada (Queue) — FIFO¶
from collections import deque
# deque este optimizat pentru operații la ambele capete: O(1)
queue = deque()
queue.append("A") # enqueue (la coadă)
queue.append("B")
queue.append("C")
print(queue.popleft()) # dequeue → "A" (primul intrat, primul ieșit)
# Implementare cu clasă:
class Queue:
def __init__(self):
self._items = deque()
def enqueue(self, item):
self._items.append(item)
def dequeue(self):
if self.is_empty():
raise IndexError("Dequeue from empty queue")
return self._items.popleft()
def peek(self):
return self._items[0]
def is_empty(self):
return len(self._items) == 0
def __len__(self):
return len(self._items)
14.3 Lista înlănțuită (Linked List)¶
class Node:
def __init__(self, data):
self.data = data
self.next = None # Pointer la următorul nod
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def prepend(self, data):
new_node = Node(data)
new_node.next = self.head
self.head = new_node
def delete(self, data):
if not self.head:
return
if self.head.data == data:
self.head = self.head.next
return
current = self.head
while current.next and current.next.data != data:
current = current.next
if current.next:
current.next = current.next.next
def __iter__(self):
current = self.head
while current:
yield current.data
current = current.next
def __repr__(self):
return " → ".join(str(item) for item in self) + " → None"
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.prepend(0)
print(ll) # 0 → 1 → 2 → 3 → None
ll.delete(2)
print(ll) # 0 → 1 → 3 → None
Comparație liste Python vs. linked list vs. array C:
Operație | Python list (array dinamic) | Linked List | Array C (fix)
─────────────────┼────────────────────────────┼────────────────┼──────────────
Acces [i] | O(1) | O(n) | O(1)
Append la final | O(1) amortizat | O(n) sau O(1)* | N/A (fix)
Insert la început| O(n) | O(1) | O(n)
Delete din mijloc| O(n) | O(1)* | O(n)
Căutare | O(n) | O(n) | O(n)
Memorie | Contiguă + overhead | Fragmentată | Contiguă, fixă
* dacă avem pointer la nodul anterior
15. Arbori și grafuri — noțiuni de bază¶
15.1 Arbore binar¶
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
class BinarySearchTree:
def __init__(self):
self.root = None
def insert(self, value):
if not self.root:
self.root = TreeNode(value)
else:
self._insert_rec(self.root, value)
def _insert_rec(self, node, value):
if value < node.value:
if node.left is None:
node.left = TreeNode(value)
else:
self._insert_rec(node.left, value)
else:
if node.right is None:
node.right = TreeNode(value)
else:
self._insert_rec(node.right, value)
def search(self, value):
return self._search_rec(self.root, value)
def _search_rec(self, node, value):
if node is None:
return False
if value == node.value:
return True
elif value < node.value:
return self._search_rec(node.left, value)
else:
return self._search_rec(node.right, value)
def inorder(self):
"""Parcurgere in-order: stânga → rădăcină → dreapta (sortată!)"""
result = []
self._inorder_rec(self.root, result)
return result
def _inorder_rec(self, node, result):
if node:
self._inorder_rec(node.left, result)
result.append(node.value)
self._inorder_rec(node.right, result)
bst = BinarySearchTree()
for val in [5, 3, 7, 1, 4, 6, 8]:
bst.insert(val)
print(bst.inorder()) # [1, 3, 4, 5, 6, 7, 8]
print(bst.search(4)) # True
print(bst.search(9)) # False
15.2 Graf — reprezentare și parcurgere¶
# Reprezentare: dicționar de adiacență
graf = {
"A": ["B", "C"],
"B": ["A", "D", "E"],
"C": ["A", "F"],
"D": ["B"],
"E": ["B", "F"],
"F": ["C", "E"]
}
# BFS (Breadth-First Search) — parcurgere pe lățime (coadă):
from collections import deque
def bfs(graf, start):
visited = set()
queue = deque([start])
visited.add(start)
order = []
while queue:
node = queue.popleft()
order.append(node)
for vecin in graf[node]:
if vecin not in visited:
visited.add(vecin)
queue.append(vecin)
return order
print(bfs(graf, "A")) # ['A', 'B', 'C', 'D', 'E', 'F']
# DFS (Depth-First Search) — parcurgere în adâncime (stivă/recursie):
def dfs(graf, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
result = [start]
for vecin in graf[start]:
if vecin not in visited:
result.extend(dfs(graf, vecin, visited))
return result
print(dfs(graf, "A")) # ['A', 'B', 'D', 'E', 'F', 'C']
16. Programare orientată pe obiecte — fundamente¶
class Student:
"""Modelează un student universitar."""
# Atribut de clasă (partajat de toate instanțele):
universitate = "Universitatea Tehnică"
def __init__(self, nume, varsta, note=None):
"""Constructor — inițializează o instanță nouă."""
self.nume = nume # Atribut de instanță
self.varsta = varsta
self.note = note if note else []
self._nr_matricol = None # Convenție: _ = "protejat"
def adauga_nota(self, nota):
if not 1 <= nota <= 10:
raise ValueError(f"Nota invalidă: {nota}")
self.note.append(nota)
@property
def media(self):
"""Proprietate calculată (se accesează ca un atribut)."""
if not self.note:
return 0.0
return sum(self.note) / len(self.note)
@property
def nr_matricol(self):
return self._nr_matricol
@nr_matricol.setter
def nr_matricol(self, value):
if self._nr_matricol is not None:
raise AttributeError("Numărul matricol nu poate fi schimbat")
self._nr_matricol = value
def __repr__(self):
return f"Student({self.nume!r}, {self.varsta}, note={self.note})"
def __str__(self):
return f"{self.nume} (media {self.media:.2f})"
def __eq__(self, other):
if not isinstance(other, Student):
return NotImplemented
return self.nume == other.nume and self.varsta == other.varsta
def __lt__(self, other):
return self.media < other.media
@classmethod
def din_string(cls, text):
"""Constructor alternativ (factory method)."""
parti = text.split(",")
return cls(parti[0].strip(), int(parti[1].strip()))
@staticmethod
def este_nota_valida(nota):
"""Nu depinde de instanță sau clasă."""
return isinstance(nota, (int, float)) and 1 <= nota <= 10
# Utilizare:
s1 = Student("Ana", 21)
s1.adauga_nota(9)
s1.adauga_nota(10)
print(s1) # Ana (media 9.50)
print(repr(s1)) # Student('Ana', 21, note=[9, 10])
s2 = Student.din_string("Ion, 22") # Constructor alternativ
print(Student.este_nota_valida(11)) # False
# Sortare studenti după medie:
studenti = [Student("A", 20, [8]), Student("B", 21, [10]), Student("C", 22, [7])]
print(sorted(studenti, reverse=True)) # Sortare descrescătoare după medie
PARTEA III — AVANSAT¶
17. OOP avansat — moștenire, polimorfism, compoziție¶
from abc import ABC, abstractmethod
# Clasă abstractă:
class Forma(ABC):
@abstractmethod
def arie(self) -> float: ...
@abstractmethod
def perimetru(self) -> float: ...
def __str__(self):
return f"{self.__class__.__name__}(arie={self.arie():.2f})"
# Moștenire:
class Cerc(Forma):
def __init__(self, raza):
self.raza = raza
def arie(self):
import math
return math.pi * self.raza ** 2
def perimetru(self):
import math
return 2 * math.pi * self.raza
class Dreptunghi(Forma):
def __init__(self, latime, inaltime):
self.latime = latime
self.inaltime = inaltime
def arie(self):
return self.latime * self.inaltime
def perimetru(self):
return 2 * (self.latime + self.inaltime)
class Patrat(Dreptunghi):
def __init__(self, latura):
super().__init__(latura, latura) # Apelează constructorul părinte
# Polimorfism — aceeași interfață, comportament diferit:
forme = [Cerc(5), Dreptunghi(3, 4), Patrat(6)]
for f in forme:
print(f"{f} — perimetru: {f.perimetru():.2f}")
# Cerc(arie=78.54) — perimetru: 31.42
# Dreptunghi(arie=12.00) — perimetru: 14.00
# Patrat(arie=36.00) — perimetru: 24.00
# Arie totală — funcționează cu ORICE Forma:
arie_totala = sum(f.arie() for f in forme)
18. Programare funcțională în Python¶
# map — aplică o funcție fiecărui element:
numere = [1, 2, 3, 4, 5]
patrate = list(map(lambda x: x**2, numere)) # [1, 4, 9, 16, 25]
# filter — selectează elementele care satisfac o condiție:
pare = list(filter(lambda x: x % 2 == 0, numere)) # [2, 4]
# reduce — reduce o secvență la o singură valoare:
from functools import reduce
suma = reduce(lambda acc, x: acc + x, numere, 0) # 15
produs = reduce(lambda acc, x: acc * x, numere, 1) # 120
# Compunere funcții:
def compose(*funcs):
"""Compune mai multe funcții: compose(f, g, h)(x) = f(g(h(x)))"""
return reduce(lambda f, g: lambda x: f(g(x)), funcs)
double = lambda x: x * 2
add_one = lambda x: x + 1
square = lambda x: x ** 2
transform = compose(square, add_one, double)
print(transform(3)) # square(add_one(double(3))) = square(7) = 49
# zip — combină secvențe element cu element:
nume = ["Ana", "Ion", "Maria"]
note = [9.5, 8.0, 9.8]
for n, nota in zip(nume, note):
print(f"{n}: {nota}")
# any / all:
print(any(x > 0 for x in [-1, -2, 3])) # True (cel puțin unul > 0)
print(all(x > 0 for x in [1, 2, 3])) # True (TOATE > 0)
19. Generatoare, iteratoare și comprehensions¶
# Generator = funcție care produce valori LAZY (la cerere, una câte una)
# Folosește 'yield' în loc de 'return'
def numara_pana_la(n):
i = 0
while i < n:
yield i # „Produce" o valoare, apoi se „suspendă"
i += 1
for x in numara_pana_la(5):
print(x) # 0, 1, 2, 3, 4
# Avantaj: nu stochează TOATĂ secvența în memorie
# range(1_000_000_000) funcționează, dar list(range(1_000_000_000)) → MemoryError
# Generator Fibonacci infinit:
def fibonacci():
a, b = 0, 1
while True: # Infinit! Se oprește doar când consumatorul se oprește
yield a
a, b = b, a + b
# Primele 10 numere Fibonacci:
from itertools import islice
print(list(islice(fibonacci(), 10))) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# Generator expression (ca list comprehension, dar lazy):
patrate_gen = (x**2 for x in range(1000000)) # Nu alocă memorie!
print(sum(patrate_gen)) # Suma pătratelor — O(1) memorie
# Dict comprehension:
inverse = {v: k for k, v in {"a": 1, "b": 2}.items()} # {1: 'a', 2: 'b'}
# Set comprehension:
vocale = {c for c in "hello world" if c in "aeiou"} # {'e', 'o'}
20. Expresii regulate (Regex)¶
import re
text = "Email-urile sunt ana@example.com și ion.popescu@gmail.com, sună la 0712345678."
# Căutare:
match = re.search(r'\d+', text) # Prima secvență de cifre
print(match.group()) # '0712345678'... nu, găsește prima!
# Toate potrivirile:
emailuri = re.findall(r'[\w.]+@[\w.]+\.\w+', text)
print(emailuri) # ['ana@example.com', 'ion.popescu@gmail.com']
telefoane = re.findall(r'\b07\d{8}\b', text)
print(telefoane) # ['0712345678']
# Înlocuire:
cenzurat = re.sub(r'[\w.]+@[\w.]+\.\w+', '[EMAIL ASCUNS]', text)
print(cenzurat)
# Validare:
def email_valid(email):
pattern = r'^[\w.+-]+@[\w-]+\.[\w.]+$'
return re.match(pattern, email) is not None
print(email_valid("test@example.com")) # True
print(email_valid("invalid@")) # False
# Caractere speciale regex:
# . Orice caracter (except newline)
# \d Cifră [0-9]
# \w Word character [a-zA-Z0-9_]
# \s Whitespace [ \t\n\r]
# ^ Început de string
# $ Sfârșit de string
# * 0 sau mai multe repetiții
# + 1 sau mai multe repetiții
# ? 0 sau 1 repetiție (opțional)
# {n,m} Între n și m repetiții
# [] Set de caractere
# () Grup de captură
# | Sau (alternativă)
# \b Word boundary
21. Module, pachete și ecosistemul Python¶
# === Importuri ===
import math # Import complet
from math import sqrt, pi # Import selectiv
from datetime import datetime as dt # Alias
import os.path # Submodul
# === Creare modul propriu ===
# utils.py:
def celsius_to_fahrenheit(c):
return c * 9/5 + 32
PI = 3.14159
# main.py:
from utils import celsius_to_fahrenheit
# === Structura unui pachet ===
# mypackage/
# ├── __init__.py (marchează directorul ca pachet)
# ├── core.py
# ├── helpers.py
# └── models/
# ├── __init__.py
# ├── user.py
# └── product.py
# from mypackage.models.user import User
Module standard utile¶
import os # Operații sistem de fișiere
import sys # Parametri sistem, argv
import math # Funcții matematice
import random # Numere aleatoare
import datetime # Date și ore
import json # Serializare JSON
import csv # Fișiere CSV
import re # Expresii regulate
import collections # Counter, defaultdict, deque, namedtuple
import itertools # Combinări, permutări, lanțuri
import functools # reduce, lru_cache, wraps
import pathlib # Căi de fișiere (modern)
import typing # Type hints
import dataclasses # Dataclass decorator
import unittest # Framework de testare
import argparse # Parsare argumente linie de comandă
import logging # Logging structurat
import copy # Copiere deep/shallow
# Biblioteci externe populare (instalate cu pip):
# requests — HTTP client simplu
# numpy — Calcul numeric, matrice
# pandas — Analiza datelor, DataFrames
# matplotlib — Grafice
# flask/django — Web frameworks
# sqlalchemy — ORM pentru baze de date
# pytest — Framework de testare
22. Introducere în testare și debugging¶
22.1 Unit testing cu pytest¶
# test_calculator.py
def aduna(a, b):
return a + b
def imparte(a, b):
if b == 0:
raise ValueError("Nu se poate împărți la zero")
return a / b
# === Teste ===
def test_aduna_numere_pozitive():
assert aduna(2, 3) == 5
def test_aduna_numere_negative():
assert aduna(-1, -2) == -3
def test_aduna_zero():
assert aduna(0, 5) == 5
def test_imparte_normal():
assert imparte(10, 2) == 5.0
def test_imparte_la_zero():
import pytest
with pytest.raises(ValueError, match="zero"):
imparte(10, 0)
# Parametrizare:
import pytest
@pytest.mark.parametrize("a, b, expected", [
(1, 1, 2),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_aduna_parametrizat(a, b, expected):
assert aduna(a, b) == expected
# Rulare: pytest test_calculator.py -v
22.2 Debugging¶
# 1. Print debugging (simplu dar eficient):
def functie_complexa(data):
print(f"DEBUG: data = {data}") # Temporar!
result = process(data)
print(f"DEBUG: result = {result}")
return result
# 2. Breakpoint (debugger interactiv):
def functie_cu_bug(lista):
for i, item in enumerate(lista):
breakpoint() # Oprește execuția aici → pdb prompt
# Comenzi pdb: n(ext), s(tep), c(ontinue), p(rint) expr, l(ist)
process(item)
# 3. Logging (pentru producție — NU print):
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s: %(message)s')
logger = logging.getLogger(__name__)
logger.debug("Valoare intermediară: %s", value) # Doar în mod debug
logger.info("Procesare completă")
logger.warning("Resurse aproape epuizate")
logger.error("Operație eșuată: %s", error_msg)
23. Bune practici și stil de cod¶
23.1 PEP 8 — ghidul de stil Python¶
# Denumire:
variabila_locala = 42 # snake_case
CONSTANTA_GLOBALA = 3.14 # SCREAMING_SNAKE_CASE
class NumeClasa: # PascalCase
def metoda_publica(self): # snake_case
self._atribut_intern = 1 # _ prefix = convenție privat
# Spațiere:
x = 1 # Spațiu în jurul = la atribuire
y = x + 2 # Spațiu în jurul operatorilor
f(arg1, arg2) # Fără spațiu înaintea parantezei funcției
lista[0] # Fără spațiu înaintea indexării
# Lungime linie: max 79 caractere (sau 120 în proiecte moderne)
# Linii lungi se sparg cu \ sau paranteze:
rezultat = (prima_valoare
+ a_doua_valoare
- a_treia_valoare)
# Importuri — ordinea:
# 1. Biblioteci standard
# 2. Biblioteci terțe
# 3. Module locale
import os
import sys
import requests
import numpy as np
from mypackage import utils
23.2 Principii de clean code¶
# 1. Funcții mici, cu un singur scop
# ❌ O funcție face totul:
def proceseaza_si_salveaza_si_trimite(data): ...
# ✅ Funcții separate:
def valideaza(data): ...
def proceseaza(data): ...
def salveaza(data): ...
# 2. Denumiri descriptive
# ❌ x, d, temp, data2, flag
# ✅ temperatura_celsius, distanta_km, este_valid, studenti_activi
# 3. Evită numerele magice
# ❌ if varsta > 18:
# ✅ VARSTA_MAJORAT = 18
# if varsta > VARSTA_MAJORAT:
# 4. DRY (Don't Repeat Yourself)
# Dacă copiezi cod, extrage-l într-o funcție
# 5. Early return (evită nesting adânc)
# ❌
def proceseaza(data):
if data is not None:
if len(data) > 0:
if valideaza(data):
return rezultat
return None
# ✅
def proceseaza(data):
if data is None:
return None
if len(data) == 0:
return None
if not valideaza(data):
return None
return rezultat
# 6. Type hints (documentare + verificare):
def calculeaza_media(note: list[float]) -> float:
"""Calculează media aritmetică a listei de note."""
if not note:
return 0.0
return sum(note) / len(note)
Anexe¶
A. Comparație Python vs. C vs. Java — rezumat¶
| Aspect | Python | C | Java |
|---|---|---|---|
| Tipare | Dynamic, strong | Static, weak | Static, strong |
| Compilare | Interpretat | Compilat (mașină) | Compilat (bytecode JVM) |
| Memorie | Garbage collector | Manual (malloc/free) | Garbage collector |
| Sintaxă blocuri | Indentare | Acolade {} | Acolade {} |
| OOP | Opțional, flexibil | Nu (struct, nu clase) | Obligatoriu (totul=clasă) |
| Pointeri | Nu | Da (explicit) | Nu (referințe implicite) |
| Moștenire multiplă | Da | N/A | Nu (interfețe da) |
| Generice | Type hints (opțional) | Macro-uri / void* | Generice (erasure) |
| Performanță | Lentă (interpretat) | Foarte rapidă | Rapidă (JIT) |
| Ecosistem | pip, PyPI | Manual / package mgr | Maven, Gradle |
| Ideal pentru | Scripting, ML, web, edu | Sisteme, embedded, OS | Enterprise, Android |
B. Glosar de termeni¶
| Termen | Definiție |
|---|---|
| Variabilă | Nume asociat cu o valoare stocată în memorie |
| Tip de date | Categoria unei valori (int, str, list etc.) |
| Funcție | Bloc de cod reutilizabil cu nume, parametri și valoare returnată |
| Parametru | Variabilă în definiția funcției |
| Argument | Valoarea concretă pasată la apelul funcției |
| Iterare | Parcurgerea element cu element a unei secvențe |
| Recursivitate | Funcție care se apelează pe ea însăși |
| Excepție | Eroare detectată la runtime |
| Clasă | Șablon (blueprint) pentru crearea obiectelor |
| Obiect | Instanță a unei clase |
| Moștenire | Mecanism prin care o clasă preia atribute/metode de la alta |
| Polimorfism | Obiectele diferite răspund la aceeași interfață în mod propriu |
| Încapsulare | Ascunderea detaliilor interne, expunere prin interfață |
| Complexitate | Măsura resurselor (timp/spațiu) necesare unui algoritm |
| Mutabil | Obiect a cărui valoare poate fi schimbată după creare |
| Imutabil | Obiect a cărui valoare NU poate fi schimbată (str, tuple) |
| Generator | Funcție care produce valori lazy (la cerere) cu yield |
| Decorator | Funcție care modifică comportamentul altei funcții |
| Comprehension | Sintaxă concisă pentru crearea colecțiilor din iterabile |
Curs realizat ca material de referință pentru studenții anului I de informatică, automatică și calculatoare.