try except python




Modo corretto per dichiarare eccezioni personalizzate nel moderno Python? (5)

Qual è il modo corretto per dichiarare le classi di eccezioni personalizzate nel moderno Python? Il mio obiettivo principale è quello di seguire qualsiasi altra classe di eccezioni standard, in modo che (per esempio) qualsiasi stringa aggiuntiva che includo nell'eccezione venga stampata da qualunque strumento abbia rilevato l'eccezione.

Con "Python moderno" intendo qualcosa che verrà eseguito in Python 2.5 ma che sia 'corretto' per il modo Python 2.6 e Python 3. * di fare le cose. E per "personalizzato" intendo un oggetto Exception che può includere dati aggiuntivi sulla causa dell'errore: una stringa, forse anche qualche altro oggetto arbitrario rilevante per l'eccezione.

Sono stato bloccato dal seguente avviso di deprecazione in 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

Sembra BaseException che BaseException abbia un significato speciale per gli attributi denominati message . Raccolgo da PEP-352 che l'attributo aveva un significato speciale in 2.5, stanno cercando di svalutare, quindi immagino che quel nome (e quello solo) sia ora proibito? Ugh.

Sono anche incredibilmente consapevole del fatto che Exception ha alcuni args parametri magici, ma non ho mai saputo come usarlo. Né sono sicuro che sia il modo giusto di fare le cose andando avanti; gran parte della discussione che ho trovato online ha suggerito che stavano cercando di eliminare argomenti in Python 3.

Aggiornamento: due risposte hanno suggerito di sovrascrivere __init__ e __str__ / __unicode__ / __repr__ . Sembra un sacco di battitura, è necessario?


"Modo corretto per dichiarare eccezioni personalizzate nel moderno Python?"

Questo va bene, a meno che la tua eccezione sia davvero un tipo di un'eccezione più specifica:

class MyException(Exception):
    pass

O meglio (forse perfetto), invece di dare una docstring:

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

Sottoclassi di sottoclassi di eccezioni

Dai docs

Exception

Tutte le eccezioni predefinite non di sistema incorporate derivano da questa classe. Tutte le eccezioni definite dall'utente dovrebbero anche essere derivate da questa classe.

Ciò significa che se la tua eccezione è un tipo di un'eccezione più specifica, sottoclassi quell'eccezione al posto dell'eccezione generica (e il risultato sarà che tu continui a derivare da Exception come consigliano i documenti). Inoltre, è possibile fornire almeno una docstring (e non essere obbligati a utilizzare la parola chiave pass ):

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

Imposta gli attributi che crei tu stesso con un __init__ personalizzato. Evita di passare un dict come argomento posizionale, i futuri utenti del tuo codice ti ringrazieranno. Se si utilizza l'attributo del messaggio deprecato, assegnandolo da sé si eviterà 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) 

Non c'è davvero bisogno di scrivere il proprio __str__ o __repr__ . Quelli incorporati sono molto belli e la tua eredità cooperativa ti assicura che tu la usi.

Critica della risposta superiore

Forse ho perso la domanda, ma perché no?

class MyException(Exception):
    pass

Ancora una volta, il problema di cui sopra è che per poterlo prendere, dovrai o chiamarlo in modo specifico (importarlo se creato altrove) o prendere Exception, (ma probabilmente non sei preparato a gestire tutti i tipi di Eccezioni, e dovresti solo prendere delle eccezioni che sei pronto a gestire). Critica simile al seguente, ma in aggiunta non è il modo di inizializzare tramite super , e otterrai un DeprecationWarning se DeprecationWarning all'attributo del messaggio:

Modifica: per sovrascrivere qualcosa (o passare argomenti aggiuntivi), fai questo:

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

In questo modo potresti passare il messaggio di errore al secondo param, e andare più tardi con e.errors

Richiede anche due argomenti da passare (a parte il self ). Niente di più, niente di meno. Questo è un vincolo interessante che i futuri utenti potrebbero non apprezzare.

Essere diretto - viola la sostituibilità di Liskov .

Mostrerò entrambi gli errori:

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

Rispetto a:

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

Con le moderne eccezioni di Python, non è necessario abusare di .message o sovrascrivere .__str__() o .__repr__() o nessuna di queste. Se tutto ciò che vuoi è un messaggio informativo quando viene sollevata l'eccezione, fai questo:

class MyException(Exception):
    pass

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

Questo darà un traceback che termina con MyException: My hovercraft is full of eels .

Se si desidera maggiore flessibilità dall'eccezione, è possibile passare un dizionario come argomento:

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

Tuttavia, ottenere quei dettagli in un blocco except è un po 'più complicato. I dettagli sono memorizzati nell'attributo args , che è una lista. Dovresti fare qualcosa del genere:

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

È ancora possibile passare in più oggetti all'eccezione e accedervi tramite gli indici di tuple, ma questo è altamente sconsigliato (ed è stato anche previsto per la deprecazione qualche tempo fa). Se hai bisogno di più di una singola informazione e il metodo di cui sopra non è sufficiente per te, allora dovresti creare una sottoclasse di Exception come descritto nel tutorial .

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

Forse ho perso la domanda, ma perché no?

class MyException(Exception):
    pass

Modifica: per sovrascrivere qualcosa (o passare argomenti aggiuntivi), fai questo:

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

In questo modo potresti passare il messaggio di errore al secondo param, e andare più tardi con e.errors

Aggiornamento Python 3: in Python 3+, puoi utilizzare questo uso leggermente più compatto di 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

No, "messaggio" non è vietato. È solo deprecato. L'applicazione funzionerà correttamente con l'utilizzo del messaggio. Ma potresti voler eliminare l'errore di deprecazione, ovviamente.

Quando crei classi di eccezioni personalizzate per la tua applicazione, molte di esse non eseguono la sottoclasse solo da Exception, ma da altre, come ValueError o simili. Quindi devi adattarti al loro uso delle variabili.

E se hai molte eccezioni nella tua applicazione, di solito è una buona idea avere una classe base personalizzata per tutti, in modo che gli utenti dei tuoi moduli possano fare

try:
    ...
except NelsonsExceptions:
    ...

E in questo caso puoi fare in modo che __init__ and __str__ necessari, quindi non devi ripeterlo per ogni eccezione. Ma semplicemente chiamare la variabile del messaggio qualcos'altro che il messaggio fa il trucco.

In ogni caso, hai solo bisogno di __init__ or __str__ se fai qualcosa di diverso da ciò che fa l'eccezione stessa. E perché se la deprecazione, allora avete bisogno di entrambi, o si ottiene un errore. Questo non è un sacco di codice extra di cui hai bisogno per classe. ;)


vedere come funzionano le eccezioni per impostazione predefinita se vengono utilizzati uno o più attributi (tracebacks omessi):

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

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

quindi potresti voler avere una sorta di " modello di eccezione ", che funziona come un'eccezione stessa, in un modo compatibile:

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

questo può essere fatto facilmente con questa sottoclasse

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

e se non ti piace quella rappresentazione tupla predefinita, aggiungi il metodo __str__ alla classe ExceptionTemplate , come:

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

e tu avrai

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




exception