Comment définir vos propres classes d'exceptions en Python

Bonjour, Habr!



Votre intérêt pour le nouveau livre " Python Pro Secrets " nous a convaincus que l'histoire des bizarreries de Python mérite d'être poursuivie. Aujourd'hui, nous vous proposons de lire un petit tutoriel sur la création de classes d'exceptions personnalisées (dans le texte - les vôtres). L'auteur a trouvé cela intéressant, il est difficile de ne pas être d'accord avec lui que l'avantage le plus important d'une exception est l'exhaustivité et la clarté du message d'erreur. Une partie du code de l'original est sous forme d'images.



Bienvenue au chat.



Créer vos propres classes d'erreur



Python offre la possibilité de créer vos propres classes d'exceptions. En créant de telles classes, vous pouvez diversifier la conception des classes dans votre application. Une classe d'erreurs personnalisée pourrait consigner les erreurs, inspecter l'objet. C'est nous qui définissons ce que fait la classe d'exception, même si généralement une classe personnalisée peut difficilement faire plus que d'afficher un message.



Naturellement, le type d'erreur lui-même est important, et nous créons souvent nos propres types d'erreur pour indiquer une situation spécifique qui n'est généralement pas couverte au niveau Python. De cette façon, les utilisateurs de la classe sauront exactement ce qui se passe lorsqu'ils rencontrent cette erreur.



Cet article est divisé en deux parties. Tout d'abord, nous définirons une classe d'exception par elle-même. Nous montrons ensuite comment nous pouvons intégrer nos propres classes d'exceptions dans nos programmes Python et montrer comment nous pouvons ainsi améliorer la convivialité des classes que nous concevons.



Classe d'exception personnalisée MyCustomError



Lancer une exception nécessite les méthodes __init__()



et __str__()



.




Lors de la levée d'une exception, nous créons déjà une instance de l'exception et en même temps l'affiche à l'écran. Examinons de plus près notre classe d'exception personnalisée ci-dessous.







La classe MyCustomError ci-dessus a deux méthodes magiques, __init__



et __str__



, qui sont automatiquement appelées lors de la gestion des exceptions. La méthode Init



est appelée lorsque l'instance est créée et la méthode str



est appelée lorsque l'instance est affichée. Par conséquent, lorsqu'une exception est levée, ces deux méthodes sont généralement appelées immédiatement l'une après l'autre. L'instruction throw d'exception Python met un programme dans un état d'erreur.



Dans la liste des arguments de la méthode __init__



est *args



. Un composant *args



est un mode de correspondance de modèle spécial utilisé dans les fonctions et les méthodes. Il vous permet de passer plusieurs arguments et stocke les arguments passés sous forme de tuple, mais vous permet en même temps de ne pas passer d'arguments du tout.



Dans notre cas, nous pouvons dire que si MyCustomError



des arguments ont été passés au constructeur , nous prenons le premier argument passé et l'affectons à un attribut message



de l'objet. Si aucun argument n'a été passé, l'attribut se message



verra attribuer une valeur None



.



Dans le premier exemple, l'exception MyCustomError



est levée sans aucun argument, donc l'attribut message



cet objet reçoit une valeur None



. Une méthode sera appelée str



et affichera le message «Le message MyCustomError a été déclenché».







L'exception MyCustomError



est levée sans aucun argument (les parenthèses sont vides). En d'autres termes, une telle conception d'objet semble non standard. Mais ce n'est qu'un support syntaxique fourni en Python lors de la levée d'une exception.



Dans le deuxième exemple, il MyCustomError



est passé avec l'argument de chaîne «Nous avons un problème». Il est défini comme un attribut message



sur l'objet et affiché sous forme de message d'erreur lorsqu'une exception est levée.







Le code de la classe d'exception MyCustomError est ici...



class MyCustomError(Exception):
    def __init__(self, *args):
        if args:
            self.message = args[0]
        else:
            self.message = None

    def __str__(self):
        print('calling str')
        if self.message:
            return 'MyCustomError, {0} '.format(self.message)
        else:
            return 'MyCustomError has been raised'


#  MyCustomError

raise MyCustomError('We have a problem')
      
      





Classe CustomIntFloatDic



Nous créons notre propre dictionnaire, dont les valeurs ne peuvent être que des entiers et des nombres à virgule flottante.



Allons-y et montrons comment injecter facilement et utilement des classes d'erreur dans nos propres programmes. Pour commencer, je vais offrir un exemple légèrement artificiel. Dans cet exemple fictif, je vais créer mon propre dictionnaire qui ne peut accepter que des entiers ou des nombres à virgule flottante.



Si l'utilisateur tente de spécifier un autre type de données comme valeur dans ce dictionnaire, une exception sera levée. Cette exception fournira des informations utiles à l'utilisateur sur la façon d'utiliser ce dictionnaire. Dans notre cas, ce message informe directement l'utilisateur que seuls des entiers ou des nombres à virgule flottante peuvent être spécifiés comme valeurs dans ce dictionnaire.



Lorsque vous créez votre propre dictionnaire, gardez à l'esprit qu'il existe deux endroits où des valeurs peuvent être ajoutées au dictionnaire. Premièrement, cela peut se produire dans la méthode init lors de la création d'un objet (à ce stade, l'objet peut déjà se voir attribuer des clés et des valeurs), et deuxièmement, lors de la définition des clés et des valeurs directement dans le dictionnaire. Dans ces deux endroits, vous devez écrire du code pour vous assurer que la valeur ne peut être que de type int



ou float



.



Tout d'abord, je définirai la classe CustomIntFloatDict qui hérite de la classe intégrée dict



. dict



est passé dans une liste d'arguments, qui sont entre parenthèses et suivent le nom de la classe CustomIntFloatDict



.



Si une classe est instanciée CustomIntFloatDict



, de plus, aucun argument n'est passé aux paramètres de clé et de valeur, ils seront mis à None



. L'expression est if



interprétée comme suit: si soit la clé est égale None



, soit la valeur est égale None



, alors une méthode sera appelée avec l'objet get_dict()



, qui retournera l'attribut empty_dict



; un tel attribut sur un objet pointe vers une liste vide. N'oubliez pas que les attributs de classe sont disponibles sur toutes les instances de la classe.







Le but de cette classe est de permettre à l'utilisateur de passer une liste ou un tuple avec les clés et les valeurs à l'intérieur. Si l'utilisateur entre une liste ou un tuple à la recherche de clés et de valeurs, ces deux ensembles itérables seront concaténés à l'aide de la fonction zip



le langage Python. Une variable crochetée pointant vers un objet zip



est itérable et les tuples sont décompressables. En itérant sur les tuples, je vérifie si val est une instance de la classe int



ou float



. S'il val



n'appartient à aucune de ces classes, je lance IntFloatValueError



ma propre exception et je lui passe val comme argument.



Classe d'exception IntFloatValueError



Lorsqu'une exception est levée, IntFloatValueError



nous créons une instance de la classe IntFloatValueError



et l'affiche simultanément à l'écran. Cela signifie que les méthodes magiques init



et seront appelées str



.



La valeur qui a provoqué l'exception levée est définie comme un attribut value



accompagnant la classe IntFloatValueError



. Lors de l'appel de la méthode magic str, l'utilisateur reçoit un message d'erreur l'informant que la valeur init



dans CustomIntFloatDict



est invalide. L'utilisateur sait quoi faire pour corriger cette erreur.







Classes d'exception IntFloatValueError



et KeyValueConstructError











si aucune exception n'est levée, c'est-à-dire val



de l'objet chaîné sont de types int



ou float



, alors ils seront définis à l'aide de __setitem__()



, et la méthode de la classe parent fera tout pour nous dict



, comme indiqué ci-dessous.







KeyValueConstructError, classe



Que se passe-t-il si l'utilisateur entre un type qui n'est pas une liste ou un tuple de clés et de valeurs?



Encore une fois, cet exemple est un peu artificiel, mais il est pratique pour vous montrer comment vous pouvez utiliser vos propres classes d'exceptions.



Si l'utilisateur ne spécifie pas les clés et les valeurs sous forme de liste ou de tuple, une exception sera levée KeyValueConstructError



. Le but de cette exception est d'informer l'utilisateur que pour écrire des clés et des valeurs dans un objet CustomIntFloatDict



, une liste ou un tuple doit être spécifié dans le constructeur de init



classe CustomIntFloatDict



.



Dans l'exemple ci-dessus, en tant que deuxième argument du constructeur init



beaucoup de choses ont été adoptées et une exception a été levée à cause de cela KeyValueConstructError



. L'avantage du message d'erreur affiché est que le message d'erreur affiché informe l'utilisateur que les clés et les valeurs à insérer doivent être signalées sous forme de liste ou de tuple.



Là encore, lorsqu'une exception est levée, une instance de KeyValueConstructError est créée et la clé et les valeurs sont transmises en tant qu'arguments au constructeur KeyValueConstructError. Ils sont définis comme les valeurs des attributs clé et valeur de KeyValueConstructError et sont utilisés dans la méthode __str__ pour générer un message d'erreur significatif lorsque le message est affiché à l'écran.



De plus, j'inclus même les types de données inhérents aux objets ajoutés au constructeur init



- Je fais ça pour plus de clarté.







Définition de la clé et de la valeur dans CustomIntFloatDict



CustomIntFloatDict



hérite de dict



. Cela signifie qu'il fonctionnera exactement comme un dictionnaire, sauf aux endroits où nous choisissons de modifier sélectivement son comportement.



__setitem__



Est une méthode magique appelée lors de la définition d'une clé et d'une valeur dans un dictionnaire. Dans notre implémentation, setitem



nous vérifions que la valeur est de type int



ou float



, et ce n'est qu'après une vérification réussie qu'elle peut être définie dans le dictionnaire. Si la vérification échoue, vous pouvez à nouveau utiliser la classe d'exception IntFloatValueError



. Ici, vous pouvez vous assurer que nous obtiendrons une exception en essayant de définir une chaîne ‘bad_value’



comme valeur dans le dictionnaire test_4



.







Tout le code de ce didacticiel est présenté ci-dessous et publié sur Github .



#  ,        int  float  

class IntFloatValueError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return '{} is invalid input, CustomIntFloatDict can only accept ' \
               'integers and floats as its values'.format(self.value)


class KeyValueContructError(Exception):
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return 'keys and values need to be passed as either list or tuple' + '\n' + \
                ' {} is of type: '.format(self.key) + str(type(self.key)) + '\n' + \
                ' {} is of type: '.format(self.value) + str(type(self.value))


class CustomIntFloatDict(dict):

    empty_dict = {}

    def __init__(self, key=None, value=None):

        if key is None or value is None:
            self.get_dict()

        elif not isinstance(key, (tuple, list,)) or not isinstance(value, (tuple, list)):
            raise KeyValueContructError(key, value)
        else:
            zipped = zip(key, value)
            for k, val in zipped:
                if not isinstance(val, (int, float)):
                    raise IntFloatValueError(val)
                dict.__setitem__(self, k, val)

    def get_dict(self):
        return self.empty_dict

    def __setitem__(self, key, value):
        if not isinstance(value, (int, float)):
            raise IntFloatValueError(value)
        return dict.__setitem__(self, key, value)


#  

# test_1 = CustomIntFloatDict()
# print(test_1)
# test_2 = CustomIntFloatDict({'a', 'b'}, [1, 2])
# print(test_2)
# test_3 = CustomIntFloatDict(('x', 'y', 'z'), (10, 'twenty', 30))
# print(test_3)
# test_4 = CustomIntFloatDict(('x', 'y', 'z'), (10, 20, 30))
# print(test_4)
# test_4['r'] = 1.3
# print(test_4)
# test_4['key'] = 'bad_value'
      
      





Conclusion



Si vous créez vos propres exceptions, travailler avec la classe devient beaucoup plus pratique. La classe d'exception doit avoir des méthodes magiques init



et str



qui sont automatiquement appelées lors de la gestion des exceptions. C'est entièrement à vous de décider de ce que fera votre propre classe d'exception. Parmi les méthodes présentées, il y a celles qui sont chargées d'inspecter un objet et d'afficher un message d'erreur informatif à l'écran.



Quoi qu'il en soit, les classes d'exception facilitent grandement la gestion des erreurs qui surviennent!



All Articles