Pythonで入れ子になったtry / exceptブロックはプログラミングの良い習慣ですか?


201

私は自分のコンテナを書いています。これは、属性呼び出しによって内部の辞書へのアクセスを提供する必要があります。コンテナの一般的な使用法は次のとおりです。

dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo

このようなものを書くのはばかげているかもしれませんが、それは私が提供する必要がある機能です。私はこれを次の方法で実装することを考えていました:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        try:
            return self.dict[item]
        except KeyError:
            print "The object doesn't have such attribute"

私は別の方法を使用することですので、必ずブロックを除いて、ネストされた試しが/良い練習しているかどうかではないんだhasattr()has_key()

def __getattribute__(self, item):
        if hasattr(self, item):
            return object.__getattribute__(item)
        else:
            if self.dict.has_key(item):
                return self.dict[item]
            else:
                raise AttributeError("some customised error")

または、そのうちの1つを使用して、次のように1つのcatchブロックを試します。

def __getattribute__(self, item):
    if hasattr(self, item):
        return object.__getattribute__(item)
    else:
        try:
            return self.dict[item]
        except KeyError:
            raise AttributeError("some customised error")

どのオプションが最もpythonicでエレガントですか?


提供するpythonの神々に感謝しif 'foo' in dict_container:ます。アーメン。
gseattle

回答:


180

最初の例は完璧です。公式のPythonドキュメントでさえ、EAFPと呼ばれるこのスタイルを推奨しています

個人的には、不要な場合はネストしないようにしています。

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass  # fallback to dict
    try:
        return self.dict[item]
    except KeyError:
        raise AttributeError("The object doesn't have such attribute") from None

PS。has_key()Python 2 item in self.dictでは長い間非推奨になっています。代わりに使用してください。


2
return object.__getattribute__(item)TypeError間違った数の引数が渡されているため、が生成され、が生成されます。代わりににする必要がありますreturn object.__getattribute__(self, item)
martineau 2014年

13
PEP 20:ネストよりもフラットの方が優れています。
Ioannis Filippidis

7
from None最後の行の意味は何ですか?
niklas

2
@niklas本質的に例外コンテキストを抑制します(「この例外の処理中に別の例外が発生しました」-esqueメッセージ)。こちらをご覧ください
Kade

Pythonのドキュメントがネストの試行を推奨しているという事実は、ちょっとおかしいです。それは明らかに恐ろしいスタイルです。このように失敗する可能性のある操作のチェーンを処理する正しい方法は、Pythonがサポートしていないある種のモナド構造を使用することです。
ヘンリーヘンリンソン

19

Javaではフロー制御に例外を使用するのは確かに悪い習慣ですが(主に例外によりjvmがリソースを収集するように強制されるため(詳細はこちら))、Pythonには2つの重要な原則があります:ダックタイピングEAFPです。これは、基本的には、オブジェクトをそれが機能すると思われる方法で使用し、そうでない場合は処理することをお勧めすることを意味します。

要約すると、唯一の問題は、コードがインデントされすぎることです。気に入った場合は、lqcが推奨するようなネストをいくつか簡略化してみてください。


10

特定の例では、実際にそれらをネストする必要はありません。tryブロック内の式が成功すると関数が返されるため、try / exceptブロック全体の後のコードは、最初の試行が失敗した場合にのみ実行されます。だからあなたはただ行うことができます:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass
    # execution only reaches here when try block raised AttributeError
    try:
        return self.dict[item]
    except KeyError:
        print "The object doesn't have such attribute"

それらをネストすることは悪いことではありませんが、平らにしておくと構造がより明確になるように感じます。

ちなみに、ここでは__getattribute__なく本当に使いたいのかと考えたくなるかもしれません__getattr____getattr__通常の属性ルックアッププロセスがすでに失敗していることがわかるので、を使用すると処理が簡単になります。


10

注意してください-この場合、最初finallyに触れられますがスキップされます。

def a(z):
    try:
        100/z
    except ZeroDivisionError:
        try:
            print('x')
        finally:
            return 42
    finally:
        return 1


In [1]: a(0)
x
Out[1]: 1

うわー、それは私の心を吹き飛ばします...この動作を説明するいくつかのドキュメントの断片を教えていただけますか
ミハル

1
@Michal:fyi:に対して両方のfinallyブロックが実行されますがa(0)、親のみfinally-returnが返されます。
SławomirLenart

7

私の意見では、これはそれを処理するための最もPythonicな方法だと思います。これは、内部辞書に保持されている「特別な」属性のみを処理する必要__getattr__()がある__getattribute__()ことを意味するため、代わりにこれを定義することに注意してください。

def __getattr__(self, name):
    """only called when an attribute lookup in the usual places has failed"""
    try:
        return self.my_dict[name]
    except KeyError:
        raise AttributeError("some customized error message")

2
exceptブロックで例外を発生させると、Python 3で出力が混乱する可能性があることに注意してください。これは、(PEP 3134に従って)Python 3が最初の例外(the KeyError)を2番目の例外(the AttributeError)の「コンテキスト」として追跡し、それがトップレベルでは、両方の例外を含むトレースバックを出力します。これは2番目の例外が予期されていなかった場合に役立ちますが、2番目の例外を意図的に発生させている場合は望ましくありません。Python 3.3の場合、PEP 415はを使用してコンテキストを抑制する機能を追加しましたraise AttributeError("whatever") from None
Blckknght 2013年

3
@Blckknght:この場合、両方の例外を含むトレースバックを印刷すると問題ありません。言い換えれば、それが常に望ましくないというあなたの包括的陳述は真実ではないと思います。ここでの使用法では、a KeyErrorをに変えAttributeError、トレースバックで何が起こったかを示し、それが有用かつ適切であることを示しています。
martineau 2013年

より複雑な状況では正しいかもしれませんが、例外タイプ間で変換する場合、最初の例外の詳細は外部のユーザーにとって重要ではないことがよくあると思います。つまり__getattr__、例外が発生した場合、バグはおそらく属性アクセスのタイプミスであり、現在のクラスのコードの実装バグではありません。以前の例外をコンテキストとして表示すると、混乱する可能性があります。また、を使用してコンテキストを抑制した場合raise Whatever from Noneでも、必要に応じてを介して前の例外を取得できますex.__context__
Blckknght 2013年

1
私はあなたの答えを受け入れたかったのですが、質問では、入れ子になったtry / catchブロックを使用するのが良い方法であるかどうかについてもっと知りました。一方、これは最もエレガントなソリューションであり、コードで使用します。マーティンに感謝します。
ミハル2013年

ミハル:どういたしまして。また、を使用するよりも高速です__getattribute__()
martineau 2013年

4

Pythonでは、許可よりも許しを求める方が簡単です。ネストされた例外処理を気にする必要はありません。

(それに加えて、has*ほとんどの場合、カバーの下で例外を使用します。)


4

ドキュメントによると、タプルなどを使用して複数の例外を処理する方がよいでしょう:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise

2
この回答は実際には元の質問には対応していませんが、NameErrorやKeyboardInterruptなどのすべてをキャッチするため、最後に(通常は)恐ろしいアイデアを除いて、それを読んでいる人にとっては「裸」に注意してください。
負け

コードがprintステートメントの直後に同じ例外を再発行することを考えると、これは本当に大きな問題です。この場合、それを非表示にすることなく、例外に関するより多くのコンテキストを提供できます。再レイズしなかった場合は完全に同意しますが、意図していない例外を非表示にするリスクはないと思います。
NimbusScale

4

ネストされたtry / exceptの適切で単純な例は次のとおりです。

import numpy as np

def divide(x, y):
    try:
        out = x/y
    except:
        try:
            out = np.inf * x / abs(x)
        except:
            out = np.nan
    finally:
        return out

さまざまな組み合わせを試してみてください。正しい結果が得られます。

divide(15, 3)
# 5.0

divide(15, 0)
# inf

divide(-15, 0)
# -inf

divide(0, 0)
# nan

[もちろん、numpyがあるので、この関数を作成する必要はありません]


2

私が回避したいのは、古い例外を処理している間に新しい例外を発生させることです。エラーメッセージが読みにくくなります。

たとえば、私のコードでは、最初に書いた

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    raise KeyError(key)

そして、私はこのメッセージを受け取りました。

>>> During handling of above exception, another exception occurred.

私が欲しかったのはこれです:

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    pass
raise KeyError(key)

例外の処理方法には影響しません。コードのどちらのブロックでも、KeyErrorがキャッチされます。これは単にスタイルポイントを取得する問題です。


昇給の受け入れ答えの使用を参照してくださいfrom Noneにもために、かかわらず、より多くのスタイルのポイント。:)
ピアノサウルス'26 / 04/19

1

try-except-finallyがfinallyブロック内にネストされている場合、「子」からの結果は最終的に保持されます。私はまだ公式な調査を見つけていませんが、次のコードスニペットはPython 3.6でのこの動作を示しています。

def f2():
    try:
        a = 4
        raise SyntaxError
    except SyntaxError as se:
        print('log SE')
        raise se from None
    finally:
        try:
            raise ValueError
        except ValueError as ve:
            a = 5
            print('log VE')
            raise ve from None
        finally:
            return 6       
        return a

In [1]: f2()
log SE
log VE
Out[2]: 6

この動作は、@SławomirLenartが最後にブロックを除いてネストした場合の例とは異なります。
Guanghua Shu

0

私はそれがpythonicまたはエレガントであることの問題だとは思わない。できる限り例外を防ぐことが問題です。例外は、制御できないコードまたはイベントで発生する可能性のあるエラーを処理するためのものです。この場合、アイテムが属性であるかディクショナリであるかをチェックするときに完全に制御できるため、ネストされた例外を回避し、2回目の試行に固執します。


ドキュメントから:マルチスレッド環境では、LBYL(Look Before You Leap)アプローチは、「見ている」と「跳躍」の間に競合状態を導入する危険を冒す可能性があります。たとえば、コードがマッピング内のキーの場合:return mapping [key]は、別のスレッドがテスト後、ルックアップの前にマッピングからキーを削除すると失敗する可能性があります。この問題は、ロックを使用するか、EAFP(許可よりも許しを求める方が簡単)アプローチ
ヌーノ・アンドレ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.