🐍 De la C++ la Python

Sistem de Gestionare Bibliotecă - Tutorial Complet

Concepte Fundamentale

🎯 Structuri în C++ vs Clase în Python

În C++, folosim struct pentru a grupa date și funcții. În Python, folosim class pentru același scop, dar cu o sintaxă mai simplă și mai flexibilă.

C++ Structură în C++
struct Student {
    int id;
    char first_name[100];
    char last_name[100];
    int age;

    void afisare() {
        cout << "Id:" << id << endl;
        cout << "First name:" << first_name << endl;
    }
};
Python Clasă în Python
class Student:
    def __init__(self, id, first_name, last_name, age):
        self.id = id
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    def afisare(self):
        print(f"Id: {self.id}")
        print(f"First name: {self.first_name}")
💡 Sfat: În Python, constructorul __init__ înlocuiește inițializarea manuală din C++. Variabilele membre sunt create dinamic cu self.nume_variabila.

🧪 Demo Interactiv (simulare JS)

Apasă butoanele pentru a simula câteva acțiuni ale unei biblioteci. (E doar o simulare în JavaScript pentru a vizualiza rezultatele rapid în pagină.)

(Aici apar rezultatele demoului)

Comparație Detaliată C++ vs Python

CaracteristicăC++Python
Declarare structură/clasăstruct NumeStruct { };class NumeClasa:
ConstructorConstructor explicit sau inițializare manualădef __init__(self, params):
MembriDeclarați explicit cu tipCreați dinamic în __init__
Arrayschar nume[100]String-uri sau liste: self.nume = ""
Funcții membrevoid functie() { }def functie(self):
Acces membriobiect.membruobiect.membru
String operationsstrcpy(dest, src)dest = src
Inputcin.getline(buffer, size)input("mesaj")
Conversie string-intatoi(string)int(string)

Exemplu Practic: Funcția de Adăugare

C++ Adăugare în C++
void adaugaCarte(Carte carte) {
    carti[nr_carti] = carte;
    nr_carti++;
}
Python Adăugare în Python
def adauga_carte(self, carte):
    self.carti.append(carte)
    # Nr de cărți se calculează automat cu len(self.carti)

Exerciții Practice

📝 Exercițiul 1: Crearea Clasei Student

Creați o clasă Student în Python cu următoarele cerințe:

  1. Atribute: id, nume, prenume, email, vârstă
  2. Metodă de afișare a tuturor informațiilor
  3. Metodă pentru actualizarea email-ului
  4. Metodă care verifică dacă studentul este major
class Student:
    def __init__(self, id, nume, prenume, email, varsta):
        self.id = id
        self.nume = nume
        self.prenume = prenume
        self.email = email
        self.varsta = varsta

    def afisare(self):
        print(f"ID: {self.id}")
        print(f"Nume complet: {self.nume} {self.prenume}")
        print(f"Email: {self.email}")
        print(f"Vârstă: {self.varsta} ani")

    def actualizeaza_email(self, email_nou):
        self.email = email_nou
        print(f"Email actualizat cu succes la: {self.email}")

    def este_major(self):
        return self.varsta >= 18

# Testare
student1 = Student(1, "Popescu", "Ion", "ion@email.com", 20)
student1.afisare()
print(f"Este major? {student1.este_major()}")

📝 Exercițiul 2: Clasa Carte

Implementați clasa Carte cu:

  1. Atribute: id, id_student, nume_carte, data_imprumut
  2. Metodă pentru calcularea zilelor de împrumut
  3. Metodă pentru verificarea întârzierii (>14 zile)
  4. Metodă __str__ pentru afișare formatată
from datetime import datetime, timedelta

class Carte:
    def __init__(self, id, id_student, nume_carte, data_imprumut=None):
        self.id = id
        self.id_student = id_student
        self.nume_carte = nume_carte
        self.data_imprumut = data_imprumut or datetime.now()

    def calculeaza_zile_imprumut(self):
        if isinstance(self.data_imprumut, str):
            data = datetime.strptime(self.data_imprumut, "%Y-%m-%d")
        else:
            data = self.data_imprumut
        return (datetime.now() - data).days

    def este_intarziata(self):
        return self.calculeaza_zile_imprumut() > 14

    def __str__(self):
        status = "⚠️ ÎNTÂRZIATĂ" if self.este_intarziata() else "✅ În termen"
        return f"[{self.id}] {self.nume_carte} - Student #{self.id_student} - {status}"

    def afisare(self):
        print(f"ID Carte: {self.id}")
        print(f"ID Student: {self.id_student}")
        print(f"Nume carte: {self.nume_carte}")
        print(f"Data împrumut: {self.data_imprumut}")
        print(f"Zile împrumut: {self.calculeaza_zile_imprumut()}")

# Testare
carte1 = Carte(1, 101, "Python pentru începători", "2024-10-01")
print(carte1)
carte1.afisare()

📝 Exercițiul 3: Clasa Biblioteca

Creați clasa Biblioteca care să gestioneze cărțile:

  1. Listă de cărți și metodă de adăugare
  2. Metodă pentru căutarea unei cărți după ID
  3. Metodă pentru afișarea cărților unui student
  4. Metodă pentru ștergerea unei cărți
  5. Statistici: număr total cărți, cărți întârziate
from datetime import datetime

class Biblioteca:
    def __init__(self):
        self.carti = []
        self._index_dupa_id = {}
        self._index_dupa_student = {}

    def adauga_carte(self, carte):
        if carte.id in self._index_dupa_id:
            raise ValueError(f"Există deja o carte cu id-ul {carte.id}")
        self.carti.append(carte)
        self._index_dupa_id[carte.id] = carte
        self._index_dupa_student.setdefault(carte.id_student, []).append(carte)

    def gaseste_dupa_id(self, id_carte):
        return self._index_dupa_id.get(id_carte)

    def carti_pentru_student(self, id_student):
        return list(self._index_dupa_student.get(id_student, []))

    def sterge_carte(self, id_carte):
        carte = self._index_dupa_id.pop(id_carte, None)
        if not carte:
            return False
        # Scoatem din lista principală
        self.carti = [c for c in self.carti if c.id != id_carte]
        # Scoatem din indexul pe student
        lista = self._index_dupa_student.get(carte.id_student, [])
        self._index_dupa_student[carte.id_student] = [c for c in lista if c.id != id_carte]
        if not self._index_dupa_student[carte.id_student]:
            self._index_dupa_student.pop(carte.id_student)
        return True

    def statistici(self):
        total = len(self.carti)
        intarziate = [c for c in self.carti if hasattr(c, 'este_intarziata') and c.este_intarziata()]
        pe_student = {sid: len(lst) for sid, lst in self._index_dupa_student.items()}
        return {
            'total_carti': total,
            'carti_intarziate': len(intarziate),
            'distributie_pe_student': pe_student
        }

# Testare rapidă
if __name__ == '__main__':
    from datetime import timedelta

    b = Biblioteca()
    b.adauga_carte(Carte(1, 101, 'Clean Code', datetime.now()))
    b.adauga_carte(Carte(2, 101, 'Design Patterns', datetime.now()))
    b.adauga_carte(Carte(3, 102, 'Fluent Python', datetime.now() - timedelta(days=20)))

    print('Toate cărțile studentului 101:')
    for c in b.carti_pentru_student(101):
        print(' -', c)

    print('Statistici:', b.statistici())
    b.sterge_carte(2)
    print('După ștergere, statistici:', b.statistici())

Soluție Completă (Python „gata de rulare”)

Varianta de mai jos combină clasele Student, Carte și Biblioteca într-un mic script demonstrativ.

Python app.py
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import List, Dict

@dataclass
class Student:
    id: int
    nume: str
    prenume: str
    email: str
    varsta: int

    def este_major(self) -> bool:
        return self.varsta >= 18

class Carte:
    def __init__(self, id: int, id_student: int, nume_carte: str, data_imprumut=None):
        self.id = id
        self.id_student = id_student
        self.nume_carte = nume_carte
        self.data_imprumut = data_imprumut or datetime.now()

    def calculeaza_zile_imprumut(self) -> int:
        if isinstance(self.data_imprumut, str):
            data = datetime.strptime(self.data_imprumut, "%Y-%m-%d")
        else:
            data = self.data_imprumut
        return (datetime.now() - data).days

    def este_intarziata(self) -> bool:
        return self.calculeaza_zile_imprumut() > 14

    def __str__(self) -> str:
        status = "⚠️ ÎNTÂRZIATĂ" if self.este_intarziata() else "✅ În termen"
        return f"[{self.id}] {self.nume_carte} - Student #{self.id_student} - {status}"

class Biblioteca:
    def __init__(self):
        self.carti: List[Carte] = []
        self._index_dupa_id: Dict[int, Carte] = {}
        self._index_dupa_student: Dict[int, List[Carte]] = {}

    def adauga_carte(self, carte: Carte) -> None:
        if carte.id in self._index_dupa_id:
            raise ValueError(f"ID duplicat: {carte.id}")
        self.carti.append(carte)
        self._index_dupa_id[carte.id] = carte
        self._index_dupa_student.setdefault(carte.id_student, []).append(carte)

    def gaseste_dupa_id(self, id_carte: int) -> Carte | None:
        return self._index_dupa_id.get(id_carte)

    def carti_pentru_student(self, id_student: int) -> List[Carte]:
        return list(self._index_dupa_student.get(id_student, []))

    def sterge_carte(self, id_carte: int) -> bool:
        carte = self._index_dupa_id.pop(id_carte, None)
        if not carte:
            return False
        self.carti = [c for c in self.carti if c.id != id_carte]
        lst = self._index_dupa_student.get(carte.id_student, [])
        self._index_dupa_student[carte.id_student] = [c for c in lst if c.id != id_carte]
        if not self._index_dupa_student[carte.id_student]:
            self._index_dupa_student.pop(carte.id_student)
        return True

    def statistici(self) -> dict:
        intarziate = [c for c in self.carti if c.este_intarziata()]
        return {
            'total_carti': len(self.carti),
            'carti_intarziate': len(intarziate),
            'distributie_pe_student': {sid: len(lst) for sid, lst in self._index_dupa_student.items()}
        }

if __name__ == '__main__':
    # Date de test
    s1 = Student(101, 'Popescu', 'Ana', 'ana@ex.com', 21)
    s2 = Student(102, 'Ionescu', 'Mihai', 'mihai@ex.com', 17)

    b = Biblioteca()
    b.adauga_carte(Carte(1, s1.id, 'Clean Code', datetime.now() - timedelta(days=10)))
    b.adauga_carte(Carte(2, s1.id, 'Design Patterns', datetime.now() - timedelta(days=30)))
    b.adauga_carte(Carte(3, s2.id, 'Fluent Python', datetime.now()))

    print('— Toate cărțile —')
    for c in b.carti:
        print(' ', c)

    print('\n— Cărți pentru studentul 101 —')
    for c in b.carti_pentru_student(101):
        print(' ', c)

    print('\n— Statistici —')
    print(b.statistici())

Tehnici Avansate și Pythonic

✅ Folosirea @dataclass + validări

Python dataclasses + proprietăți
from dataclasses import dataclass, field
from datetime import datetime
from typing import ClassVar

@dataclass
class Carte:
    MAX_ZILE: ClassVar[int] = 14

    id: int
    id_student: int
    nume_carte: str
    data_imprumut: datetime = field(default_factory=datetime.now)

    def __post_init__(self):
        if not self.nume_carte:
            raise ValueError("nume_carte nu poate fi gol")

    @property
    def zile(self) -> int:
        return (datetime.now() - self.data_imprumut).days

    @property
    def intarziata(self) -> bool:
        return self.zile > self.MAX_ZILE

💾 Persistență simplă în JSON

Python salvare/încărcare
import json
from pathlib import Path
from datetime import datetime

class BibliotecaPersistenta(Biblioteca):
    def salveaza(self, fisier: str):
        payload = [
            {
                'id': c.id,
                'id_student': c.id_student,
                'nume_carte': c.nume_carte,
                'data_imprumut': c.data_imprumut.strftime('%Y-%m-%d')
            } for c in self.carti
        ]
        Path(fisier).write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding='utf-8')

    def incarca(self, fisier: str):
        self.carti.clear(); self._index_dupa_id.clear(); self._index_dupa_student.clear()
        data = json.loads(Path(fisier).read_text(encoding='utf-8'))
        for row in data:
            c = Carte(
                row['id'], row['id_student'], row['nume_carte'],
                datetime.strptime(row['data_imprumut'], '%Y-%m-%d')
            )
            self.adauga_carte(c)

🧪 Teste unitare minimale (pytest)

Python test_biblioteca.py
from datetime import datetime, timedelta
from app import Biblioteca, Carte

def test_adaugare_si_statistici():
    b = Biblioteca()
    b.adauga_carte(Carte(1, 101, 'X', datetime.now() - timedelta(days=20)))
    b.adauga_carte(Carte(2, 101, 'Y', datetime.now()))

    stats = b.statistici()
    assert stats['total_carti'] == 2
    assert stats['carti_intarziate'] == 1