Erreurs et frontières d'exception
Une exception indique qu'une opération n'a pas pu respecter son contrat. Ne la capturez que si le programme peut récupérer, la traduire dans une abstraction plus utile ou ajouter du contexte avant de la laisser remonter.
class ConfigurationError(ValueError):
pass
def load_port(raw: str) -> int:
try:
port = int(raw)
except ValueError as error:
raise ConfigurationError(f"invalid port: {raw!r}") from error
if not 1 <= port <= 65_535:
raise ConfigurationError("port is outside the valid range")
return port
raise ... from error rend la traduction explicite en préservant la cause. N'utilisez from None que si supprimer le contexte inférieur améliore réellement le diagnostic public.
Structure de traitement
Gardez le bloc try étroit afin que le gestionnaire ne capture que l'opération visée :
try:
document = read_document(path)
except FileNotFoundError:
return default_document()
else:
return parse_document(document)
Le bloc else ne s'exécute qu'après réussite de l'opération protégée. finally s'exécute en sortie normale comme exceptionnelle et convient au nettoyage inévitable ; les gestionnaires de contexte sont généralement plus clairs pour les ressources.
Capturez des sous-classes précises. except Exception peut convenir à une frontière de processus, requête ou worker qui journalise et isole un échec inattendu, mais ne doit pas transformer silencieusement des défauts arbitraires en réussite. KeyboardInterrupt, SystemExit et les autres signaux d'arrêt héritent directement de BaseException et doivent normalement se propager.
Définir les erreurs
Réutilisez les exceptions intégrées si leur sens convient. Une bibliothèque peut exposer une petite hiérarchie métier issue d'une exception publique afin que l'appelant choisisse un traitement large ou étroit. Conservez des attributs structurés utiles si l'appelant doit inspecter l'échec ; ne l'obligez pas à analyser le message.
Les exceptions doivent dire ce qui a échoué sans révéler secrets, jetons ou charges sensibles complètes. Journaliser et relancer la même exception à chaque couche crée du bruit en double ; choisissez la frontière responsable du diagnostic.
Les assertions ne valident pas les entrées
assert consigne un invariant interne pour les développeurs. Python peut supprimer ces instructions en mode optimisé : ne les utilisez jamais pour valider entrée utilisateur, permissions, configuration ou conditions requises à l'exécution. Levez une exception adaptée.
Tester le contrat d'échec
import pytest
def test_load_port_rejects_out_of_range_value() -> None:
with pytest.raises(ConfigurationError, match="outside"):
load_port("70000")
Testez le type public, les attributs pertinents, la préservation de l'état et le nettoyage. N'imposez pas une trace complète ou un libellé instable, sauf si le texte exact appartient à l'interface externe.
Nettoyage et suppression
else ne s'exécute que si try se termine normalement, pas après return, break ou continue. Une exception dans else n'est pas traitée par le except précédent. Pendant le déroulement normal de la pile Python, finally s'exécute aussi lors d'un retour anticipé. Un return dans finally peut remplacer la valeur de retour ou supprimer une exception : évitez ce contrôle de flux dans le nettoyage. Un arrêt brutal du processus ne garantit pas le nettoyage.
Un générateur décoré par contextmanager doit produire exactement une valeur : l'entrée avance jusqu'au yield, la sortie reprend ensuite. Une exception du corps with est relancée à ce point de suspension. Utilisez finally pour libérer la ressource sans absorber l'erreur :
from contextlib import contextmanager
from io import StringIO
@contextmanager
def text_buffer(text):
handle = StringIO(text)
try:
yield handle
finally:
handle.close()
try:
with text_buffer("hello") as handle:
assert handle.read() == "hello"
raise ValueError("demo")
except ValueError:
assert handle.closed
StringIO prend déjà en charge with ; l'enveloppe illustre le protocole, pas une abstraction nécessaire. Pour un gestionnaire défini par une classe, __enter__ fournit la valeur de as et __exit__ reçoit les détails de l'exception. Une valeur vraie renvoyée par __exit__ supprime l'exception. Si __enter__ échoue, __exit__ n'est pas appelé : l'entrée doit nettoyer les ressources partiellement acquises.