Pregunta ¿La forma correcta de declarar excepciones personalizadas en Python moderno?


¿Cuál es la forma correcta de declarar clases de excepciones 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 con la herramienta que capture la excepción.

Por "Python moderno" me refiero a algo que se ejecutará en Python 2.5 pero que será 'correcto' para Python 2.6 y Python 3. * la forma de hacer las cosas. Y por "personalizado" me refiero a un objeto Exception 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 desactivació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 BaseException tiene un significado especial para los atributos nombrados message. Me reúno de PEP-352 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 muy consciente de que Exception tiene algún parámetro mágico args, pero nunca he sabido cómo usarlo. Tampoco estoy seguro de que sea la manera correcta de hacer las cosas en el futuro; Mucha de la discusión que encontré en línea sugirió que estaban tratando de acabar con los argumentos en Python 3.

Actualización: dos respuestas han sugerido anular __init__y __str__/__unicode__/__repr__. Parece una gran cantidad de tipeo, ¿es necesario?


892
2017-08-23 21:29


origen


Respuestas:


Tal vez me perdí la pregunta, pero ¿por qué no?

class MyException(Exception):
    pass

Editar: para anular algo (o pasar argumentos extra), haz 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 forma, podría pasar el dict de los mensajes de error al segundo parámetro y llegar a él más tarde con e.errors


Actualización de Python 3: En Python 3+, puede usar este uso un poco más compacto de super():

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

924
2017-08-23 21:55



Con las modernas excepciones de Python, no necesita abusar .message, o anular .__str__() o .__repr__() o cualquiera de eso. Si todo lo que desea es un mensaje informativo cuando se presenta su excepción, haga esto:

class MyException(Exception):
    pass

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

Eso dará un traceback que termina con MyException: My hovercraft is full of eels.

Si desea 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, para obtener esos detalles en una except el bloqueo es un poco más complicado. Los detalles se almacenan en args atributo, que es una lista. Deberías 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 desalentado (e incluso fue pensado para desaprobar 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.

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message

359
2018-04-22 18:18



"¿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 docstring:

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

Subclases de subclases de excepción

Desde el 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 este   clase.

Eso significa que Si su excepción es un tipo de excepción más específica, una subclase que la excepción en lugar del genérico Exception (y el resultado será que todavía deriva de Exception como recomiendan los doctores). Además, al menos puede proporcionar un docstring (y no ser forzado a usar el pass palabra clave):

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

Establezca los atributos que crea usted mismo con una costumbre __init__. Evite pasar un dict como un argumento posicional, los futuros usuarios de su código se lo agradecerán. Si usa el atributo de mensaje obsoleto, al asignarlo usted mismo evitará un 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 hay necesidad de escribir su propio __str__ o __repr__. Los integrados son muy agradables, y tu herencia cooperativa asegura que lo usas

Crítica de la respuesta superior

Tal vez me perdí la pregunta, pero ¿por qué no?

class MyException(Exception):
    pass

Nuevamente, el problema con lo anterior es que para atraparlo, tendrá que nombrarlo específicamente (importarlo si se creó en otro lugar) o capturar Excepción, (pero probablemente no esté preparado para manejar todo tipo de excepciones, y solo debería detectar 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 supery obtendrás un DeprecationWarning si accede al atributo de mensaje:

Editar: para anular algo (o pasar argumentos extra), haz 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 esta forma, podría pasar el dict de los mensajes de error al segundo parámetro y llegar a él más tarde con e.errors

También requiere que se pasen exactamente dos argumentos (aparte del self.) Ni mas ni menos. Esa es una limitación interesante que los futuros usuarios pueden no apreciar.

Para ser directo, viola Sustituibilidad de Liskov.

Voy a 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'

Comparado con:

>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'

150
2017-11-14 21:09



ver cómo funcionan las excepciones por defecto si uno vs se usan más atributos (se omite el seguimiento):

>>> raise Exception('bad thing happened')
Exception: bad thing happened

>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')

por lo que es posible que desee tener una especie de "plantilla de excepción", trabajando como una excepción en sí misma, de una manera compatible:

>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened

>>> raise nastyerr()
NastyError: bad thing happened

>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')

esto se puede hacer fácilmente con esta subclase

class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

y si no le gusta la representación predeterminada tipo tupla, simplemente agregue __str__ método a la ExceptionTemplate clase, como:

    # ...
    def __str__(self):
        return ': '.join(self.args)

y tendrás

>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken

37
2017-08-07 16:23



Debe anular __repr__ o __unicode__ En vez de utilizar el mensaje, los argumentos que proporcione cuando construya la excepción estarán en el args atributo del objeto de excepción.


14
2017-08-23 21:46



No, "mensaje" no está prohibido. Simplemente está en desuso. Su aplicación funcionará bien con el uso de mensajes. 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 desde Exception, sino desde otras, como ValueError o similar. Entonces debes adaptarte a su uso de variables.

Y si tiene muchas excepciones en su aplicación, generalmente es una buena idea tener una clase base común personalizada para todas ellas, para que los usuarios de sus módulos puedan hacer

try:
    ...
except NelsonsExceptions:
    ...

Y en ese caso puedes hacer __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 funciona.

En cualquier caso, solo necesitas el __init__ or __str__ si haces algo diferente de lo que hace Exception. Y porque si la depreciación, entonces necesita ambos, o recibe un error. Eso no es una gran cantidad de código adicional que necesita por clase. ;)


5
2017-08-23 21:58



Prueba este ejemplo

class InvalidInputError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return repr(self.msg)

inp = int(input("Enter a number between 1 to 10:"))
try:
    if type(inp) != int or inp not in list(range(1,11)):
        raise InvalidInputError
except InvalidInputError:
    print("Invalid input entered")

0
2017-07-22 05:27