Cours 1 : variables, if, fonctions, récursivité, ordre supérieur.
Cours 2 : listes 1D, for, while, compréhensions,
polynômes, vecteurs, signaux.
Aujourd'hui on ajoute trois super-pouvoirs : les tableaux
à deux dimensions (matrices, images), la manipulation de texte (découpage,
fusion, cryptage), et la lecture de fichiers CSV avec les outils du
pauvre (c'est-à-dire sans importer csv ni
pandas). Pourquoi ? Parce que comprendre ce qui se cache sous
le capot, c'est ça qui fait de vous un vrai ingénieur.
Un tableau 2D, c'est une liste dont chaque élément est lui-même une liste (de même taille). Imaginez un tableau Excel, une grille de pixels, ou un échiquier.
# Création manuelle d'une matrice 3×3
matrice = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# Accès : matrice[ligne][colonne]
print(matrice[0][0]) # 1 — première ligne, première colonne
print(matrice[1][2]) # 6 — deuxième ligne, troisième colonne
print(matrice[2]) # [7, 8, 9] — toute la troisième ligne
On ne peut pas écrire [[0] * 3] * 3 pour créer une grille 3×3
de zéros. Pourquoi ? Parce que les trois lignes seraient en fait la
même liste en mémoire. Un bug mémorable.
# BONNE méthode — compréhension de liste
lignes, colonnes = 3, 4
grille = [[0 for _ in range(colonnes)] for _ in range(lignes)]
print(grille)
# [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
grille[0][0] = 42 # ne modifie que la première ligne
print(grille)
# [[42, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] ✓
grille = [[0] * 4] * 3 crée UNE seule ligne [0,0,0,0]
et la répète 3 fois (même référence). Modifier
grille[0][0] modifierait toutes les lignes.
C'est le piège numéro 1 des tableaux 2D en Python. Souvenez-vous-en.
def affiche_matrice(m):
"""Affiche une matrice ligne par ligne."""
for ligne in m:
print(ligne)
def parcours_indices(m):
"""Parcourt avec indices (utile pour modifier)."""
for i in range(len(m)):
for j in range(len(m[i])):
print("m[", i, "][", j, "] =", m[i][j])
affiche_matrice(matrice)
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
def addition_matrice(A, B):
"""Additionne deux matrices de mêmes dimensions."""
lignes = len(A)
colonnes = len(A[0])
return [[A[i][j] + B[i][j] for j in range(colonnes)] for i in range(lignes)]
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
affiche_matrice(addition_matrice(A, B))
# [6, 8]
# [10, 12]
def transpose(m):
"""Transpose une matrice : lignes ↔ colonnes."""
lignes = len(m)
colonnes = len(m[0])
# On crée une matrice colonnes×lignes
return [[m[i][j] for i in range(lignes)] for j in range(colonnes)]
M = [[1, 2, 3],
[4, 5, 6]]
affiche_matrice(transpose(M))
# [1, 4]
# [2, 5]
# [3, 6]
def produit_matrice(A, B):
"""Multiplie deux matrices A (n×p) et B (p×m)."""
n, p = len(A), len(A[0])
p2, m = len(B), len(B[0])
if p != p2:
raise ValueError("Dimensions incompatibles pour le produit")
# Résultat : n × m
C = [[0 for _ in range(m)] for _ in range(n)]
for i in range(n):
for j in range(m):
for k in range(p):
C[i][j] = C[i][j] + A[i][k] * B[k][j]
return C
A = [[1, 2], [3, 4]] # 2×2
B = [[2, 0], [1, 2]] # 2×2
affiche_matrice(produit_matrice(A, B))
# [4, 4] → 1×2 + 2×1 = 4, 1×0 + 2×2 = 4
# [10, 8] → 3×2 + 4×1 = 10, 3×0 + 4×2 = 8
for imbriquées. On appelle ça un produit
matriciel naïf — O(n³). C'est lent, c'est moche, mais ça marche.
Les bibliothèques comme NumPy utilisent des algorithmes bien plus
performants (Strassen, multiplication par blocs, blas). Mais vous,
vous avez le mérite d'avoir écrit les trois boucles vous-même.
C'est comme cuisiner un œuf à la coque : tout le monde sait le faire,
mais personne ne sait comment fonctionne vraiment une poule.
def identite(n):
"""Génère la matrice identité n×n."""
return [[1 if i == j else 0 for j in range(n)] for i in range(n)]
affiche_matrice(identite(4))
# [1, 0, 0, 0]
# [0, 1, 0, 0]
# [0, 0, 1, 0]
# [0, 0, 0, 1]
La matrice identité, c'est le « 1 » des matrices. Multiplier une matrice par l'identité, c'est comme multiplier un nombre par 1 : ça ne change rien. C'est rassurant. Dans un monde chaotique, l'identité est une constante.
Une image en niveaux de gris (256 nuances), c'est une matrice de nombres : chaque case est un pixel dont la valeur (0–255) représente l'intensité lumineuse. 0 = noir, 255 = blanc.
from random import randint def image_noir(hauteur, largeur): """Image entièrement noire (0 partout).""" return [[0 for _ in range(largeur)] for _ in range(hauteur)] def image_bruit(hauteur, largeur): """Image aléatoire (bruit, comme une vieille télé sans antenne).""" return [[randint(0, 255) for _ in range(largeur)] for _ in range(hauteur)] def negatif(image): """Inverse les couleurs : 0→255, 255→0.""" return [[255 - pixel for pixel in ligne] for ligne in image] def affiche_ascii(image): """Affiche une image en ASCII art (approximatif).""" for ligne in image: ligne_ascii = "" for pixel in ligne: if pixel > 200: ligne_ascii = ligne_ascii + "█" elif pixel > 100: ligne_ascii = ligne_ascii + "▓" elif pixel > 50: ligne_ascii = ligne_ascii + "▒" else: ligne_ascii = ligne_ascii + "░" print(ligne_ascii) # Testons avec un petit dégradé image = [[i * 2 for i in range(20)] for _ in range(8)] affiche_ascii(image)
@ = vous, D = dragon.
Tout un univers dans 256 caractères.
def seuillage(image, seuil=128):
"""Transforme en noir & blanc : 0 si < seuil, 255 sinon."""
return [[255 if pixel >= seuil else 0 for pixel in ligne] for ligne in image]
# Appliquer un seuil, c'est la base du traitement d'image —
# les scanners de documents, la reconnaissance de plaques
# d'immatriculation, et les filtres Instagram les plus basiques
# fonctionnent comme ça.
Les chaînes de caractères (str), on les utilise depuis le
premier cours. Mais on peut faire bien plus que les
afficher : découper, fusionner, chiffrer, analyser.
phrase = "Python est le meilleur langage pour l'IA"
# Découpage : str.split() → liste de mots
mots = phrase.split()
print(mots)
# ['Python', 'est', 'le', 'meilleur', 'langage', 'pour', "l'IA"]
# Découpage avec séparateur personnalisé
data = "2024-03-15;42;3.14;true"
champs = data.split(";")
print(champs) # ['2024-03-15', '42', '3.14', 'true']
# Fusion : séparateur.join(liste)
phrase_again = " ".join(mots)
print(phrase_again) # "Python est le meilleur langage pour l'IA"
csv_ligne = ",".join(champs)
print(csv_ligne) # "2024-03-15,42,3.14,true"
split et join sont deux des méthodes les plus
utiles de Python. Retenez-les comme votre prénom et votre mot de passe
(sauf que split et join vous ne les oublierez
pas). split transforme une chaîne en liste ;
join fait l'inverse.
def cesar(message, decalage):
"""Chiffre (ou déchiffre) un message par décalage de César.
Arguments :
message — str, le texte à transformer
decalage — int, positif = chiffrer, négatif = déchiffrer
Retourne :
str, le message transformé
"""
alphabet = "abcdefghijklmnopqrstuvwxyz"
resultat = ""
for car in message.lower():
if car in alphabet:
pos = alphabet.index(car)
nouvelle_pos = (pos + decalage) % len(alphabet)
resultat = resultat + alphabet[nouvelle_pos]
else:
# On laisse les espaces et la ponctuation inchangés
resultat = resultat + car
return resultat
message = "Bonjour les futurs ingenieurs"
crypte = cesar(message, 3)
print("Crypté :", crypte)
decrypte = cesar(crypte, -3)
print("Décrypté :", decrypte)
def chiffre_substitution(message, cle):
"""Substitution mono-alphabétique.
cle est une chaîne de 26 lettres représentant l'alphabet permuté.
"""
alphabet = "abcdefghijklmnopqrstuvwxyz"
table = str.maketrans(alphabet, cle)
return message.lower().translate(table)
# On génère une clé aléatoire en mélangeant l'alphabet
import random
alphabet = list("abcdefghijklmnopqrstuvwxyz")
random.shuffle(alphabet)
cle = "".join(alphabet)
print("Clé :", cle)
print(chiffre_substitution("message secret", cle))
La substitution mono-alphabétique est plus forte que César (26! ≈ 4×10²⁶ clés possibles), mais elle se casse facilement avec une analyse fréquentielle : dans un texte français, le 'e' est la lettre la plus courante (~14%), suivie du 'a', du 'i', etc. En regardant les lettres les plus fréquentes du texte chiffré, on devine la substitution.
def xor_bytes(donnees, cle):
"""Crypte/décrypte par XOR avec une clé (répétée si nécessaire)."""
resultat = bytearray()
for i, octet in enumerate(donnees):
resultat.append(octet ^ cle[i % len(cle)])
return bytes(resultat)
# Testons avec un message en bytes
message = "Message ultra secret".encode("utf-8")
cle = "clef".encode("utf-8")
crypte = xor_bytes(message, cle)
print("Crypté (bytes) :", crypte)
decrypte = xor_bytes(crypte, cle)
print("Décrypté :", decrypte.decode("utf-8"))
# Propriété magique du XOR : (m XOR k) XOR k = m
# Le même code sert à chiffrer ET déchiffrer !
^) est l'opération
magique de la cryptographie. Si vous faites a ^ b ^ b, vous
retrouvez a. C'est pour ça que le XOR est la base de
tous les chiffrements modernes (AES, cha-cha, etc.).
Et aussi que a ^ a = 0, ce qui en fait un très bon moyen
de mettre une variable à zéro (les vieux programmeurs assembleur
adorent ça).
def frequences_lettres(texte):
"""Calcule la fréquence de chaque lettre dans le texte."""
freq = {}
for car in texte.lower():
if car.isalpha():
freq[car] = freq.get(car, 0) + 1
return freq
# Test sur un vrai texte
extrait = "L'informatique est la science du traitement automatique de l'information"
freq = frequences_lettres(extrait)
# Trier par fréquence décroissante
trie = sorted(freq.items(), key=lambda x: -x[1])
for lettre, compte in trie[:5]:
print(lettre, ":", "■" * compte, "(" + str(compte) + ")")
Le 'e' et le 'i' dominent. Coïncidence ? Non. La langue française est ainsi faite. Si vous voyez 'z' arriver en tête, vous avez probablement un texte en espéranto ou un message chiffré.
Le format CSV (Comma-Separated Values) est l'un des plus répandus pour échanger des données tabulaires. Chaque ligne est un enregistrement, les champs sont séparés par des virgules (ou points-virgules, selon le pays — merci l'Europe).
def lit_csv(chemin, separateur=","):
"""Lit un fichier CSV et retourne une liste de dictionnaires.
La première ligne est supposée contenir les noms de colonnes.
"""
with open(chemin, "r", encoding="utf-8") as f:
lignes = f.read().strip().split("\n")
# La première ligne contient les en-têtes
en_tetes = lignes[0].split(separateur)
resultat = []
for ligne in lignes[1:]:
if ligne.strip() == "":
continue # ignorer les lignes vides
valeurs = ligne.split(separateur)
# On crée un dictionnaire : {en_tete: valeur, ...}
dico = {}
for i, en_tete in enumerate(en_tetes):
dico[en_tete.strip()] = valeurs[i].strip()
resultat.append(dico)
return resultat
# Supposons qu'on ait un fichier etudiants.csv :
# nom,note,age
# Alice,14,22
# Bob,8,23
# Charlie,16,21
# Pour tester sans fichier, on simule le contenu :
contenu_csv = """nom,note,age
Alice,14,22
Bob,8,23
Charlie,16,21
"""
with open(...) as f: est la façon propre
d'ouvrir un fichier en Python. Le fichier sera automatiquement fermé,
même si une erreur survient. C'est comme un majordome qui range après
votre départ. Utilisez-le toujours. Jamais
f = open(...) sans with.
# Fonction pour charger depuis une chaîne CSV (pour tester)
def parse_csv_string(contenu, separateur=","):
lignes = contenu.strip().split("\n")
en_tetes = lignes[0].split(separateur)
donnees = []
for ligne in lignes[1:]:
if ligne.strip() == "":
continue
valeurs = ligne.split(separateur)
dico = {}
for i, en_tete in enumerate(en_tetes):
dico[en_tete.strip()] = valeurs[i].strip()
donnees.append(dico)
return donnees
donnees = parse_csv_string(contenu_csv)
# Analysons : moyenne des notes
notes = [int(d["note"]) for d in donnees]
moyenne = sum(notes) / len(notes)
print("Moyenne des notes :", moyenne)
# Étudiants ayant plus de 10
adm = [d["nom"] for d in donnees if int(d["note"]) >= 10]
print("Admis :", adm)
pandas.read_csv() en 15 lignes.
C'est moche, c'est fragile, ça ne gère pas les guillemets ni les
virgules dans les champs. Mais ça marche. Et vous
comprenez ce qu'il se passe. C'est ça qui compte. Pandas arrive vite,
promis. Mais d'abord, il faut savoir marcher sans béquilles.
def ecrit_csv(donnees, chemin, separateur=","):
"""Écrit une liste de dictionnaires dans un fichier CSV.
Arguments :
donnees — liste de dict, chaque dict est une ligne
chemin — str, nom du fichier de sortie
separateur — str, séparateur (défaut:',')
"""
if not donnees:
return
# Récupérer les noms de colonnes (à partir des clés du premier dict)
colonnes = list(donnees[0].keys())
with open(chemin, "w", encoding="utf-8") as f:
# Ligne d'en-tête
f.write(separateur.join(colonnes) + "\n")
# Lignes de données
for d in donnees:
ligne = separateur.join(str(d[col]) for col in colonnes)
f.write(ligne + "\n")
# Testons
sortie = [
{"nom": "Alice", "note": 14},
{"nom": "Bob", "note": 8},
]
ecrit_csv(sortie, "notes.csv")
# Vérifions ce qu'on a écrit
with open("notes.csv", "r") as f:
print(f.read())
csv de la bibliothèque
standard. Mais pour comprendre le principe, c'est parfait.
def filtre_csv(donnees, colonne, valeur_min):
"""Filtre les lignes où la colonne (convertie en int) ≥ valeur_min."""
return [d for d in donnees if int(d[colonne]) >= valeur_min]
# Qui a au moins 12 ?
bons = filtre_csv(donnees, "note", 12)
for d in bons:
print(d["nom"], d["note"])
# Alice 14
# Charlie 16
En faisant ça, vous avez implémenté un SELECT ... WHERE note >= 12
— sans base de données, sans SQL, juste des listes et des boucles.
Les fichiers sont partout : CSV, JSON, TXT, logs, configs. Python lit tout
grâce à open(). Les modes :
"r" — lecture (défaut)"w" — écriture (efface le contenu existant !)"a" — ajout à la fin (append)"rb" / "wb" — mode binaire (images, sons)Une vraie image (PNG, JPG) n'est pas qu'une matrice de pixels — elle est compressée. Pour la manipuler sans bibliothèque, c'est une autre histoire. Mais le principe est là : une image est une matrice (ou trois matrices pour RGB). Les filtres de base (flou, netteté, détection de contours) ne sont que des opérations sur ces matrices. C'est ce qu'on appelle la convolution — le cœur des réseaux de neurones convolutionnels (CNN).
Vrai problème : les champs qui contiennent des virgules. La solution :
entourer le champ de guillemets doubles. Ex :
"Dupont, Jean",14,22. Pour parser ça proprement, il faut
un petit automate. C'est exactement ce que fait le module csv
de la bibliothèque standard. Utilisez-le en production.
Aujourd'hui, vous avez ajouté trois cordes à votre arc : les tableaux 2D (matrices, images), les textes (découpage, fusion, cryptographie), et les CSV (lecture, écriture, filtrage). Sans importer une seule bibliothèque externe. Vous méritez une médaille. (Ou au moins un café.)
[[0]*3]*3 est un piège. ⚠️