¿Forma correcta de declarar excepciones personalizadas en Python moderno?

Resuelto Nelson asked hace 15 años • 16 respuestas

¿Cuál es la forma correcta de declarar clases de excepción personalizadas en Python moderno? Mi objetivo principal es seguir cualquier estándar que tengan otras clases de excepción, de modo que (por ejemplo) cualquier cadena adicional que incluya en la excepción se imprima mediante cualquier herramienta que detecte la excepción.

Por "Python moderno" me refiero a algo que se ejecutará en Python 2.5 pero que será "correcto" para la forma de hacer las cosas de Python 2.6 y Python 3.*. Y por "personalizado" me refiero a un Exceptionobjeto que puede incluir datos adicionales sobre la causa del error: una cadena, tal vez también algún otro objeto arbitrario relevante para la excepción.

Me tropecé con la siguiente advertencia de obsolescencia en Python 2.6.2:

>>> class MyError(Exception):
...     def __init__(self, message):
...         self.message = message
... 
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6

Parece una locura que BaseExceptiontenga un significado especial para los atributos nombrados message. Deduzco de PEP-352 que el atributo tenía un significado especial en 2.5 que están tratando de desaprobar, así que supongo que ese nombre (y solo ese) ahora está prohibido. Puaj.

También soy vagamente consciente de que Exceptiontiene algún parámetro mágico args, pero nunca supe cómo usarlo. Tampoco estoy seguro de que sea la manera correcta de hacer las cosas en el futuro; Gran parte de la discusión que encontré en línea sugería que estaban tratando de eliminar los argumentos en Python 3.

Actualización: dos respuestas han sugerido anular __init__y __str__// . Parece mucho escribir, ¿es necesario?__unicode____repr__

Nelson avatar Aug 24 '09 04:08 Nelson
Aceptado

Quizás me perdí la pregunta, pero ¿por qué no?

class MyException(Exception):
    pass

Para anular algo (o pasar argumentos adicionales), haga esto:

class ValidationError(Exception):
    def __init__(self, message, errors):            
        # Call the base class constructor with the parameters it needs
        super().__init__(message)
            
        # Now for your custom code...
        self.errors = errors

De esa manera, podría pasar mensajes de error al segundo parámetro y acceder a él más tarde con e.errors.

En Python 2, debes usar esta forma un poco más compleja de super():

super(ValidationError, self).__init__(message)
gahooa avatar Aug 23 '2009 21:08 gahooa

Con las excepciones modernas de Python, no es necesario abusar de ellas .message, .__str__()ni .__repr__()anularlas. Si todo lo que desea es un mensaje informativo cuando se genere su excepción, haga esto:

class MyException(Exception):
    pass

raise MyException("My hovercraft is full of eels")

Eso dará un rastreo que termina en MyException: My hovercraft is full of eels.

Si desea obtener más flexibilidad de la excepción, puede pasar un diccionario como argumento:

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

Sin embargo, llegar a esos detalles en un exceptbloque es un poco más complicado. Los detalles se almacenan en el argsatributo, que es una lista. Tendrías que hacer algo como esto:

try:
    raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
    details = e.args[0]
    print(details["animal"])

Todavía es posible pasar varios elementos a la excepción y acceder a ellos a través de índices de tupla, pero esto es muy desaconsejable (e incluso estaba previsto que quedara obsoleto hace un tiempo). Si necesita más de una pieza de información y el método anterior no es suficiente para usted, entonces debe crear una subclase Exceptioncomo se describe en el tutorial .

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message
frnknstn avatar Apr 22 '2012 18:04 frnknstn

"¿Cuál es la forma correcta de declarar excepciones personalizadas en Python moderno?"

Esto está bien a menos que su excepción sea realmente un tipo de excepción más específica:

class MyException(Exception):
    pass

O mejor (tal vez perfecto), en lugar de passdar una cadena de documentación:

class MyException(Exception):
    """Raise for my specific kind of exception"""

Subclases de excepción de subclases

De los documentos

Exception

Todas las excepciones integradas que no salen del sistema se derivan de esta clase. Todas las excepciones definidas por el usuario también deben derivarse de esta clase.

Eso significa que si su excepción es un tipo de excepción más específica, subclase esa excepción en lugar de la genérica Exception(y el resultado será el que aún derivará Exceptioncomo lo recomiendan los documentos). Además, al menos puede proporcionar una cadena de documentación (y no verse obligado a utilizar la passpalabra clave):

class MyAppValueError(ValueError):
    '''Raise when my specific value is wrong'''

Establece atributos que creas tú mismo con un archivo personalizado __init__. Evite pasar un dict como argumento posicional, los futuros usuarios de su código se lo agradecerán. Si utiliza el atributo de mensaje obsoleto, asignarlo usted mismo evitará DeprecationWarning:

class MyAppValueError(ValueError):
    '''Raise when a specific subset of values in context of app is wrong'''
    def __init__(self, message, foo, *args):
        self.message = message # without this you may get DeprecationWarning
        # Special attribute you desire with your Error, 
        # perhaps the value that caused the error?:
        self.foo = foo         
        # allow users initialize misc. arguments as any other builtin Error
        super(MyAppValueError, self).__init__(message, foo, *args) 

Realmente no es necesario escribir el tuyo propio __str__o __repr__. Los integrados son muy buenos y su herencia cooperativa garantiza que los utilice.

Crítica de la respuesta principal.

Quizás me perdí la pregunta, pero ¿por qué no?

class MyException(Exception):
    pass

Nuevamente, el problema con lo anterior es que para detectarlo, tendrá que nombrarlo específicamente (importándolo si se creó en otro lugar) o capturar la Excepción (pero probablemente no esté preparado para manejar todos los tipos de Excepciones). y sólo debería detectar excepciones que esté preparado para manejar). Críticas similares a las siguientes, pero además esa no es la forma de inicializar a través de super, y obtendrás un DeprecationWarningsi accedes al atributo de mensaje:

Editar: para anular algo (o pasar argumentos adicionales), haga esto:

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super(ValidationError, self).__init__(message)

        # Now for your custom code...
        self.errors = errors

De esa manera, podría pasar mensajes de error al segundo parámetro y acceder a él más tarde con e.errors.

También requiere que se pasen exactamente dos argumentos (aparte del self.) Ni más ni menos. Se trata de una limitación interesante que quizá los futuros usuarios no aprecien.

Para ser directo, viola la sustituibilidad de Liskov .

Demostraré ambos errores:

>>> ValidationError('foo', 'bar', 'baz').message

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)

>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'

En comparación con:

>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'
Russia Must Remove Putin avatar Nov 14 '2014 21:11 Russia Must Remove Putin