設計 - python 例外処理 ベストプラクティス




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

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

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

class MyException(Exception):
    pass

または、より良い(おそらく完璧な)、 pass代わりにdocstring pass与える:

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

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

docsから

Exception

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

つまり、例外がより具体的な例外のタイプである場合 、汎用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__または__repr__を書く必要はありません。 組み込みのものは非常に使いやすく、 協力して継承することで確実に使用できます。

トップアンケートの批判

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

class MyException(Exception):
    pass

繰り返しますが、上記の問題は、それをキャッチするために、別の名前を付けて(別の場所に作成した場合はインポートする)、Exceptionをキャッチする必要があります(ただし、すべてのタイプの例外を処理する準備ができていない可能性があります。処理できるように準備されている例外だけをキャッチする必要があります)。 以下同様の批判がありますが、これは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

その方法で、エラーメッセージのdictを2番目のパラメータに渡し、後でe.errorsで取得できます

それはまた、渡される正確に2つの議論を必要とする( self除いて)。 これは、将来のユーザーが気付かないという興味深い制約です。

直接的であるために、それは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'

現代の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という名前の属性に特別な意味があることは夢中にBaseException message 。 私はPEP-352から属性が2.5で特別な意味を持っていることを知っているので、その名前は廃止されようとしています。 ああ。

また、 Exceptionはいくつかの魔法のパラメータargsがあることを知っていExceptionが、私はそれを使う方法を知らなかった。 私はそれが今後のことを行う正しい方法だとは確信していません。 私がオンラインで見つけた議論の多くは、Python 3でargsを取り除こうとしていることを示唆していました。

更新: __str____str__ / __unicode__ / __repr__オーバーライドすることを提案しています。 それはタイピングのように思える、それは必要ですか?


1つ以上の属性が使用されている場合(トレースバックは省略されている)、例外がどのように機能するかを参照してください。

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

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

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

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

try:
    ...
except NelsonsExceptions:
    ...

その場合__init__ and __str__必要になるので、すべての例外に対してそれを繰り返す必要はありません。 しかし、message変数以外の何かをmessage以外のもので呼び出すことは、そのトリックを行います。

いずれにしても、例外自体とは異なる何かをする場合は__init__ or __str__だけが必要です。 また、非推奨の場合は、両方を必要とするため、エラーが発生します。 それはクラスごとに必要な余分なコードがたくさんあるわけではありません。 ;)


この例を試す

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

現代のPythonの例外では、 .messageを乱用する必要はありません。 .message.__str__()または.__repr__()またはそのいずれかを上書きする必要はありません。 例外が発生したときに有益なメッセージが表示される場合は、次のようにします。

class MyException(Exception):
    pass

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

それは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"])

例外に複数の項目を渡してタプル・インデックスを使ってアクセスすることは可能ですが、これは非常に推奨されません。 1つ以上の情報が必要で、上記の方法では不十分な場合は、 tutorial説明に従ってExceptionサブクラス化を行う必要があります。

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




exception