Ressources PYA
TP : Comparaison de deux listes
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
# Intersection (elements commun)
intersection = set1 & set2 # Output: {3, 4}
# Union (tout les élements uniques)
union = set1 | set2 # Output: {1, 2, 3, 4, 5, 6}
# Difference (différences dans le set1 mais pas dans le set2)
difference = set1 - set2 # Output: {1, 2}
# Difference Symétrique (soit dans le set1 soit dans le set2)
symmetric_difference = set1 ^ set2 # Output: {1, 2, 5, 6}
TP : Ensemble d'entrée
Créer un programme qui demande à l'utilisateur d'entrer des noms et de les ajouter à une liste. Implémentez des fonctionnalités pour :
Fonction : input()
- Afficher tous les noms dans la liste.
- Supprimer un nom donné par l'utilisateur.
- Trier la liste par ordre alphabétique.
- Compter le nombre d'occurrences d'un nom spécifique.
- Rechercher si un nom donné est présent dans la liste.
TP : Mise en place d'une pile d'évènements
Sujet 1 : Créer un programme simulant une file d'attente pour un service client :
- Utiliser une
deque
pour ajouter les clients à la file d'attente. - Offrir la possibilité de traiter le premier client dans la file (supprimer de la tête).
- Ajouter une fonctionnalité pour ajouter un client prioritaire (ajouter à la tête de la deque).
- Afficher la file d'attente actuelle après chaque opération.
Sujet 2 : Implémentez un système de gestion de navigation (comme un navigateur web simple) avec la possibilité d'aller en avant et en arrière dans l'historique :
- Utilisez une
deque
pour stocker les pages visitées. - Permettez à l'utilisateur d'aller à une nouvelle page (ajouter à la deque).
- Gérer la navigation arrière et avant avec des méthodes spécifiques de la deque.
- Revenir a un moment spécifique de la navigation (par exemple 4 page en arrière)
TP : Mise en pratique dict
- Créer un programme qui analyse un texte et calcule la fréquence d'apparition de chaque mot :
- Lire un texte fourni par l'utilisateur.
- Stocker chaque mot comme clé d'un dictionnaire et le nombre d'apparitions comme valeur.
- Afficher les mots les plus fréquents.
- Implémentez un carnet d'adresses utilisant un
dict
:- Chaque contact a un nom comme clé et un numéro de téléphone comme valeur.
- Permettre l'ajout, la modification, et la suppression de contacts.
- Rechercher un contact par son nom.
- Afficher tous les contacts dans l'ordre alphabétique.
- Supression des contacts en double
TP : Mise en pratique globale
Créer une application de gestion de bibliothèque :
- Utiliser un
list
pour stocker les livres disponibles. - Un
set
pour suivre les genres de livres uniques dans la bibliothèque. - Un
deque
pour gérer les emprunts et les retours de livres (les premiers empruntés doivent être les premiers rendus). - Un
dict
pour gérer les informations des livres (titre, auteur, année, genre, nombre de livres). - Un
defaultdict(list)
pour regrouper les livres par auteur.
Règles à implémenter :
- L'ajout d'un livre contenant un genre non existant est interdit
- Un livre peut avoir plusieurs occurrences (e.g. deux livre avec le même titre)
- Les livres sont référencés par leur titre
- Un livre ne peut pas être emprunté plus de fois que d'exemplaire disponible (à vous de trouver le format de donnée pour l'implémentation)
- Un livre peut être rendu
- On doit pouvoir retrouver les livres par leur auteur
Optionnel
- On peut rendre les livres dans le désordre
- Si on supprime un genre tous les livres du genre sont supprimés
- Si on supprime un auteur tous les livres de l'auteur sont supprimés
TP Finaux
TP 1 : Manipulation de fichier
Partie 1 : Manipulation d’un Fichier CSV
1. Création d’un fichier CSV
Crée un fichier produits.csv contenant les données suivantes (avec un script python) :
ID | Nom du Produit | Catégorie | Prix (€) | Stock |
---|
1 | Ordinateur | Informatique | 1000 | 10 |
2 | Souris | Informatique | 20 | 50 |
3 | Clavier | Informatique | 30 | 40 |
4 | Téléphone | Téléphonie | 500 | 25 |
5 | Casque Audio | Audio | 100 | 15 |
Solution
import csv
# Création du fichier CSV
with open('produits.csv', 'w', newline='', encoding='utf-8') as fichier_csv:
writer = csv.writer(fichier_csv)
# Écriture de l'en-tête
writer.writerow(['ID', 'Nom du Produit', 'Catégorie', 'Prix (€)', 'Stock'])
# Écriture des données
writer.writerows([
[1, 'Ordinateur', 'Informatique', 1000, 10],
[2, 'Souris', 'Informatique', 20, 50],
[3, 'Clavier', 'Informatique', 30, 40],
[4, 'Téléphone', 'Téléphonie', 500, 25],
[5, 'Casque Audio', 'Audio', 100, 15],
])
- Lire et afficher le fichier en python
Solution
# Lecture du fichier CSV
with open('produits.csv', 'r', encoding='utf-8') as fichier_csv:
reader = csv.reader(fichier_csv)
for ligne in reader:
print(ligne)
Partie 2 : Manipulation d’un Fichier JSON
1. Conversion du CSV en JSON
Écrire un script qui lit le fichier produits.csv et le convertit en fichier JSON nommé produits.json.
Solution
import json
# Lecture du fichier CSV et conversion en liste de dictionnaires
produits = []
with open('produits.csv', 'r', encoding='utf-8') as fichier_csv:
reader = csv.DictReader(fichier_csv)
for ligne in reader:
produits.append(ligne)
# Écriture dans un fichier JSON
with open('produits.json', 'w', encoding='utf-8') as fichier_json:
json.dump(produits, fichier_json, indent=4, ensure_ascii=False)
2. Lecture et Modification du fichier JSON
Modifier le prix de tous les produits en appliquant une réduction de 10 %.
Solution
# Lecture du fichier JSON
with open('produits.json', 'r', encoding='utf-8') as fichier_json:
produits = json.load(fichier_json)
# Application de la réduction de 10 % sur les prix
for produit in produits:
produit['Prix (€)'] = round(float(produit['Prix (€)']) * 0.9, 2)
# Écriture des modifications dans le fichier JSON
with open('produits.json', 'w', encoding='utf-8') as fichier_json:
json.dump(produits, fichier_json, indent=4, ensure_ascii=False)
Partie 3 : Extraction et Analyse des Données
Extraction : Créer une liste des produits dont le stock est inférieur à 20.
Analyse : Afficher la somme totale des stocks.
Solution
# Extraction des produits avec un stock inférieur à 20
produits_stock_limite = [produit for produit in produits if int(produit['Stock']) < 20]
print("Produits avec stock inférieur à 20 :")
for produit in produits_stock_limite:
print(produit['Nom du Produit'], "-", produit['Stock'])
# Calcul de la somme totale des stocks
stock_total = sum(int(produit['Stock']) for produit in produits)
print(f"Stock total : {stock_total}")
Bonus : Exportation des Données Modifiées en CSV
Recréer un fichier CSV à partir des données modifiées du JSON.
Solution
# Écriture du fichier CSV à partir des données JSON
with open('produits_modifies.csv', 'w', newline='', encoding='utf-8') as fichier_csv:
writer = csv.DictWriter(fichier_csv, fieldnames=produits[0].keys())
writer.writeheader()
writer.writerows(produits)
TP 2 : Programmation Réseau avec Python
Partie 1 : Création d’un Serveur TCP
Le serveur doit écouter sur une adresse IP locale et un port spécifique, recevoir des messages d’un client, et répondre avec une confirmation.
Solution
import socket
# Création du serveur TCP
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serveur.bind(('127.0.0.1', 12345))
serveur.listen(1)
print("Serveur en attente de connexion...")
connexion, adresse = serveur.accept()
print(f"Connecté à {adresse}")
# Boucle pour recevoir et répondre aux messages
while True:
message = connexion.recv(1024).decode('utf-8')
if not message or message.lower() == 'quit':
print("Fermeture de la connexion.")
break
print(f"Message reçu : {message}")
connexion.sendall("Message reçu".encode('utf-8'))
connexion.close()
serveur.close()
Partie 2 : Création du client TCP
Le client doit se connecter au serveur, envoyer un message, puis afficher la réponse du serveur.
Solution
import socket
# Création du client TCP
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 12345))
# Envoi d'un message
message = input("Entrez un message à envoyer au serveur : ")
client.sendall(message.encode('utf-8'))
# Réception de la réponse
reponse = client.recv(1024).decode('utf-8')
print(f"Réponse du serveur : {reponse}")
client.close()
Partie 3 : Communication UDP
- Le serveur UDP
Solution
Le serveur UDP doit écouter sur un port et répondre à chaque message reçu.
import socket
# Création du serveur UDP
serveur = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serveur.bind(('127.0.0.1', 12345))
print("Serveur UDP en attente de messages...")
while True:
message, adresse = serveur.recvfrom(1024)
print(f"Message reçu de {adresse} : {message.decode('utf-8')}")
serveur.sendto("Message bien reçu".encode('utf-8'), adresse)
- Le client UDP
Le client UDP envoie un message et attend la réponse du serveur.
Solution
import socket
# Création du client UDP
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
message = input("Entrez un message à envoyer au serveur : ")
client.sendto(message.encode('utf-8'), ('127.0.0.1', 12345))
reponse, _ = client.recvfrom(1024)
print(f"Réponse du serveur : {reponse.decode('utf-8')}")
client.close()
Partie 4 : Analyse Simple des Réseaux
- Récupération d’une Adresse IP depuis un Nom de Domaine
Écrire un script qui demande à l’utilisateur un nom de domaine et affiche son adresse IP.
Solution
import socket
nom_domaine = input("Entrez un nom de domaine : ")
adresse_ip = socket.gethostbyname(nom_domaine)
print(f"L'adresse IP de {nom_domaine} est : {adresse_ip}")
- Vérification de Ports Ouverts
Écrire un script qui scanne les ports d’une machine locale pour vérifier s’ils sont ouverts.
Solution
import socket
hote = '127.0.0.1'
ports_a_verifier = [22, 80, 443, 12345]
for port in ports_a_verifier:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
resultat = s.connect_ex((hote, port))
if resultat == 0:
print(f"Port {port} : OUVERT")
else:
print(f"Port {port} : FERMÉ")
Partie 5 : Serveur Multi-Clients (Bonus)
Modifier le serveur TCP pour gérer plusieurs connexions en parallèle avec des threads.
Solution
import socket
import threading
def gerer_client(connexion, adresse):
print(f"Nouvelle connexion : {adresse}")
while True:
message = connexion.recv(1024).decode('utf-8')
if not message or message.lower() == 'quit':
break
print(f"Message de {adresse} : {message}")
connexion.sendall("Message reçu".encode('utf-8'))
connexion.close()
print(f"Connexion fermée : {adresse}")
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serveur.bind(('127.0.0.1', 12345))
serveur.listen(5)
print("Serveur en attente de connexions...")
while True:
connexion, adresse = serveur.accept()
thread = threading.Thread(target=gerer_client, args=(connexion, adresse))
thread.start()
TP 3 : Introduction IA avec tensorflow
Étape 1 : Chargement des données
- TensorFlow propose des datasets intégrés. Utilisez le dataset MNIST (chiffres manuscrits).
# Install: pip install tensorflow-datasets
import tensorflow_datasets as tfds
mnist_data = tfds.load("mnist")
# Charger les données et les séparer en ensembles d'entraînement et de test
mnist_train, mnist_test = mnist_data["train"], mnist_data["test"]
batch_size = 32
train_size = mnist_train.cardinality().numpy() * batch_size
test_size = mnist_test.cardinality().numpy() * batch_size
print(f"Taille du jeu d'entraînement : {train_size} échantillons")
print(f"Taille du jeu de test : {test_size} échantillons")
Étape 2 : Prétraitement des données
- Normalisez les images pour que les valeurs des pixels soient comprises entre 0 et 1.
- Transformez les labels (y) en vecteurs one-hot encodés.
def preprocess(features):
image = features["image"]
label = features["label"]
image = tf.cast(image, tf.float32) / 255.0 # Normalize to [0, 1]
return image, label
# Prepare the training and test datasets
batch_size = 32
mnist_train = mnist_train.map(preprocess).shuffle(10000).batch(batch_size)
mnist_test = mnist_test.map(preprocess).batch(batch_size)
Étape 3 : Création du modèle
- Créez un modèle simple avec une couche d'entrée flatten, une couche cachée dense avec activation ReLU, et une couche de sortie softmax.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten, Dense
model = Sequential([
Flatten(input_shape=(28, 28, 1)),
Dense(128, activation='relu'),
Dense(10, activation='softmax')
])
Étape 4 : Compilation et entraînement
- Compilez le modèle avec une fonction de perte
categorical_crossentropy
, un optimiseuradam
, et mesurez l’accuracy
. - Entraînez le modèle sur les données d’entraînement avec validation sur les données de test.
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(mnist_train, epochs=5)
Étape 5 : Évaluation du modèle
- Évaluez la performance du modèle sur le jeu de test.
- Affichez les résultats et tracez les courbes de perte et d’accuracy.
test_loss, test_acc = model.evaluate(mnist_test)
print(f"Précision sur le jeu de test : {test_acc:.2f}")
import matplotlib.pyplot as plt
# Evaluate the model
test_loss, test_acc = model.evaluate(mnist_test)
# Plot the results
metrics = ['Test Loss', 'Test Accuracy']
values = [test_loss, test_acc]
# Create a bar chart
plt.figure(figsize=(8, 5))
plt.bar(metrics, values, color=['skyblue', 'lightgreen'])
plt.ylim(0, 1) # Accuracy is between 0 and 1, so we set limits for better visualization
plt.title('Model Evaluation Metrics')
plt.ylabel('Value')
plt.text(0, values[0] + 0.02, f'{values[0]:.4f}', ha='center')
plt.text(1, values[1] + 0.02, f'{values[1]:.4f}', ha='center')
# Display the plot
plt.show()
TP 4 : Utilisation de Docker + Python (automatisation)
pip install docker
Partie 1 : Automatisation du Démarrage et de l’Arrêt des Conteneurs
1. Script Python pour Démarrer un Conteneur Docker
Le script suivant démarre un conteneur à partir d’une image donnée (par exemple, nginx
).
Solution
import docker
# Connexion au daemon Docker
client = docker.from_env()
def demarrer_conteneur(image_name):
try:
conteneur = client.containers.run(image_name, detach=True, name="mon_conteneur")
print(f"Conteneur démarré : {conteneur.short_id}")
except docker.errors.APIError as e:
print(f"Erreur lors du démarrage : {e}")
# Démarrer un conteneur nginx
demarrer_conteneur("nginx")
2. Script Python pour Arrêter et Supprimer un Conteneur
Le script suivant arrête et supprime le conteneur démarré précédemment.
Solution
def arreter_et_supprimer_conteneur(container_name):
try:
conteneur = client.containers.get(container_name)
conteneur.stop()
conteneur.remove()
print(f"Conteneur arrêté et supprimé : {container_name}")
except docker.errors.NotFound:
print(f"Conteneur {container_name} introuvable.")
except docker.errors.APIError as e:
print(f"Erreur : {e}")
# Arrêter et supprimer le conteneur nommé "mon_conteneur"
arreter_et_supprimer_conteneur("mon_conteneur")
Partie 2 : Gestion des Logs des Conteneurs
1. Affichage des Logs d’un Conteneur en Temps Réel
Ce script affiche les logs en continu pour un conteneur spécifique.
Solution
def afficher_logs(container_name):
try:
conteneur = client.containers.get(container_name)
for ligne in conteneur.logs(stream=True):
print(ligne.decode('utf-8').strip())
except docker.errors.NotFound:
print(f"Conteneur {container_name} introuvable.")
# Afficher les logs du conteneur nommé "mon_conteneur"
afficher_logs("mon_conteneur")
2. Sauvegarde des Logs dans un Fichier
Le script suivant enregistre les logs d’un conteneur dans un fichier.
Solution
def sauvegarder_logs(container_name, fichier_log):
try:
conteneur = client.containers.get(container_name)
with open(fichier_log, 'w') as fichier:
fichier.write(conteneur.logs().decode('utf-8'))
print(f"Logs sauvegardés dans {fichier_log}.")
except docker.errors.NotFound:
print(f"Conteneur {container_name} introuvable.")
# Sauvegarder les logs du conteneur "mon_conteneur" dans un fichier
sauvegarder_logs("mon_conteneur", "logs_conteneur.txt")
Partie 3 : Automatisation Avancée
1. Automatiser le Démarrage de Plusieurs Conteneurs
Démarre plusieurs conteneurs à partir d’une liste d’images.
def demarrer_conteneurs(images):
for image in images:
demarrer_conteneur(image)
# Démarrer des conteneurs pour plusieurs images
images = ["nginx", "redis", "alpine"]
demarrer_conteneurs(images)
- Nettoyage Automatique des Conteneurs Arrêtés
Ce script supprime tous les conteneurs arrêtés.
def nettoyage_conteneurs_arretes():
conteneurs = client.containers.list(all=True)
for conteneur in conteneurs:
if conteneur.status == 'exited':
print(f"Suppression du conteneur : {conteneur.name}")
conteneur.remove()
# Nettoyage des conteneurs arrêtés
nettoyage_conteneurs_arretes()
Partie 4 : Créer un Service Automatisé avec systemd
1. Fichier de Service systemd
Crée un fichier /etc/systemd/system/gestion_docker.service :
[Unit]
Description=Service d'automatisation Docker
After=docker.service
[Service]
ExecStart=/usr/bin/python3 /chemin/vers/script_docker.py
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable gestion_docker.service
sudo systemctl start gestion_docker.service
Calcul Scientifique avec Python
pip install numpy scipy matplotlib
Partie 1 : Manipulation des Matrices avec NumPy
1. Création et Opérations sur les Matrices
Crée deux matrices et effectue des opérations basiques comme l'addition, la multiplication, et le calcul du déterminant.
import numpy as np
# Création de matrices
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
B = np.array([[9, 8, 7], [6, 5, 4], [3, 2, 1]])
# Addition
C = A + B
print("Addition des matrices :\n", C)
# Multiplication élément par élément
D = A * B
print("Multiplication élément par élément :\n", D)
# Produit matriciel
E = np.dot(A, B)
print("Produit matriciel :\n", E)
# Calcul du déterminant
det_A = np.linalg.det(A)
print("Déterminant de A :", det_A)
2. Résolution d’un Système Linéaire
Résolvons le système d’équations suivant :
2x+y−z=8−3x−y+2z=−11−2x+y+2z=−3
# Matrice des coefficients
coefficients = np.array([[2, 1, -1], [-3, -1, 2], [-2, 1, 2]])
# Matrice des constantes
constantes = np.array([8, -11, -3])
# Résolution du système
solution = np.linalg.solve(coefficients, constantes)
print("Solution du système : x = {}, y = {}, z = {}".format(*solution))
Partie 2 : Calculs Avancés avec SciPy
1. Calcul d’Intégrale
Calculons l’intégrale de la fonction f(x)=x2f(x) = x^2f(x)=x2 entre 0 et 4.
from scipy.integrate import quad
# Définition de la fonction
def f(x):
return x**2
# Calcul de l'intégrale
resultat, erreur = quad(f, 0, 4)
print("Résultat de l'intégrale :", resultat)
2. Résolution d’une Équation Différentielle
Résolvons l’équation différentielle suivante :
dx / dy=−2y ,y(0)=1
from scipy.integrate import solve_ivp
# Définition de l'équation différentielle
def equation(t, y):
return -2 * y
# Résolution de l'équation
solution = solve_ivp(equation, [0, 5], [1], t_eval=np.linspace(0, 5, 100))
print("Solution :\n", solution.y[0])
Partie 3 : Visualisation des Données avec Matplotlib
1. Tracé d’une Fonction
Traçons la fonction f(x)=sin(x)
import matplotlib.pyplot as plt
# Définition des données
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
# Tracé
plt.plot(x, y, label='sin(x)')
plt.title('Tracé de sin(x)')
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.legend()
plt.grid(True)
plt.show()
2. Visualisation des Solutions de l’Équation Différentielle
Représentons graphiquement la solution obtenue précédemment.
# Tracé de la solution
plt.plot(solution.t, solution.y[0], label='Solution de dy/dx = -2y')
plt.title('Solution de l\'équation différentielle')
plt.xlabel('Temps (t)')
plt.ylabel('y(t)')
plt.legend()
plt.grid(True)
plt.show()
Partie 4 : Analyse Statistique
1. Calcul de Statistiques de Base
Analyse des données suivantes : [10, 20, 30, 40, 50].
# Données
donnees = np.array([10, 20, 30, 40, 50])
# Calcul des statistiques
moyenne = np.mean(donnees)
mediane = np.median(donnees)
ecart_type = np.std(donnees)
print(f"Moyenne : {moyenne}, Médiane : {mediane}, Écart-type : {ecart_type}")
TP : Connexion avec la base de donnée MYSQL
pip install mysql-connector-python
Partie 1 : Connexion à une Base de Données MySQL
Établissons une connexion à une base de données MySQL locale.
import mysql.connector
# Connexion à MySQL
conn = mysql.connector.connect(
host="localhost",
user="root", # Remplacez par votre nom d'utilisateur
password="password" # Remplacez par votre mot de passe
)
if conn.is_connected():
print("Connexion réussie à MySQL")
else:
print("Échec de la connexion")
Partie 2 : Création d'une Base de Données et d'une Table
Créons une base de données nommée tp_mysql et une table etudiants.
# Création d'un curseur
cursor = conn.cursor()
# Création de la base de données
cursor.execute("CREATE DATABASE IF NOT EXISTS tp_mysql")
# Connexion à la base de données créée
conn.database = "tp_mysql"
# Création de la table
cursor.execute("""
CREATE TABLE IF NOT EXISTS etudiants (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(255),
age INT,
filiere VARCHAR(255)
)
""")
print("Table 'etudiants' créée.")
Partie 3 : Insertion de Données
Ajoutons des enregistrements dans la table etudiants.
# Requête d'insertion
sql = "INSERT INTO etudiants (nom, age, filiere) VALUES (%s, %s, %s)"
valeurs = [
("Alice", 20, "Informatique"),
("Bob", 22, "Mathématiques"),
("Charlie", 21, "Physique")
]
# Exécution de la requête
cursor.executemany(sql, valeurs)
conn.commit()
print(f"{cursor.rowcount} enregistrements insérés.")
Partie 4 : Lecture des Données
Récupérons les données de la table etudiants.
# Requête de sélection
cursor.execute("SELECT * FROM etudiants")
resultats = cursor.fetchall()
# Affichage des résultats
for etudiant in resultats:
print(etudiant)
Partie 5 : Mise à Jour des Données
Modifions l'age d'un étudiant
# Requête de mise à jour
sql = "UPDATE etudiants SET age = %s WHERE nom = %s"
valeurs = (23, "Alice")
# Exécution de la requête
cursor.execute(sql, valeurs)
conn.commit()
print(f"{cursor.rowcount} enregistrement(s) mis à jour.")
Partie 6 : Suppression de Données
Supprimons un étudiant de la table.
# Requête de suppression
sql = "DELETE FROM etudiants WHERE nom = %s"
valeurs = ("Bob",)
# Exécution de la requête
cursor.execute(sql, valeurs)
conn.commit()
print(f"{cursor.rowcount} enregistrement(s) supprimé(s).")
Partie 7 : Gestion des Erreurs et Fermeture de la Connexion
Assurons-nous de gérer les erreurs et de fermer proprement la connexion.
# Gestion des erreurs
try:
cursor.execute("SELECT * FROM etudiants")
except mysql.connector.Error as err:
print(f"Erreur : {err}")
# Fermeture des connexions
cursor.close()
conn.close()
print("Connexion fermée.")
Exercices Complémentaires :
Ajoutez de nouvelles colonnes dans la table etudiants pour stocker des informations supplémentaires, comme l'adresse ou le numéro de téléphone.
Effectuez des requêtes filtrées pour récupérer uniquement les étudiants d’une certaine filière.
Ajoutez des contraintes comme des clés étrangères en créant une nouvelle table.