¿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 haya detectado la excepción.
Por "Python moderno" me refiero a algo que se ejecutará en Python 2.5 pero será "correcto" para la forma de hacer las cosas Python 2.6 y Python 3. *. Y por "personalizado" me refiero a un objeto de excepción 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 desaprobación 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
. Yo recojo de PEP-352 deduzco que ese atributo tenía un significado especial en 2.5 que están tratando de desaprobar, así que supongo que ese nombre (y ese solo) ahora está prohibido. Ugh
También soy consciente de que Exception
tiene algún parámetro mágicoargs
, pero nunca he sabido cómo usarlo. Tampoco estoy seguro de que sea la forma correcta de hacer las cosas en el futuro; Gran parte de la discusión que encontré en línea sugirió que estaban tratando de eliminar los argumentos en Python 3.
Actualización: dos respuestas han sugerido anular __init__
, y __str__
/ __unicode__
/ __repr__
. Parece mucho escribir, ¿es necesario?
fuente
Con modernas excepciones Python, que no es necesario para el abuso
.message
, o anulación.__str__()
o.__repr__()
o nada de eso. Si todo lo que desea es un mensaje informativo cuando se genera su excepción, haga lo siguiente:Eso dará un rastreo que termina en
MyException: My hovercraft is full of eels
.Si desea más flexibilidad de la excepción, puede pasar un diccionario como argumento:
Sin embargo, llegar a esos detalles en un
except
bloque es un poco más complicado. Los detalles se almacenan en elargs
atributo, que es una lista. Tendría que hacer algo como esto:Todavía es posible pasar múltiples elementos a la excepción y acceder a ellos a través de índices de tupla, pero esto es altamente desaconsejado (e incluso fue pensado para la desvalorización hace un tiempo). Si necesita más que una sola información y el método anterior no es suficiente para usted, entonces debe subclasificar
Exception
como se describe en el tutorial .fuente
Exception(foo, bar, qux)
.Esto está bien, a menos que su excepción sea realmente un tipo de excepción más específica:
O mejor (tal vez perfecto), en lugar de
pass
dar una cadena de documentación:Subclases Excepción Subclases
De los documentos
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 derivaException
como lo recomiendan los documentos). Además, al menos puede proporcionar una cadena de documentación (y no verse obligado a usar lapass
palabra clave):Establezca atributos que cree usted mismo con una costumbre
__init__
. Evite pasar un dict como argumento posicional, los futuros usuarios de su código se lo agradecerán. Si usa el atributo de mensaje en desuso, asignándolo usted mismo evitará unDeprecationWarning
:En realidad no hay necesidad de escribir su propio
__str__
o__repr__
. Los incorporados son muy agradables y su herencia cooperativa asegura que lo use.Crítica de la respuesta superior
Una vez más, el problema con lo anterior es que para atraparlo, tendrá que nombrarlo específicamente (importándolo si se creó en otro lugar) o atrapar Exception (pero probablemente no esté preparado para manejar todo tipo de Excepciones, y solo debe detectar las excepciones que está preparado para manejar). Crítica similar a la siguiente, pero además esa no es la forma de inicializar a través de
super
, y obtendrá unDeprecationWarning
si accede al atributo de mensaje:También requiere que se pasen exactamente dos argumentos (aparte del
self
.) Ni más ni menos. Esa es una restricción interesante que los futuros usuarios pueden no apreciar.Para ser directo, viola la sustituibilidad de Liskov .
Demostraré ambos errores:
Comparado con:
fuente
BaseException.message
se ha ido en Python 3, por lo que la crítica solo es válida para versiones antiguas, ¿verdad?__str__
método enMyAppValueError
lugar de confiar en elmessage
atributoValueError
. Esto tiene sentido si está en la categoría de Errores de valor. Si no está en la categoría de Errores de valor, argumentaría en contra de la semántica. Hay espacio para algunos matices y razonamientos por parte del programador, pero prefiero la especificidad cuando corresponde. Actualizaré mi respuesta para abordar mejor el tema pronto.vea cómo funcionan las excepciones de forma predeterminada si se utilizan uno frente a más atributos (se omiten las trazas):
por lo que es posible que desee tener una especie de " plantilla de excepción ", que funcione como una excepción en sí misma, de manera compatible:
Esto se puede hacer fácilmente con esta subclase
y si no le gusta esa representación de tipo tupla predeterminada, simplemente agregue el
__str__
método a laExceptionTemplate
clase, como:y tendrás
fuente
A partir de Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ), el método recomendado sigue siendo:
¡No olvide documentar, por qué es necesaria una excepción personalizada!
Si lo necesita, este es el camino a seguir para excepciones con más datos:
y traerlos como:
payload=None
Es importante hacer que se pueda conservar en vinagre. Antes de tirarlo, tienes que llamarerror.__reduce__()
. La carga funcionará como se espera.Tal vez debería investigar para encontrar una solución usando la
return
declaración de pitones si necesita transferir muchos datos a alguna estructura externa. Esto parece ser más claro / más pitónico para mí. Las excepciones avanzadas se usan mucho en Java, lo que a veces puede ser molesto, cuando se usa un marco y se tiene que detectar todos los errores posibles.fuente
__str__
) en lugar de otras respuestas que usansuper().__init__(...)
... Es una pena que anule__str__
y__repr__
probablemente sea necesario solo para una mejor serialización "predeterminada".Debe anular
__repr__
o__unicode__
métodos en lugar de usar el mensaje, los argumentos que proporcione cuando construya la excepción estarán en elargs
atributo del objeto de excepción.fuente
No, "mensaje" no está prohibido. Simplemente está en desuso. Su aplicación funcionará bien con el uso del mensaje. Pero es posible que desee deshacerse del error de desaprobación, por supuesto.
Cuando crea clases de Excepción personalizadas para su aplicación, muchas de ellas no se subclasifican solo de Excepción, sino de otras, como ValueError o similar. Luego hay que adaptarse a su uso de variables.
Y si tiene muchas excepciones en su aplicación, generalmente es una buena idea tener una clase base personalizada común para todas ellas, de modo que los usuarios de sus módulos puedan hacer
Y en ese caso puedes hacer el
__init__ and __str__
necesario allí, por lo que no tiene que repetirlo para cada excepción. Pero simplemente llamar a la variable del mensaje algo más que mensaje hace el truco.En cualquier caso, solo necesita el
__init__ or __str__
si hace algo diferente de lo que hace la Excepción misma. Y porque si la desaprobación, entonces necesita ambos, o se obtiene un error. Eso no es una gran cantidad de código adicional que necesita por clase. ;)fuente
Vea un muy buen artículo " La guía definitiva para las excepciones de Python ". Los principios básicos son:
BaseException.__init__
con un solo argumento.También hay información sobre organización (en módulos) y excepciones de envoltura, recomiendo leer la guía.
fuente
Always call BaseException.__init__ with only one argument.
Parece una restricción innecesaria, ya que en realidad acepta cualquier número de argumentos.Prueba este ejemplo
fuente
Para definir sus propias excepciones correctamente, hay algunas prácticas recomendadas que debe seguir:
Definir una clase base heredando de
Exception
. Esto permitirá detectar cualquier excepción relacionada con el proyecto (las excepciones más específicas deben heredar de él):Organizar estas clases de excepción en un módulo separado (por ejemplo
exceptions.py
) es generalmente una buena idea.Para crear una excepción personalizada, subclase la clase de excepción base.
Para agregar soporte para argumentos adicionales a una excepción personalizada, defina un
__init__()
método personalizado con un número variable de argumentos. Llame a la clase base__init__()
, pasándole cualquier argumento posicional (recuerde esoBaseException
/Exception
espere cualquier número de argumentos posicionales ):Para generar dicha excepción con un argumento adicional, puede usar:
Este diseño se adhiere al principio de sustitución de Liskov , ya que puede reemplazar una instancia de una clase de excepción base con una instancia de una clase de excepción derivada. Además, le permite crear una instancia de una clase derivada con los mismos parámetros que el padre.
fuente