Clase
Clasele sunt baza programării orientate pe obiecte (OOP). O clasă definește un tip de date personalizat care grupează date (atribute) și funcționalități (metode).
Concepte fundamentale
- Clasă: un șablon/model pentru crearea obiectelor
- Obiect/Instanță: o realizare concretă a unei clase
- Atribute: variabile care aparțin clasei/obiectului
- Metode: funcții definite în interiorul clasei
Definirea unei clase
class Persoana:
# Constructor - se apelează la crearea obiectului
def __init__(self, nume, varsta):
self.nume = nume # atribut de instanță
self.varsta = varsta # atribut de instanță
# Metodă
def salut(self):
print(f"Salut, sunt {self.nume}!")
def prezentare(self):
return f"{self.nume}, {self.varsta} ani"
Crearea obiectelor
# Creare instanțe
persoana1 = Persoana("Ana", 25)
persoana2 = Persoana("Ion", 30)
# Accesare atribute
print(persoana1.nume) # Ana
print(persoana2.varsta) # 30
# Apelare metode
persoana1.salut() # Salut, sunt Ana!
print(persoana2.prezentare()) # Ion, 30 ani
# Modificare atribute
persoana1.varsta = 26
Parametrul self
self reprezintă referința către instanța curentă a clasei. Este primul parametru al oricărei metode și permite accesul la atributele și metodele obiectului.
class Dreptunghi:
def __init__(self, lungime, latime):
self.lungime = lungime
self.latime = latime
def aria(self):
return self.lungime * self.latime
def perimetru(self):
return 2 * (self.lungime + self.latime)
d = Dreptunghi(5, 3)
print(d.aria()) # 15
print(d.perimetru()) # 16
Atribute de clasă vs instanță
class Student:
# Atribut de clasă - partajat de toate instanțele
scoala = "Liceul Teoretic"
numar_studenti = 0
def __init__(self, nume, clasa):
# Atribute de instanță - unice pentru fiecare obiect
self.nume = nume
self.clasa = clasa
Student.numar_studenti += 1
s1 = Student("Ana", "10A")
s2 = Student("Ion", "10B")
print(s1.scoala) # Liceul Teoretic
print(s2.scoala) # Liceul Teoretic
print(Student.numar_studenti) # 2
Metode speciale (dunder methods)
Metodele cu __nume__ sunt metode speciale care definesc comportamente standard.
__str__ și __repr__
class Punct:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
# Pentru print() și str()
return f"Punct({self.x}, {self.y})"
def __repr__(self):
# Pentru debugging și reprezentare oficială
return f"Punct(x={self.x}, y={self.y})"
p = Punct(3, 4)
print(p) # Punct(3, 4)
print(repr(p)) # Punct(x=3, y=4)
Operatori aritmetici
Poți defini cum se comportă obiectele cu operatori (+, -, *, etc.):
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __str__(self):
return f"({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
print(v2 - v1) # (2, 2)
print(v1 * 3) # (3, 6)
print(v1 == v2) # False
__len__ și __getitem__
Pentru a permite len() și indexarea cu []:
class Colectie:
def __init__(self, elemente):
self.elemente = elemente
def __len__(self):
return len(self.elemente)
def __getitem__(self, index):
return self.elemente[index]
def __setitem__(self, index, valoare):
self.elemente[index] = valoare
c = Colectie([1, 2, 3, 4, 5])
print(len(c)) # 5
print(c[0]) # 1
c[0] = 10
print(c[0]) # 10
Încapsulare
Încapsularea înseamnă ascunderea detaliilor interne și expunerea doar a unei interfețe controlate.
Atribute private (convenție)
În Python, prefixul _ indică un atribut "protejat" (convenție, nu e forțat):
class ContBancar:
def __init__(self, titular, sold_initial):
self.titular = titular
self._sold = sold_initial # convenție: atribut "protejat"
def depune(self, suma):
if suma > 0:
self._sold += suma
def retrage(self, suma):
if 0 < suma <= self._sold:
self._sold -= suma
return True
return False
def get_sold(self):
return self._sold
cont = ContBancar("Ana", 1000)
cont.depune(500)
print(cont.get_sold()) # 1500
Property (getters/setters)
@property permite accesul la atribute cu validare:
class Cerc:
def __init__(self, raza):
self._raza = raza
@property
def raza(self):
return self._raza
@raza.setter
def raza(self, valoare):
if valoare > 0:
self._raza = valoare
else:
raise ValueError("Raza trebuie să fie pozitivă")
@property
def aria(self):
import math
return math.pi * self._raza ** 2
c = Cerc(5)
print(c.raza) # 5
print(c.aria) # 78.54...
c.raza = 10
print(c.aria) # 314.15...
Moștenire
O clasă poate moșteni atribute și metode de la o altă clasă (clasa părinte/de bază).
# Clasa părinte (de bază)
class Animal:
def __init__(self, nume):
self.nume = nume
def vorbeste(self):
pass
# Clase copil (derivate)
class Caine(Animal):
def vorbeste(self):
return f"{self.nume} zice: Ham!"
class Pisica(Animal):
def vorbeste(self):
return f"{self.nume} zice: Miau!"
caine = Caine("Rex")
pisica = Pisica("Whiskers")
print(caine.vorbeste()) # Rex zice: Ham!
print(pisica.vorbeste()) # Whiskers zice: Miau!
Extinderea constructorului
Folosești super() pentru a apela constructorul clasei părinte:
class Angajat:
def __init__(self, nume, salariu):
self.nume = nume
self.salariu = salariu
class Manager(Angajat):
def __init__(self, nume, salariu, departament):
super().__init__(nume, salariu) # apelează constructorul părintelui
self.departament = departament
def prezentare(self):
return f"{self.nume}, Manager {self.departament}"
m = Manager("Ion", 5000, "IT")
print(m.prezentare()) # Ion, Manager IT
print(m.salariu) # 5000
Verificare tip
print(isinstance(caine, Caine)) # True
print(isinstance(caine, Animal)) # True
print(isinstance(caine, Pisica)) # False
print(issubclass(Caine, Animal)) # True
print(issubclass(Caine, Pisica)) # False
Polimorfism
Polimorfismul permite ca același nume de metodă să aibă comportament diferit în clase diferite:
class Forma:
def aria(self):
pass
class Dreptunghi(Forma):
def __init__(self, lungime, latime):
self.lungime = lungime
self.latime = latime
def aria(self):
return self.lungime * self.latime
class Cerc(Forma):
def __init__(self, raza):
self.raza = raza
def aria(self):
import math
return math.pi * self.raza ** 2
# Funcție care lucrează cu orice formă
def afiseaza_aria(forma):
print(f"Aria: {forma.aria():.2f}")
forme = [Dreptunghi(5, 3), Cerc(4)]
for forma in forme:
afiseaza_aria(forma)
# Aria: 15.00
# Aria: 50.27
Metode de clasă și statice
class Utilitar:
valoare_clasa = 0
def __init__(self, valoare):
self.valoare = valoare
# Metodă de instanță - acces la self
def metoda_instanta(self):
return f"Valoare instanță: {self.valoare}"
# Metodă de clasă - acces la cls (clasa însăși)
@classmethod
def metoda_clasa(cls):
return f"Valoare clasă: {cls.valoare_clasa}"
@classmethod
def creeaza_cu_valoare_dubla(cls, valoare):
return cls(valoare * 2)
# Metodă statică - fără acces la self sau cls
@staticmethod
def metoda_statica(x, y):
return x + y
u = Utilitar(10)
print(u.metoda_instanta()) # Valoare instanță: 10
print(Utilitar.metoda_clasa()) # Valoare clasă: 0
print(Utilitar.metoda_statica(3, 4)) # 7
u2 = Utilitar.creeaza_cu_valoare_dubla(5)
print(u2.valoare) # 10
Aplicații practice
Sistem de gestiune bibliotecă
class Carte:
def __init__(self, titlu, autor, isbn):
self.titlu = titlu
self.autor = autor
self.isbn = isbn
self.disponibila = True
def __str__(self):
status = "disponibilă" if self.disponibila else "împrumutată"
return f"{self.titlu} de {self.autor} ({status})"
class Biblioteca:
def __init__(self):
self.carti = []
def adauga_carte(self, carte):
self.carti.append(carte)
def cauta_dupa_autor(self, autor):
return [c for c in self.carti if autor.lower() in c.autor.lower()]
def imprumuta(self, isbn):
for carte in self.carti:
if carte.isbn == isbn and carte.disponibila:
carte.disponibila = False
return True
return False
def returneaza(self, isbn):
for carte in self.carti:
if carte.isbn == isbn:
carte.disponibila = True
return True
return False
# Utilizare
biblioteca = Biblioteca()
biblioteca.adauga_carte(Carte("Ion", "Liviu Rebreanu", "123"))
biblioteca.adauga_carte(Carte("Mara", "Ioan Slavici", "456"))
for carte in biblioteca.carti:
print(carte)
Joc simplu - Personaje
class Personaj:
def __init__(self, nume, viata, atac):
self.nume = nume
self.viata = viata
self.atac = atac
def este_viu(self):
return self.viata > 0
def ataca(self, inamic):
if self.este_viu() and inamic.este_viu():
inamic.viata -= self.atac
print(f"{self.nume} atacă pe {inamic.nume} cu {self.atac} damage!")
class Erou(Personaj):
def __init__(self, nume, viata, atac, mana):
super().__init__(nume, viata, atac)
self.mana = mana
def vindeca(self, cantitate=20):
if self.mana >= 10:
self.viata += cantitate
self.mana -= 10
print(f"{self.nume} s-a vindecat cu {cantitate} HP!")
class Monstru(Personaj):
def atac_special(self, inamic):
damage = self.atac * 2
inamic.viata -= damage
print(f"{self.nume} folosește atac special: {damage} damage!")
# Simulare luptă
erou = Erou("Arthur", 100, 15, 50)
monstru = Monstru("Dragon", 80, 10)
erou.ataca(monstru)
monstru.atac_special(erou)
erou.vindeca()