¿Forma correcta de declarar excepciones personalizadas en Python moderno?
¿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 Exception
objeto 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 BaseException
tenga 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 Exception
tiene 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__
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)
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 except
bloque es un poco más complicado. Los detalles se almacenan en el args
atributo, 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 Exception
como 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
"¿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 pass
dar 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á Exception
como lo recomiendan los documentos). Además, al menos puede proporcionar una cadena de documentación (y no verse obligado a utilizar la pass
palabra 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
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 DeprecationWarning
si 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'