Cómo definir sus propias clases de excepción en Python

¡Hola, Habr!



Su interés en el nuevo libro " Python Pro Secrets " nos ha convencido de que la historia de las peculiaridades de Python merece continuar. Hoy ofrecemos leer un pequeño tutorial sobre la creación de clases de excepción personalizadas (en el texto, las suyas propias). El autor lo entendió interesante, es difícil no estar de acuerdo con él en que la ventaja más importante de una excepción es la integridad y claridad del mensaje de error. Parte del código del original tiene forma de imágenes.



Bienvenido a cat.



Creando sus propias clases de error



Python ofrece la posibilidad de crear sus propias clases de excepción. Al crear dichas clases, puede diversificar el diseño de las clases en su aplicación. Una clase personalizada de errores podría registrar errores, inspeccionar el objeto. Somos nosotros quienes definimos lo que hace la clase de excepción, aunque normalmente una clase personalizada difícilmente puede hacer más que mostrar un mensaje.



Naturalmente, el tipo de error en sí es importante y, a menudo, creamos nuestros propios tipos de error para indicar una situación específica que normalmente no se cubre en el nivel de Python. De esta manera, los usuarios de la clase sabrán exactamente qué está sucediendo cuando encuentren este error.



Este artículo está dividido en dos partes. Primero, definiremos una clase de excepción por sí misma. Luego demostramos cómo podemos integrar nuestras propias clases de excepción en nuestros programas de Python y mostramos cómo podemos mejorar así la usabilidad de las clases que diseñamos.



Clase de excepción personalizada MyCustomError



Lanzar una excepción requiere los métodos __init__()



y __str__()



.




Al lanzar una excepción, ya creamos una instancia de la excepción y al mismo tiempo la mostramos en la pantalla. Echemos un vistazo más de cerca a nuestra clase de excepción personalizada que se muestra a continuación.







La clase MyCustomError anterior tiene dos métodos mágicos, __init__



y __str__



, que se llaman automáticamente durante el manejo de excepciones. Se Init



llama al método cuando se crea la instancia y str



se llama al método cuando se muestra la instancia . Por lo tanto, cuando se lanza una excepción, estos dos métodos se suelen llamar inmediatamente uno después del otro. La declaración de lanzamiento de excepción de Python pone un programa en un estado de error.



En la lista de argumentos del método __init__



es *args



. Un componente *args



es un modo de coincidencia de patrones especial que se utiliza en funciones y métodos. Le permite pasar múltiples argumentos y almacena los argumentos pasados ​​como una tupla, pero al mismo tiempo le permite no pasar argumentos en absoluto.



En nuestro caso, podemos decir que si MyCustomError



se pasa algún argumento al constructor , entonces tomamos el primer argumento pasado y lo asignamos a un atributo message



en el objeto. Si no se pasaron argumentos, al atributo message



se le asignará un valor None



.



En el primer ejemplo, la excepción MyCustomError



se lanza sin ningún argumento, por lo que el atributo message



a este objeto se le asigna un valor None



. Se llamará a un método str



y mostrará el mensaje 'Se ha generado el mensaje MyCustomError'.







La excepción MyCustomError



se lanza sin ningún argumento (los paréntesis están vacíos). En otras palabras, este diseño de objeto no parece estándar. Pero esto es solo soporte sintáctico proporcionado en Python cuando se lanza una excepción.



En el segundo ejemplo, se MyCustomError



pasa con un argumento de cadena 'Tenemos un problema'. Se establece como un atributo del message



objeto y se muestra como un mensaje de error cuando se lanza una excepción.







El código para la clase de excepción MyCustomError está aquí...



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')
      
      





Clase CustomIntFloatDic



Creamos nuestro propio diccionario, cuyos valores solo pueden ser enteros y números de coma flotante.



Sigamos adelante y demostremos cómo inyectar clases de error de manera fácil y útil en nuestros propios programas. Para empezar, ofreceré un ejemplo ligeramente elaborado. En este ejemplo ficticio, crearé mi propio diccionario que solo puede aceptar números enteros o de coma flotante.



Si el usuario intenta especificar cualquier otro tipo de datos como valor en este diccionario, se lanzará una excepción. Esta excepción proporcionará información útil al usuario sobre cómo utilizar este diccionario. En nuestro caso, este mensaje informa directamente al usuario que solo los números enteros o de coma flotante se pueden especificar como valores en este diccionario.



Al crear su propio diccionario, tenga en cuenta que hay dos lugares donde se pueden agregar valores al diccionario. En primer lugar, esto puede suceder en el método init al crear un objeto (en esta etapa, al objeto ya se le pueden asignar claves y valores) y, en segundo lugar, al configurar claves y valores directamente en el diccionario. En ambos lugares, debe escribir código para asegurarse de que el valor solo pueda ser de tipo int



o float



.



Primero, definiré la clase CustomIntFloatDict que hereda de la clase incorporada dict



. dict



se pasa en una lista de argumentos, que están entre paréntesis y siguen al nombre de la clase CustomIntFloatDict



.



Si se crea una instancia de una clase CustomIntFloatDict



Además, no se pasan argumentos a los parámetros de clave y valor, se establecerán en None



. La expresión se if



interpreta de la siguiente manera: si la clave es igual None



o el valor es igual None



, se llamará a un método con el objeto get_dict()



, que devolverá el atributo empty_dict



; tal atributo en un objeto apunta a una lista vacía. Recuerde que los atributos de clase están disponibles en todas las instancias de la clase.







El propósito de esta clase es permitirle al usuario pasar una lista o tupla con las claves y valores dentro. Si el usuario ingresa a una lista o tupla buscando claves y valores, entonces estos dos conjuntos iterables se concatenarán usando la función zip



el lenguaje Python. Una variable enganchada que apunta a un objeto zip



es iterable y las tuplas no se pueden empaquetar. Al iterar sobre las tuplas, verifico si val es una instancia de la clase int



o float



. Si val



no pertenece a ninguna de estas clases, lanzo IntFloatValueError



mi propia excepción y le paso val como argumento.



Clase de excepción IntFloatValueError



Cuando se lanza una excepción, IntFloatValueError



creamos una instancia de la clase IntFloatValueError



y la mostramos simultáneamente en la pantalla. Esto significa que los métodos mágicos init



y serán llamados str



.



El valor que provocó la excepción lanzada se establece como un atributo que value



acompaña a la clase IntFloatValueError



. Al llamar al método magic str, el usuario recibe un mensaje de error que informa que el valor init



de CustomIntFloatDict



no es válido. El usuario sabe qué hacer para corregir este error.







Clases de excepción IntFloatValueError



y KeyValueConstructError











si no se lanza ninguna excepción, es decir, todas val



del objeto encadenado son de tipos int



o float



, luego se establecerán usando __setitem__()



, y el método de la clase padre hará todo por nosotros dict



, como se muestra a continuación.







Clase KeyValueConstructError



¿Qué sucede si el usuario ingresa un tipo que no es una lista o una tupla de claves y valores?



Nuevamente, este ejemplo es un poco artificial, pero es útil para mostrarle cómo puede usar sus propias clases de excepción.



Si el usuario no especifica las claves y valores como una lista o una tupla, se lanzará una excepción KeyValueConstructError



. El propósito de esta excepción es informar al usuario que para escribir claves y valores en un objeto CustomIntFloatDict



, se debe especificar una lista o tupla en el constructor de la init



clase CustomIntFloatDict



.



En el ejemplo anterior, como segundo argumento del constructor init



se pasó mucho y se lanzó una excepción debido a esto KeyValueConstructError



. La ventaja del mensaje de error que se muestra es que el mensaje de error que se muestra informa al usuario que las claves y los valores que se deben insertar deben informarse como una lista o una tupla.



Nuevamente, cuando se lanza una excepción, se crea una instancia de KeyValueConstructError y la clave y los valores se pasan como argumentos al constructor KeyValueConstructError. Se establecen como los valores de los atributos de clave y valor de KeyValueConstructError y se utilizan en el método __str__ para generar un mensaje de error significativo cuando el mensaje se muestra en la pantalla.



Además, incluso incluyo los tipos de datos inherentes a los objetos agregados al constructor init



- Hago esto por claridad.







Configuración de clave y valor en CustomIntFloatDict



CustomIntFloatDict



hereda de dict



. Esto significa que funcionará exactamente como un diccionario, excepto en los lugares que elijamos para cambiar selectivamente su comportamiento.



__setitem__



Es un método mágico llamado al establecer una clave y un valor en un diccionario. En nuestra implementación, setitem



verificamos que el valor sea de tipo int



o float



, y solo después de una verificación exitosa se puede establecer en el diccionario. Si la comprobación falla, puede volver a utilizar la clase de excepción IntFloatValueError



. Aquí podemos asegurarnos de que obtendremos una excepción al intentar establecer una cadena ‘bad_value’



como valor en el diccionario test_4



.







Todo el código de este tutorial se muestra a continuación y se publica en 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'
      
      





Conclusión



Si crea sus propias excepciones, trabajar con la clase se vuelve mucho más conveniente. La clase de excepción debe tener métodos mágicos init



y str



que se invoquen automáticamente durante el manejo de excepciones. Depende completamente de usted lo que hará su propia clase de excepción. Entre los métodos mostrados se encuentran los que se encargan de inspeccionar un objeto y mostrar un mensaje de error informativo en la pantalla.



Sea como sea, las clases de excepción hacen que sea mucho más fácil manejar cualquier error que surja.



All Articles