現代の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で廃止予定の特別な意味を持っていたので、その名前(とその名前だけ)は現在禁止されていると思いますか?うーん。
私はまたException
が何らかのargs
という魔法のパラメータを持っていることをあいまいに認識しています、しかし私はそれを使う方法を知りませんでした。それは、物事を進めるための正しい方法だとも私は確信していません。私がオンラインで見つけた多くの議論は彼らがPython 3の引数を取り除こうとしていることを示唆しました。
更新:__init__
と__str__
/__unicode__
/__repr__
をオーバーライドすることを2つの回答が示唆しています。それは多くのタイピングのように思えます、それは必要ですか?
多分私は質問を逃したが、なぜそうではない:
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
最近の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
「現代の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'
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
Messageを使用する代わりに__repr__
または__unicode__
メソッドをオーバーライドする必要があります。例外を構築するときに指定する引数は、例外オブジェクトのargs
属性にあります。
いいえ、「メッセージ」は禁止されていません。廃止予定です。あなたのアプリケーションはメッセージを使用してうまく動作します。しかし、もちろん、廃止予定のエラーを取り除きたいと思うかもしれません。
アプリケーション用のカスタムExceptionクラスを作成する場合、それらの多くはExceptionからだけでなく、ValueErrorなどの他のクラスからもサブクラス化されます。それからあなたはそれらの変数の使用法に適応する必要があります。
そして、あなたがあなたのアプリケーションに多くの例外があるなら、それはあなたのモジュールのユーザができるように、それらすべてのための共通のカスタム基本クラスを持つことは通常良い考えです。
try:
...
except NelsonsExceptions:
...
そしてその場合、あなたはそこで必要とされる__init__ and __str__
をすることができます、それであなたはすべての例外のためにそれを繰り返す必要はありません。しかし、単純にメッセージ変数をmessage以外の何かに呼び出すだけでうまくいきます。
いずれにせよ、__init__ or __str__
が必要なのは、Exception自体とは異なることをした場合だけです。そして、廃止予定の場合は、その両方が必要になるか、エラーが発生します。それはあなたがクラスごとに必要な余分なコードの全部ではありません。 ;)
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では高度な例外が頻繁に使用されていますが、フレームワークを使用していて可能性のあるすべてのエラーを検出しなければならない場合は、厄介なこともあります。
この例を試す
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")
非常に良い記事「 The Definitive Guide to Python exceptions 」を参照してください。基本原則は次のとおりです。
BaseException.__init__
を呼び出します。例外の整理(モジュール単位)とラッピングに関する情報もあります。ガイドを読むことをお勧めします。