Conception orientée objet en Python
Une classe définit un type et son comportement. Son appel crée une instance ; __init__ initialise cette instance déjà créée. Il est donc plus précis d'appeler __init__ un initialiseur plutôt que le constructeur lui-même.
class Account:
institution = "Example Credit Union" # attribut de classe partagé
def __init__(self, owner: str, balance: int = 0) -> None:
if balance < 0:
raise ValueError("opening balance cannot be negative")
self.owner = owner # attributs d'instance
self._balance = balance
def deposit(self, amount: int) -> None:
if amount <= 0:
raise ValueError("amount must be positive")
self._balance += amount
@property
def balance(self) -> int:
return self._balance
Une méthode d'instance est une fonction stockée sur la classe. La recherche d'attribut lie l'instance comme premier argument, appelé self par convention.
État et invariants
Conservez l'état de l'instance sur l'instance. Un attribut de classe mutable est partagé par toutes les instances et constitue une source courante d'état global accidentel.
Les méthodes sont utiles lorsqu'elles préservent un invariant : un Account peut refuser un dépôt invalide au lieu d'exposer une modification arbitraire du solde. Les propriétés peuvent ajouter validation ou calcul tout en gardant la syntaxe d'un attribut ; de simples attributs publics conviennent lorsqu'aucun invariant n'a besoin d'être protégé.
Le trait de soulignement initial signale par convention un membre non public ; ce n'est pas une frontière de contrôle d'accès. Deux traits initiaux déclenchent la transformation des noms, qui aide à éviter les collisions dans les sous-classes sans assurer le secret.
Choisir le plus petit modèle
- Utilisez une fonction ordinaire lorsque le comportement n'exige pas d'état persistant.
- Employez
dataclasses.dataclasspour une valeur semblable à un enregistrement avec du code répétitif généré. - Préférez la composition lorsqu'un objet peut déléguer à un autre.
- N'utilisez l'héritage que si le sous-type respecte réellement le contrat comportemental du parent, pas pour réutiliser quelques lignes.
Le code Python dépend souvent du comportement plutôt que d'un arbre d'héritage concret. Tout objet qui respecte le protocole requis peut être accepté, quelle que soit sa classe. Les vérificateurs statiques peuvent l'exprimer avec typing.Protocol.
@classmethod reçoit la classe et convient aux constructeurs alternatifs. @staticmethod ne reçoit ni classe ni instance ; si la fonction n'appartient pas véritablement à l'espace de noms du type, une fonction de module est généralement plus simple.
Les méthodes spéciales comme __repr__, __eq__ et __len__ intègrent un type au modèle de données Python. Respectez leurs contrats documentés ; n'appelez pas directement une méthode spéciale lorsqu'une opération intégrée correspondante existe.
Utiliser les instances et observer la recherche d'attributs
Cet exemple suppose des montants entiers en unités monétaires mineures fournis
par l'appelant. Les annotations ne vérifient pas cette hypothèse : un flottant
positif ou True passerait aussi le test actuel. Une frontière recevant des
montants non fiables doit valider explicitement type et plage. Cette classe
illustre la responsabilité de l'état, pas un registre financier complet.
ada = Account("Ada", 100)
grace = Account("Grace")
assert ada.deposit(25) is None
assert ada.balance == 125
assert grace.balance == 0
Account.deposit(grace, 10) # équivaut à grace.deposit(10)
assert grace.balance == 10
ada.institution = "Local branch"
assert ada.institution == "Local branch"
assert grace.institution == Account.institution
Pour les attributs de données ordinaires, la recherche consulte l'instance avant
la classe. Affecter ada.institution masque donc l'attribut de classe sans
modifier la classe ni grace. Une propriété suit les règles des descripteurs :
ada.balance = 0 lève AttributeError, car cette propriété n'a pas de méthode
d'affectation. L'affectation directe à ada._balance reste possible par convention.
Le tutoriel sur les classes
explique l'héritage et la redéfinition des méthodes. Une sous-classe déclare sa
base avec class Child(Parent): et peut remplacer une méthode. Si elle définit
__init__, elle doit appeler super().__init__(...) lorsque l'initialisation de
la base est nécessaire ; cet appel n'est pas inséré automatiquement. super()
suit l'ordre de résolution des méthodes, et non un parent fixé à l'avance,
ce qui compte en héritage multiple.