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


1290

最新の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

BaseExceptionという名前の属性に特別な意味があるのはおかしいようmessageです。私がPEP-352から収集した属性は、2.5で特別な意味を持っていたため、廃止しようとしているので、その名前(およびその1つだけ)は現在禁止されていると思いますか?ああ。

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

更新:2つの回答により、、__init__および__str__/ __unicode__/のオーバーライドが提案されています__repr__。タイピングが多いようですが、必要ですか?

回答:


1322

多分私は質問を逃しました、しかしなぜそうしないのですか:

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の更新: 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

35
ただし、このように定義された例外は選択できません。ここでの議論は見stackoverflow.com/questions/16244923/...
jiakai

86
@jiakaiは「ピックル可能」を意味します。:-)
Robino

1
ユーザー定義の例外に関するpythonのドキュメントに従って、__ init__関数で言及されている名前は正しくありません。(self、message、error)の代わりに(self、expression、message)です。属性式はエラーが発生した入力式であり、メッセージはエラーの説明です。
ddleon

2
それは誤解です、@ ddleon。参照しているドキュメント内の例は、特定の使用例です。サブクラスのコンストラクター引数の名前(およびその数)に意味はありません。
asthasr

498

最新の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"])

複数の項目を例外に渡し、タプルインデックスを介してそれらにアクセスすることは依然として可能ですが、これは推奨されませ(しばらくの間非推奨にすることさえ意図されていました)。複数の情報が必要で、上記の方法では不十分な場合Exceptionは、チュートリアルで説明されているようにサブクラス化する必要があります。

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

2
「しかし、これは将来廃止される予定です」-これはまだ非推奨を意図していますか?Python 3.7はまだ喜んで受け入れているようException(foo, bar, qux)です。
mtraceur

移行の苦痛のために最後の試みが失敗して以来、それを剥奪するための最近の作業は見ていませんが、その使用はまだお勧めできません。私はそれを反映するために私の答えを更新します。
frnknstn 2018年

@frnknstn、なぜそれが推奨されないのですか?私にはいい慣用句のように見えます。
18年

2
@nevesは最初に、タプルを使用して例外情報を格納しても、同じことを行うためにディクショナリを使用するよりも利点がありません。例外の変更の背後にある理由に興味がある場合は、PEP352をご覧ください
frnknstn

PEP352の関連セクションは「撤回されたアイデア」です。
liberforce

196

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

あなたの例外が本当により具体的な例外のタイプでない限り、これは問題ありません:

class MyException(Exception):
    pass

passdocstring を与える代わりに、より良い(多分完璧かもしれません):

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

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

ドキュメントから

Exception

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

つまり、例外がより具体的な例外のタイプである場合、ジェネリックの代わりにその例外をサブクラス化しますException(その結果Exception、ドキュメントが推奨するとおりに引き続き派生します)。また、少なくともdocstringを提供できます(passキーワードを強制的に使用する必要はありません)。

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

カスタムで自分で作成した属性を設定します__init__。dictを位置引数として渡さないでください。将来のコードのユーザーはあなたに感謝します。非推奨のメッセージ属性を使用する場合、自分で割り当てると、次のような事態を回避できます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__or を書く必要は本当にありません__repr__。ビルトインのものはとても素晴らしいです、そしてあなたの協調的な継承はあなたがそれを使うことを保証します。

トップアンサーの批評

多分私は質問を逃しました、しかしなぜそうしないのですか:

class MyException(Exception):
    pass

繰り返しますが、上記の問題は、それをキャッチするために、具体的に名前を付ける(他の場所で作成された場合はインポートする)か、例外をキャッチする必要があることです(ただし、すべてのタイプの例外を処理する準備が整っていない可能性があります)。処理する準備ができている例外のみをキャッチする必要があります)。以下と同様の批判superですが、それはを介して初期化する方法ではありません。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つの引数を渡す必要もあります。これは、将来のユーザーが理解できないかもしれない興味深い制約です。

直接的であること- リスコフの代替可能性に違反します。

両方のエラーを説明します。

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

2
2018年からこんにちは!BaseException.messagePython 3でなくなったので、批判は古いバージョンにのみ当てはまりますね?
コス

5
@Kosリスコフ代替性に関する批評はまだ有効です。「メッセージ」としての最初の議論のセマンティクスも間違いなく疑わしいですが、私はその点について議論するつもりはないと思います。暇な時間があれば、もっと見ていきます。
アーロンホール

1
FWIW、Python 3(少なくとも3.6+の場合)では、属性に依存する代わりに__str__メソッドを再定義MyAppValueErrormessageます
Jacquot

1
@AaronHall Exceptionではなく、ValueErrorをサブクラス化することのメリットを拡大していただけますか?あなたはこれがドキュメントが意味するものであると述べていますが、直読ではその解釈がサポートされておらず、Pythonチュートリアルの「ユーザー定義の例外」では、ユーザーが選択できることは明らかです。直接的または間接的に。」したがって、あなたの見解が正当であるかどうかを理解することに熱心です。
ostergaard 2018

1
@ostergaard現在完全に回答することはできませんが、要するに、ユーザーはキャッチする追加オプションを取得しますValueError。これが値エラーのカテゴリにある場合、これは理にかなっています。値エラーのカテゴリに含まれていない場合は、セマンティクスについては反対するでしょう。プログラマーには多少のニュアンスと推論の余地がありますが、私は適用可能な場合は具体性を優先します。すぐにこの問題に取り組むために、回答を更新します。
アーロンホール

50

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

そして、そのデフォルトのタプルのような表現が気に入らない場合は__str__ExceptionTemplateクラスにメソッドを追加するだけです:

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

そしてあなたは

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

32

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__()。ロードは期待どおりに機能します。

return多くのデータを外部構造に転送する必要がある場合は、pythons ステートメントを使用してソリューションを見つけることを検討する必要があります。これは私にはより明確/よりpythonicのようです。高度な例外はJavaで頻繁に使用されますが、フレームワークを使用し、すべての可能なエラーをキャッチする必要がある場合、煩わしい場合があります。


1
少なくとも、現在のドキュメントは、これが。__str__を使用する他の回答ではなく(少なくともなしで)実行する方法であることを示していますsuper().__init__(...)。オーバーライドして__str____repr__おそらく「デフォルト」のシリアル化を改善するために必要なだけの恥です。
kevlarr

2
正直な質問:例外をピクル可能にすることがなぜ重要なのですか?例外のダンプとロードのユースケースは何ですか?
Roel Schroeven、

1
@RoelSchroeven:コードを一度並列化する必要がありました。単一のプロセスを実行しましたが、そのクラスの一部の側面はシリアル化できませんでした(ラムダ関数がオブジェクトとして渡されています)。それを理解し、修正するのに少し時間がかかりました。後で誰かがコードをシリアル化する必要が生じ、それを実行できず、その理由を掘り下げる必要があることを意味します...私の問題は重大なエラーではありませんでしたが、同様の問題を引き起こしていることがわかります。
logicOnAbstractions

17

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


7

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

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

また、アプリケーションに多くの例外がある場合、通常、それらすべてに共通のカスタム基本クラスを用意して、モジュールのユーザーができるようにすることをお勧めします。

try:
    ...
except NelsonsExceptions:
    ...

そしてその場合、__init__ and __str__必要なことをそこで行うことができるので、例外ごとに繰り返す必要はありません。しかし、メッセージ変数をメッセージ以外のものと呼ぶだけでうまくいきます。

いずれにせよ、必要なのは__init__ or __str__、Exception自体が行うこととは異なる何かを行う場合だけです。また、廃止予定の場合、両方が必要になるか、エラーが発生するためです。クラスごとに必要な追加のコードはそれほど多くありません。;)


Djangoの例外が共通のベースから継承しないのは興味深いことです。docs.djangoproject.com/en/2.2/_modules/django/core/exceptions特定のアプリケーションからのすべての例外をキャッチする必要がある場合の良い例はありますか?(多分それはいくつかの特定のタイプのアプリケーションでのみ有用です)。
Yaroslav Nikitenko

このトピックについての良い記事、julien.danjou.info / python-exceptions-guideを見つけました。例外は、アプリケーションベースではなく、主にドメインベースでサブクラス化する必要があると思います。アプリがHTTPプロトコルに関するものである場合、HTTPErrorから派生します。アプリの一部がTCPである場合、TCPErrorからその部分の例外を派生させます。ただし、アプリが多数のドメイン(ファイル、権限など)にまたがっている場合、MyBaseExceptionが発生する理由は少なくなります。それとも「レイヤー違反」から守るためですか?
Yaroslav Nikitenko

6

非常に優れた記事「Python例外の決定的なガイド」を参照してください。基本原則は次のとおりです。

  • 常に(少なくとも)例外から継承します。
  • 常にBaseException.__init__1つの引数のみで呼び出します。
  • ライブラリを構築するときは、Exceptionから継承する基本クラスを定義します。
  • エラーの詳細を提供します。
  • 必要に応じて、組み込みの例外タイプから継承します。

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


1
これは、SOで通常最も投票された回答をチェックする理由の良い例ですが、最新の回答も確認します。役に立つ追加、ありがとう。
logicOnAbstractions

1
Always call BaseException.__init__ with only one argument.実際には任意の数の引数を受け入れるため、不要な制約のように見えます。
Eugene Yarmash

@EugeneYarmash同意しますが、今は理解できません。とにかく使いません。多分私は記事をもう一度読んで私の答えを広げるべきです。
Yaroslav Nikitenko

@EugeneYarmash私は再び記事を読みました。いくつかの引数がある場合、Cの実装は「return PyObject_Str(self-> args);」を呼び出すと述べられています。これは、1つの文字列が複数の文字列よりも適切に機能することを意味します。確認しましたか?
Yaroslav Nikitenko

3

この例を試す

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

独自の例外を正しく定義するには、いくつかのベストプラクティスに従う必要があります。

  • から継承する基本クラスを定義しExceptionます。これにより、プロジェクトに関連するすべての例外をキャッチできます(より具体的な例外はプロジェクトから継承する必要があります)。

    class MyProjectError(Exception):
        """A base class for MyProject exceptions."""

    これらの例外クラスを別のモジュール(例:)に編成することexceptions.pyは、一般的に良い考えです。

  • カスタム例外を作成するには、基本例外クラスをサブクラス化します。

  • カスタム例外に追加の引数のサポートを追加するに__init__()は、可変数の引数でカスタムメソッドを定義します。基本クラスのを呼び出し、__init__()任意の位置引数をそれに渡します(BaseException/Exceptionは任意の数の位置引数を期待することに注意してください)。

    class CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            super(CustomError, self).__init__(*args)
            self.foo = kwargs.get('foo')

    追加の引数を使用してそのような例外を発生させるには、以下を使用できます。

    raise CustomError('Something bad happened', foo='foo')

ベース例外クラスのインスタンスを派生例外クラスのインスタンスで置き換えることができるため、この設計はリスコフ置換原則に準拠しています。また、親と同じパラメーターを持つ派生クラスのインスタンスを作成できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.