iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
Python · Débutant

Python

Apprendre Python de zéro jusqu'au niveau expert : syntaxe, structures de données, POO, générateurs, décorateurs, typage, tests, async et internals du langage.

Python 3.13uvpytestmypyasyncio
01Introduction et installation02Variables et types de base03Conditions et boucles04Fonctions05Listes, tuples, dictionnaires et ensembles06Modules, fichiers et exceptions07Programmation orientée objet08Itérateurs, générateurs, décorateurs et context managers09Typage statique et tests10Concurrence : threads, processus et asyncio11Niveau expert : internals, métaprogrammation et performance
Chapitre 7·45 min

Programmation orientée objet

En Python, tout est objet : les entiers, les fonctions, les classes elles-mêmes. La programmation orientée objet consiste à regrouper des données et les comportements qui s'y appliquent dans une même unité, la classe. Python le fait avec une syntaxe légère et une philosophie pragmatique : pas de private, pas d'interfaces obligatoires, on fait confiance au développeur.

Classe, instance, attributs

class CompteBancaire:
    taux_interet = 0.02          # attribut de classe, partagé
 
    def __init__(self, titulaire: str, solde: float = 0):
        self.titulaire = titulaire   # attribut d'instance
        self.solde = solde
 
    def deposer(self, montant: float) -> None:
        if montant <= 0:
            raise ValueError("Montant positif requis")
        self.solde += montant
 
    def __repr__(self) -> str:
        return f"CompteBancaire({self.titulaire!r}, {self.solde})"
 
compte = CompteBancaire("Ada", 100)
compte.deposer(50)
compte             # CompteBancaire('Ada', 150)

self est l'instance, passée automatiquement. compte.deposer(50) équivaut à CompteBancaire.deposer(compte, 50).

Encapsulation par convention

  • _solde : « interne », accessible mais on ne devrait pas y toucher
  • __solde : name mangling, renommé en _CompteBancaire__solde pour éviter les collisions en héritage

Pour contrôler l'accès, on utilise @property plutôt que des getters/setters Java :

class Temperature:
    def __init__(self, celsius: float):
        self.celsius = celsius           # passe par le setter
 
    @property
    def celsius(self) -> float:
        return self._celsius
 
    @celsius.setter
    def celsius(self, valeur: float) -> None:
        if valeur < -273.15:
            raise ValueError("Sous le zéro absolu")
        self._celsius = valeur
 
    @property
    def fahrenheit(self) -> float:
        return self._celsius * 9 / 5 + 32
 
t = Temperature(20)
t.fahrenheit     # 68.0 — calculé, sans parenthèses
t.celsius = -300 # ValueError

Méthodes de classe et statiques

from datetime import date
 
class Personne:
    def __init__(self, nom: str, naissance: date):
        self.nom = nom
        self.naissance = naissance
 
    @classmethod
    def depuis_chaine(cls, s: str) -> "Personne":
        nom, iso = s.split(";")
        return cls(nom, date.fromisoformat(iso))
 
    @staticmethod
    def est_majeur(age: int) -> bool:
        return age >= 18
 
p = Personne.depuis_chaine("Ada;1815-12-10")

@classmethod reçoit la classe (cls) : c'est le pattern des constructeurs alternatifs. @staticmethod ne reçoit rien, c'est une fonction rangée dans la classe.

Héritage

class Animal:
    def __init__(self, nom: str):
        self.nom = nom
 
    def parler(self) -> str:
        raise NotImplementedError
 
class Chien(Animal):
    def parler(self) -> str:
        return f"{self.nom} : Wouf"
 
class ChienDeGarde(Chien):
    def __init__(self, nom: str, zone: str):
        super().__init__(nom)
        self.zone = zone
 
for a in [Chien("Rex"), ChienDeGarde("Max", "entrepôt")]:
    print(a.parler())

super() suit le MRO (Method Resolution Order), visible avec ChienDeGarde.__mro__. Python gère l'héritage multiple, mais je le réserve aux petites classes « mixin » qui ajoutent un comportement isolé.

Composition plutôt qu'héritage

Un Moteur n'est pas une Voiture. Une voiture a un moteur.

class Moteur:
    def demarrer(self) -> str:
        return "Vroum"
 
class Voiture:
    def __init__(self):
        self.moteur = Moteur()
 
    def demarrer(self) -> str:
        return self.moteur.demarrer()

L'héritage crée un couplage fort. Dès que la relation « est un » est discutable, composez.

Méthodes spéciales (dunder)

Les méthodes __nom__ permettent à vos objets de s'intégrer au langage : opérateurs, len(), for, with, comparaison.

from functools import total_ordering
 
@total_ordering
class Vecteur:
    def __init__(self, x: float, y: float):
        self.x, self.y = x, y
 
    def __add__(self, autre: "Vecteur") -> "Vecteur":
        return Vecteur(self.x + autre.x, self.y + autre.y)
 
    def __mul__(self, k: float) -> "Vecteur":
        return Vecteur(self.x * k, self.y * k)
 
    def __abs__(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5
 
    def __eq__(self, autre: object) -> bool:
        return isinstance(autre, Vecteur) and (self.x, self.y) == (autre.x, autre.y)
 
    def __lt__(self, autre: "Vecteur") -> bool:
        return abs(self) < abs(autre)
 
    def __repr__(self) -> str:
        return f"Vecteur({self.x}, {self.y})"
 
Vecteur(1, 2) + Vecteur(3, 4)    # Vecteur(4, 6)
abs(Vecteur(3, 4))               # 5.0
MéthodeDéclenchée par
__init__Classe()
__repr__ / __str__repr() / print()
__len__len(obj)
__getitem__obj[cle]
__iter__for x in obj
__contains__x in obj
__call__obj()
__enter__ / __exit__with obj:
__hash__clé de dict, élément de set

Définir __eq__ sans __hash__ rend l'objet non hashable. C'est voulu : deux objets égaux doivent avoir le même hash.

dataclasses

Pour les classes qui transportent surtout des données, @dataclass génère __init__, __repr__ et __eq__.

from dataclasses import dataclass, field
 
@dataclass
class Produit:
    nom: str
    prix: float
    tags: list[str] = field(default_factory=list)
 
@dataclass(frozen=True, slots=True)
class Coordonnee:
    lat: float
    lon: float
 
p = Produit("Clavier", 79.9)
p                    # Produit(nom='Clavier', prix=79.9, tags=[])
Coordonnee(48.8, 2.3) == Coordonnee(48.8, 2.3)   # True

frozen=True rend l'instance immuable et hashable. slots=True supprime le __dict__ par instance : moins de mémoire, accès plus rapide.

Classes abstraites et Protocols

abc.ABC force les sous-classes à implémenter certaines méthodes :

from abc import ABC, abstractmethod
from pathlib import Path
 
class Stockage(ABC):
    @abstractmethod
    def sauver(self, cle: str, data: bytes) -> None: ...
 
class StockageDisque(Stockage):
    def sauver(self, cle, data):
        Path(cle).write_bytes(data)
 
Stockage()   # TypeError : classe abstraite

Protocol exprime le duck typing de manière vérifiable, sans héritage :

from typing import Protocol
 
class Sauvegardable(Protocol):
    def sauver(self, cle: str, data: bytes) -> None: ...
 
def archiver(s: Sauvegardable) -> None:
    s.sauver("backup", b"...")

Toute classe qui possède une méthode sauver compatible est acceptée par mypy. Je préfère Protocol à ABC dans la plupart des cas : moins de couplage.

Exercice : un panier d'achat

from dataclasses import dataclass, field
 
@dataclass(frozen=True)
class Article:
    nom: str
    prix: float
 
@dataclass
class Panier:
    lignes: dict[Article, int] = field(default_factory=dict)
 
    def ajouter(self, article: Article, qte: int = 1) -> None:
        self.lignes[article] = self.lignes.get(article, 0) + qte
 
    def __len__(self) -> int:
        return sum(self.lignes.values())
 
    @property
    def total(self) -> float:
        return round(sum(a.prix * q for a, q in self.lignes.items()), 2)
 
panier = Panier()
panier.ajouter(Article("Livre", 12.5), 2)
panier.ajouter(Article("Stylo", 1.2))
len(panier), panier.total    # (3, 26.2)

Python pousse vers une POO légère : des dataclasses pour les données, des protocols pour les contrats, de la composition pour l'assemblage. Les hiérarchies profondes à la Java sont presque toujours un signal d'alarme.

Précédent
Modules, fichiers et exceptions
Suivant
Itérateurs, générateurs, décorateurs et context managers

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main