errors - python custom exception



¿Una forma adecuada de declarar excepciones personalizadas en Python moderno? (5)

¿Cuál es la forma correcta de declarar clases de excepción personalizadas en Python moderno? Mi objetivo principal es seguir cualquier otra clase de excepción estándar, de modo que (por ejemplo) cualquier cadena adicional que incluya en la excepción sea impresa por la herramienta que haya capturado la excepción.

Por "Python moderno" me refiero a algo que se ejecutará en Python 2.5 pero será "correcto" para Python 2.6 y Python 3. * forma de hacer las cosas. 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 sorprendió 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 la BaseException tenga un significado especial para los atributos llamados message . Supongo que de PEP-352 ese atributo tenía un significado especial en 2.5 que están tratando de dejar de usarlo, así que supongo que ese nombre (y ese solo) ahora está prohibido. Ugh.

También estoy consciente de que Exception tiene algunos argumentos de parámetros mágicos, 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 intentaban acabar con Args en Python 3.

Actualización: dos respuestas han sugerido sobrescribir __init__ , y __str__ / __unicode__ / __repr__ . Eso parece mucho escribir, ¿es necesario?

https://ffff65535.com


"¿Una forma adecuada 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 da una cadena de documentos:

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

Subclases de Subclases de Excepciones

De los docs

Exception

Todas las excepciones integradas que no se 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 una excepción más específica, subclase esa excepción en lugar de la Exception genérica (y el resultado será que usted todavía deriva de la Exception como lo recomiendan los documentos). Además, al menos puede proporcionar una cadena de documentos (y no estar obligado a usar la palabra clave pass ):

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

Establezca los atributos que cree usted mismo con un __init__ personalizado. Evite pasar un dictado como un argumento posicional, los futuros usuarios de su código se lo agradecerán. Si usa el atributo de mensaje en desuso, al asignarlo usted mismo evitará una 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 tu propio __str__ o __repr__ . Las integradas son muy agradables, y su herencia cooperativa asegura que usted las use.

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 capturarlo, tendrá que nombrarlo específicamente (importándolo si se creó en otro lugar) o capturar Excepción, (pero probablemente no esté preparado para manejar todo tipo de Excepciones, y solo debe detectar las excepciones que está preparado para manejar). Una crítica similar a la de abajo, pero adicionalmente esa no es la manera de inicializar a través de super , y obtendrá una DeprecationWarning si accede al atributo del mensaje:

Editar: para anular algo (o pasar argumentos adicionales), 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 manera, podría pasar mensajes de error a la segunda param, y llegar más tarde con e.errors

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 no pueden apreciar.

Para ser directo, viola la sustituibilidad de Liskov .

Voy a demostrar los dos 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'

Con las excepciones de Python modernas, no necesita abusar de .message , o invalidar .__str__() o .__repr__() o cualquiera de ellas. Si todo lo que desea es un mensaje informativo cuando se produce la excepción, haga lo siguiente:

class MyException(Exception):
    pass

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

Eso dará una respuesta que terminará 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, llegar a esos detalles en un bloque de except es un poco más complicado. Los detalles se almacenan en el atributo args , que es una lista. Necesitarí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 tuplas, pero esto es muy desalentador (e incluso fue destinado a la desaprobación hace un tiempo). Si necesita más que una sola pieza de información y el método anterior no es suficiente para usted, entonces debe 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

No, el "mensaje" no está prohibido. Es sólo en desuso. Tu 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 son subclases solo de Excepción, sino de otras, como ValueError o similar. Entonces tienes que adaptarte a su uso de variables.

Y si tiene muchas excepciones en su aplicación, por lo general es una buena idea tener una clase base personalizada común para todos ellos, para que los usuarios de sus módulos puedan hacerlo.

try:
    ...
except NelsonsExceptions:
    ...

Y en ese caso, puede hacer los __init__ and __str__ necesarios allí, para no tener que repetirlos para cada excepción. Pero simplemente llamando a la variable del mensaje para algo más que el mensaje hace el truco.

En cualquier caso, solo necesita __init__ or __str__ si hace algo diferente de lo que Exception hace. Y porque si la depreciación, a continuación, necesita ambos, o recibe un error. Eso no es un montón de código extra que necesita por clase. ;)


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

ver cómo funcionan las excepciones de forma predeterminada si se usan uno o más atributos (se omiten las trazas):

>>> 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 ", que funciona 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 te gusta esa representación predeterminada similar a una tupla, simplemente agrega el método __str__ a la clase ExceptionTemplate , como:

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

y tendrás

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




exception