Python: Est-ce une mauvaise forme de lever des exceptions dans __init__?

128

Est-il considéré comme une mauvaise forme de soulever des exceptions à l'intérieur __init__? Si tel est le cas, quelle est la méthode acceptée pour lancer une erreur lorsque certaines variables de classe sont initialisées comme Noneou d'un type incorrect?

krizajb
la source
En C, il n'est pas bon de lever une exception dans le destructeur, mais le constructeur devrait convenir.
Calyth
6
@Calyth, vous voulez dire C ++ - il n'y a pas de constructeurs ou de destructeurs dans C.
Alex Martelli
4
... ou des exceptions, d'ailleurs.
Matt Wilding
@Calyth: lol, veuillez supprimer cette déclaration absurde faite par une faute de frappe innocente ;-)
lpapp
4
@lpapp oui. C ++. FML. Et je ne pouvais pas modifier ce commentaire. Donc, Internet documentera mon idiotie;)
Calyth

Réponses:

159

Soulever des exceptions à l'intérieur __init__()est absolument parfait. Il n'y a pas d'autre bon moyen d'indiquer une condition d'erreur dans un constructeur, et il existe plusieurs centaines d'exemples dans la bibliothèque standard où la construction d'un objet peut déclencher une exception.

La classe d'erreur à élever, bien sûr, dépend de vous. ValueErrorest préférable si le constructeur a reçu un paramètre non valide.

John Millikin
la source
14
Les exceptions les plus utilisées dans le constructeur sont ValueErroret TypeError.
Denis Otkidach
9
Soulignant simplement que ce __init__n'est pas le constructeur, c'est l'initiateur. Vous voudrez peut-être modifier la ligne. Il n'y a pas d'autre bon moyen d'indiquer une condition d'erreur dans un constructeur, ..
Haris
26

Il est vrai que la seule manière correcte d'indiquer une erreur dans un constructeur est de lever une exception. C'est pourquoi en C ++ et dans d'autres langages orientés objet qui ont été conçus avec la sécurité des exceptions à l'esprit, le destructeur n'est pas appelé si une exception est levée dans le constructeur d'un objet (ce qui signifie que l'initialisation de l'objet est incomplète). Ce n'est souvent pas le cas dans les langages de script, tels que Python. Par exemple, le code suivant lève une AttributeError si socket.connect () échoue:

class NetworkInterface:
    def __init__(self, address)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(address)
        self.stream = self.socket.makefile()

    def __del__(self)
        self.stream.close()
        self.socket.close()

La raison en est que le destructeur de l'objet incomplet est appelé après l'échec de la tentative de connexion, avant que l'attribut stream n'ait été initialisé. Vous ne devriez pas éviter de lancer des exceptions de la part des constructeurs, je dis simplement qu'il est difficile d'écrire du code entièrement sécurisé en Python. Certains développeurs Python évitent complètement d'utiliser des destructeurs, mais c'est un autre débat.

Seppo Enarvi
la source
Cette réponse est vraiment utile. Il ne s'agit pas seulement de parler du "feu vert", mais mentionne un exemple avec le "destructeur".
lpapp
Votre code python échoue __del__car il est mal écrit. Vous auriez exactement le même problème si vous le faisiez this->p_socket->close()dans un destructeur en C ++. En C ++, vous ne feriez pas cela - vous laisseriez l'objet membre se détruire. Faites de même en python.
Eric
1
@Eric Je n'aurais pas le problème en C ++ car C ++ ne détruit pas les objets qui n'ont pas été correctement initialisés . En fait, c'est un idiome très courant en C ++ d' allouer des ressources dans le constructeur et de les désallouer dans le destructeur . Vous suggérez que l'objet membre se détruit lui-même (en libérant les ressources dans son destructeur) - alors le même problème se pose lors de l'écriture de la classe membre.
Seppo Enarvi
11

Je ne vois aucune raison pour que ce soit une mauvaise forme.

Au contraire, l'une des choses que les exceptions sont connues pour bien faire, par opposition au renvoi de codes d'erreur, est que les codes d'erreur ne peuvent généralement pas être retournés par les constructeurs. Donc, au moins dans des langages comme C ++, lever des exceptions est le seul moyen de signaler les erreurs.

Edan Maor
la source
6

La bibliothèque standard dit:

>>> f = file("notexisting.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'notexisting.txt'

De plus, je ne vois pas vraiment pourquoi cela devrait être considéré comme une mauvaise forme.

qc
la source
3

Je devrais penser que c'est le cas parfait pour l' ValueErrorexception intégrée .

Teddy
la source
2

Je suis d'accord avec tout ce qui précède.

Il n'y a vraiment pas d'autre moyen de signaler que quelque chose s'est mal passé lors de l'initialisation d'un objet autre que de lever une exception.

Dans la plupart des classes de programmes où l'état d'une classe dépend entièrement des entrées de cette classe, nous pouvons nous attendre à ce qu'une sorte de ValueError ou TypeError soit déclenchée.

Les classes avec des effets secondaires (par exemple, celles qui font du réseautage ou des graphiques) peuvent générer une erreur dans init si (par exemple) le périphérique réseau n'est pas disponible ou l'objet canevas ne peut pas être écrit. Cela me semble judicieux car vous souhaitez souvent connaître les conditions de défaillance le plus rapidement possible.

Salim Fadhley
la source
2

Relever des erreurs depuis init est inévitable dans certains cas, mais faire trop de travail dans init est un mauvais style. Vous devriez envisager de créer une fabrique ou une pseudo-fabrique - une méthode de classe simple qui renvoie un objet configuré.

Ctrl-C
la source