web-dev-qa-db-ja.com

現代のPythonでカスタム例外を宣言する適切な方法は?

現代のPythonでカスタム例外クラスを宣言する適切な方法は何ですか?私の主な目的は、他の例外クラスの標準に従うことです。そのため、(たとえば)例外に含まれている余分な文字列は、例外を検出したツールによって出力されます。

「現代のPython」とは、Python 2.5で動作するが、Python 2.6およびPython 3には「正しい」ものを意味します。そして「カスタム」とは、エラーの原因に関する追加のデータを含むことができるExceptionオブジェクトを意味します。文字列、あるいは例外に関連するその他の任意のオブジェクトでもあります。

私は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

BaseExceptionmessageという名前の属性に特別な意味を持つのはおかしいようです。 PEP-352 その属性は、2.5で廃止予定の特別な意味を持っていたので、その名前(とその名前だけ)は現在禁止されていると思いますか?うーん。

私はまたExceptionが何らかのargsという魔法のパラメータを持っていることをあいまいに認識しています、しかし私はそれを使う方法を知りませんでした。それは、物事を進めるための正しい方法だとも私は確信していません。私がオンラインで見つけた多くの議論は彼らがPython 3の引数を取り除こうとしていることを示唆しました。

更新:__init____str__/__unicode__/__repr__をオーバーライドすることを2つの回答が示唆しています。それは多くのタイピングのように思えます、それは必要ですか?

1073
Nelson

多分私は質問を逃したが、なぜそうではない:

class MyException(Exception):
    pass

編集: 何かを上書きする(または追加の引数を渡す)には、次の操作を行います。

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

そうすれば、エラーメッセージを2番目のパラメータに渡し、後でe.errorsを使ってそれにアクセスできます。


Python 3 Update: Python 3+では、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
1111
gahooa

最近のPython例外では、.messageを悪用したり、.__str__().__repr__()、またはそのいずれかをオーバーライドする必要はありません。例外が発生したときに必要な情報が有益なメッセージだけである場合は、次の操作を行います。

class MyException(Exception):
    pass

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

それはMyException: My hovercraft is full of eelsで終わるトレースバックを与えるでしょう。

例外からもっと柔軟にしたい場合は、引数として辞書を渡すことができます。

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

しかし、exceptブロックでそれらの詳細を理解するのはもう少し複雑です。詳細はリストであるargs属性に格納されます。あなたはこのようなことをする必要があるでしょう:

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

複数の項目を例外に渡してTupleインデックスを介してそれらにアクセスすることは依然として可能ですが、これは 強く推奨されていません (そしてしばらくの間は廃止予定です)複数の情報が必要で、上記の方法では不十分な場合は、 tutorial で説明されているようにExceptionをサブクラス化する必要があります。

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

「現代のPythonでカスタム例外を宣言する適切な方法は?」

あなたの例外が本当により具体的な例外の一種でない限り、これは問題ありません。

class MyException(Exception):
    pass

passの代わりにdocstringを与えるといいでしょう(おそらく完璧)。

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

例外サブクラスのサブクラス化

docs から

Exception

組み込みの、システムを終了しない例外はすべてこのクラスから派生しています。すべてのユーザー定義の例外もこのクラスから派生する必要があります。

つまり、 if あなたの例外はより具体的な例外の一種であり、一般的なExceptionではなくその例外をサブクラス化します(そして結果として、ドキュメントの推奨どおりExceptionから派生することになります)。また、少なくともdocstringを提供できます(そしてpassキーワードを使用することを強制されないでください)。

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

自分で作成した属性をカスタムの__init__で設定します。位置引数として辞書を渡さないでください、あなたのコードの将来のユーザはあなたに感謝します。非推奨のメッセージ属性を使用する場合は、自分で割り当てることで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) 

あなた自身の__str____repr__を書く必要は本当にありません。組み込みのものはとても素敵で、あなたの 協調的な継承 はあなたがそれを使うことを保証します。

トップ回答に対する批判

多分私は質問を逃したが、なぜそうではない:

class MyException(Exception):
    pass

繰り返しますが、上記の問題は、それを捉えるためには、それを具体的に名前を付ける(他の場所で作成された場合はインポートする)か、Exceptionをキャッチする必要があるということです。そして、あなたはあなたが扱う用意ができている例外だけを捕らえるべきです)。以下と同様の批判がありますが、それはsuperで初期化する方法ではありません。また、message属性にアクセスするとDeprecationWarningが返されます。

編集:何かを上書きする(または追加の引数を渡す)には、次の操作を行います。

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

このようにして、エラーメッセージの辞書を2番目のパラメータに渡し、後でe.errorsを使用して取得できます

selfは別として)2つの引数を渡す必要があります。これは、将来のユーザーが評価しないかもしれないという興味深い制約です。

直接的に - それは違反しています Liskovの代用性

両方のエラーについて説明します。

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

に比べ:

>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'
171
Aaron Hall

1つの vs 複数の属性が使用されている場合(トレースバックは省略)、デフォルトで例外がどのように機能するかを確認します。

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

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

そのため、一種の " 例外テンプレート "を互換性のある方法で例外として機能させることができます。

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

これはこのサブクラスで簡単にできます

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

そのデフォルトのTuple風の表現が気に入らない場合は、ExceptionTemplateクラスに__str__メソッドを追加するだけです。

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

そしてあなたは

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

Messageを使用する代わりに__repr__または__unicode__メソッドをオーバーライドする必要があります。例外を構築するときに指定する引数は、例外オブジェクトのargs属性にあります。

15

いいえ、「メッセージ」は禁止されていません。廃止予定です。あなたのアプリケーションはメッセージを使用してうまく動作します。しかし、もちろん、廃止予定のエラーを取り除きたいと思うかもしれません。

アプリケーション用のカスタムExceptionクラスを作成する場合、それらの多くはExceptionからだけでなく、ValueErrorなどの他のクラスからもサブクラス化されます。それからあなたはそれらの変数の使用法に適応する必要があります。

そして、あなたがあなたのアプリケーションに多くの例外があるなら、それはあなたのモジュールのユーザができるように、それらすべてのための共通のカスタム基本クラスを持つことは通常良い考えです。

try:
    ...
except NelsonsExceptions:
    ...

そしてその場合、あなたはそこで必要とされる__init__ and __str__をすることができます、それであなたはすべての例外のためにそれを繰り返す必要はありません。しかし、単純にメッセージ変数をmessage以外の何かに呼び出すだけでうまくいきます。

いずれにせよ、__init__ or __str__が必要なのは、Exception自体とは異なることをした場合だけです。そして、廃止予定の場合は、その両方が必要になるか、エラーが発生します。それはあなたがクラスごとに必要な余分なコードの全部ではありません。 ;)

6
Lennart Regebro

Python 3.8 (2018、 https://docs.python.org/dev/whatsnew/3.8.html )以降、推奨される方法はまだ次のとおりです。

class CustomExceptionName(Exception):
    """Exception raised when very uncommon things happen"""
    pass

カスタム例外が必要な理由を文書化することを忘れないでください!

あなたがする必要があるなら、これはより多くのデータを持つ例外を探すための方法です:

class CustomExceptionName(Exception):
    """Still an exception raised when uncommon things happen"""
    def __init__(self, message, payload=None):
        self.message = message
        self.payload = payload # you could add more args
    def __str__(self):
        return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types

そしてそれらを次のように取得します。

try:
    raise CustomExceptionName("Very bad mistake.", "Forgot upgrading from Python 1")
except CustomExceptionName as error:
    print(str(error)) # Very bad mistake
    print("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1

payload=Noneは漬け込み可能にするために重要です。それをダンプする前に、あなたはerror.__reduce__()を呼ばなければなりません。ロードは期待どおりに機能します。

何らかの外部構造に転送するために大量のデータが必要な場合は、pythons returnステートメントを使用して解決策を見つけることを検討する必要があります。これは私にとってはっきりしている/よりPythonicであるように思われる。 Javaでは高度な例外が頻繁に使用されていますが、フレームワークを使用していて可能性のあるすべてのエラーを検出しなければならない場合は、厄介なこともあります。

6
fameman

この例を試す

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")
1
Omkaar.K

非常に良い記事「 The Definitive Guide to Python exceptions 」を参照してください。基本原則は次のとおりです。

  • 常に(少なくとも)例外から継承します。
  • 常に1つの引数のみでBaseException.__init__を呼び出します。
  • ライブラリを構築するときは、例外を継承する基本クラスを定義します。
  • エラーに関する詳細を提供します。
  • 理にかなっている場合は、組み込み例外の型から継承します。

例外の整理(モジュール単位)とラッピングに関する情報もあります。ガイドを読むことをお勧めします。

1