Pythonでtry vs ifを使用する


145

変数に値があるかどうかをテストするときに、tryどちらifを使用するかを決定する根拠はありますか?

たとえば、リストを返すか、値を返さない関数があります。処理する前に結果を確認したい。次のうちどれがより好ましいでしょうか、それはなぜですか?

result = function();
if (result):
    for r in result:
        #process items

または

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

関連ディスカッション:

Pythonでのメンバーの存在の確認


#process itemsスタンザがTypeErrorをスローする可能性がある場合は、別のtry:except:ブロックでラップする必要があることに注意してください。:この特定の例では、私はでわずかであれば使用したい
リコー

回答:


237

PythonはLBYLスタイル(「ジャンプする前に見る」)よりもEAFPスタイル(「許可よりも許しを求める方が簡単」)を奨励しているとよく耳にします。私にとって、それは効率と読みやすさの問題です。

あなたの例では(リストまたは空の文字列を返す代わりに、関数がリストまたはを返すことになったとしますNone)、99%の時間resultに実際に反復可能な何かが含まれることが予想される場合は、このtry/exceptアプローチを使用します。例外が本当に例外的である場合、それはより速くなります。が時間の50%を超える場合resultNone、使用することifをお勧めします。

いくつかの測定でこれをサポートするには:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

したがって、ifステートメントは常にコストがかかりますが、try/exceptブロックを設定することはほぼ無料です。しかし、Exception実際に発生すると、コストははるかに高くなります。

道徳の:

  • try/exceptフロー制御に使用することは完全に(そして「pythonic」)、
  • しかし、Exceptionsが実際に例外的である場合、それは最も理にかなっています。

Pythonのドキュメントから:

EAFP

許可より許しを求める方が簡単です。この一般的なPythonコーディングスタイルは、有効なキーまたは属性の存在を想定しており、想定が誤っていることが判明した場合は例外をキャッチします。このクリーンで速いスタイルは、多数のtryand exceptステートメントの存在によって特徴付けられ ます。この技法は、 Cなどの他の多くの言語に共通のLBYLスタイルとは対照的です。


1
ありがとうございました。この場合、理論的根拠が結果の期待になり得ることがわかります。
artdanil 2009

6
....そして、それが、PythonでJITを実際に最適化することが非常に難しい理由の1つです。最近のベータ版LuaJIT 2が証明するように、動的言語本当に非常に高速です。しかし、それは初期の言語設計とそれが奨励するスタイルに大きく依存します。(関連するメモでは、言語設計は、最高のJavaScirpt JITでさえLuaJIT 1と比較できない理由です。2ははるかに少ないです)
Javier

1
@ 2rs2ts:私は自分で同じようなタイミングをとっただけです。Python 3では、キーが辞書にある場合try/exceptよりも25%高速if key in d:でした。予想通り、キーが辞書にないときはずっと遅く、この答えと一致していました。
Tim Pietzcker 2013

5
ただし、これは古い答えです1/1。timeitのようなステートメントを使用することは最適化されないため、最適な選択肢ではありません(dis.dis('1/1')除算は行われないことに注意してください)。
Andrea Corbellini、2015

1
また、実行する操作にも依存します。単一の除算は高速で、エラーの検出は多少コストがかかります。ただし、例外処理のコストが許容範囲内である場合でも、コンピュータに要求することを慎重に検討してください。結果に関係なく操作自体が非常に高価であり、成功が可能かどうかを判断する安価な方法がある場合は、それを使用してください。例:十分なスペースがないことを理解するためだけにファイルの半分をコピーしないでください。
Bachsau

14

関数は混合型(リストまたは空の文字列)を返すべきではありません。値のリストまたは空のリストのみを返す必要があります。その後、何もテストする必要はありません。つまり、コードは次のように折りたたまれます。

for r in function():
    # process items

2
その点で私はあなたに完全に同意します。しかし、機能は私のものではなく、私はそれを使用しています。
artdanil 2009

2
@artdanil:そのため、Brandon Corfmanが考えている機能と同じように機能するものを、その機能でラップすることができます。
quamrana 2009

24
どちらが疑問を投げかけます:ラッパーはifまたはtryを使用して反復不可能なケースを処理する必要がありますか?
jcdyer 09

@quamranaこれはまさに私がやろうとしていることです。@jcdが指摘したように、問題はラッパー関数の状況をどのように処理すべきかについてです。
artdanil 2009

12

私が提供するコードが一見明白ではなく、コードサンプルの後に説明を読む必要がある場合は、私のソリューションを無視してください。

「戻り値なし」は戻り値がNoneであることを意味すると思いますか?はいの場合、または「値なし」がブール値でFalseの場合、コードは基本的に「値なし」を「反復しない」として扱うため、次のことを実行できます。

for r in function() or ():
    # process items

function()Trueでないものを返す場合は、空のタプルを反復処理します。つまり、反復を実行しません。これは本質的にLBYLです。


4

2番目の例は壊れています。文字列とリストの両方を反復処理できるため、コードがTypeError例外をスローすることはありません。空の文字列またはリストを繰り返し処理することも有効です。ループの本体を0回実行します。


4

次のうちどれがより好ましいでしょうか、それはなぜですか?

この場合は、Leap Before You Leapが望ましいです。例外アプローチを使用すると、TypeErrorがループ本体の任意の場所で発生する可能性があり、キャッチされて破棄されるため、デバッグが難しくなります。

(しかし、私はBrandon Corfmanに同意します。空のリストの代わりに「アイテムなし」に対してNoneを返すのは失敗します。これは、Pythonでは見られないはずのJavaプログラマーの習慣です。Javaでは。)


4

一般に、私が得た印象は、例外は例外的な状況のために予約する必要があるということです。resultが空になることが予想されない場合(ただし、ディスクがクラッシュした場合など)は、2番目のアプローチが有効です。一方、空resultが通常の条件下で完全に妥当である場合は、ifステートメントを使用してそれをテストする方が理にかなっています。

(より一般的な)シナリオを念頭に置いていました:

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

同等のものの代わりに:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

これは、Tim Pietzckerが言及するアプローチの違いの例です。最初はLBYL、2番目はEAFPです
Managu

++Pythonでは機能しない簡単なメモです+= 1。代わりに使用してください。
tgray 2009

3

bobinceは、2番目のケースをラップすることでループ内のTypeErrorsもキャッチできることを賢明に指摘していますが、これは望ましいことではありません。実際に試してみたい場合は、ループの前に反復可能かどうかをテストできます

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

ご覧のとおり、かなり醜いです。お勧めはしませんが、完全を期すために言及する必要があります。


私の目には、意図的に型エラーが発生することは常に悪いコーディングスタイルです。開発者は何を期待するかを理解し、例外なくその値を処理する準備をする必要があります。操作は、(プログラマの観点から)それが行うことになっていたものでしたし、期待される結果がリストされているかたびNone、いつもと結果を確認しますis Noneis not None。反対に、正当な結果に対して例外を発生させることも悪いスタイルです。例外は予想外のものです。例:str.find()検索自体がエラーなしで完了したため、何も見つからない場合は-1を返します。
Bachsau

1

パフォーマンスに関する限り、通常は例外を発生させないコードにtryブロックを使用する方が、毎回ifステートメントを使用するよりも高速です。したがって、決定は例外的なケースの確率に依存します。


-5

一般的な経験則として、フローを制御するために、try / catchなどの例外処理機能を使用しないでください。舞台裏での反復はStopIteration例外の発生によって制御されますが、2番目よりも最初のコードスニペットを優先する必要があります。


7
これはJavaにも当てはまります。PythonはEAFPを非常に強く受け入れています。
fengb 2009

3
それはまだ奇妙な習慣です。ほとんどのプログラマーの頭脳は、私の経験ではEAFPをスキップするように配線されています。彼らはなぜ特定の道が選ばれたのかと思ってしまいます。そうは言っても、すべてのルールを破る時​​間と場所があります。
ilowe 2009
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.