102

イディオムと読みやすさについて質問がありますが、この特定のケースではPythonの哲学が衝突しているようです。

辞書Bから辞書Aを作成したい。Bに特定のキーが存在しない場合は、何もせずに続行します。

どちらが良いですか?

try:
    A["blah"] = B["blah"]
except KeyError:
    pass

また

if "blah" in B:
    A["blah"] = B["blah"]

「許しをし、求めなさい」対「単純さと明白さ」。

どちらが良いのか、そしてその理由は?

4

11 に答える 11

80

例外は条件付きではありません。

条件付きバージョンの方が明確です。これは当然のことです。これは単純なフロー制御であり、例外ではなく、条件が設計されているものです。

例外バージョンは、ループ内でこれらのルックアップを実行する際の最適化として主に使用されます。一部のアルゴリズムでは、内部ループからテストを排除できます。ここではそのメリットはありません。2回言わなくても済むという小さな利点がありますが、これらをたくさん行う場合は、とにかく"blah"ヘルパー関数を使用する必要があります。move_key

一般に、特別な理由がない限り、デフォルトで条件付きバージョンを使用することを強くお勧めします。条件付きはこれを行うための明白な方法です。これは通常、あるソリューションを別のソリューションよりも優先することを強くお勧めします。

于 2010-12-22T19:38:43.787 に答える
70

例外とダブルルックアップの両方を回避する3番目の方法もあります。これは、ルックアップにコストがかかる場合に重要になる可能性があります。

value = B.get("blah", None)
if value is not None: 
    A["blah"] = value

辞書に値が含まれていると予想される場合は、Noneなどのより難解な定数を使用するか、新しい定数を作成できます。NotImplementedEllipsis

MyConst = object()
def update_key(A, B, key):
    value = B.get(key, MyConst)
    if value is not MyConst: 
        A[key] = value

とにかく、使用することupdate()は私にとって最も読みやすいオプションです:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)
于 2010-12-22T20:04:40.503 に答える
14

私が理解していることから、dictAをdictBのキーと値のペアで更新したいとします。

updateより良い選択です。

A.update(B)

例:

>>> A = {'a':1, 'b': 2, 'c':3}
>>> B = {'d': 2, 'b':5, 'c': 4}
>>> A.update(B)
>>> A
{'a': 1, 'c': 4, 'b': 5, 'd': 2}
>>> 
于 2010-12-22T18:51:10.133 に答える
10

Pythonパフォーマンスウィキからの直接引用:

初めての場合を除いて、単語が表示されるたびに、ifステートメントのテストが失敗します。多数の単語を数えている場合、その多くはおそらく複数回発生します。値の初期化が1回だけ発生し、その値の拡張が何度も発生する状況では、tryステートメントを使用する方が安価です。

したがって、状況に応じて両方のオプションが実行可能であるように見えます。詳細については、次のリンクを確認してください:Try-except-performance

于 2012-04-28T14:23:41.037 に答える
3

ここでの一般的なルールは通常存在すると思いますA["blah"]が、そうであれば試してみてください-そうでない場合は例外として使用してくださいif "blah" in b:

「試してみる」は時間的には安いと思いますが、「例外」はもっと高いです。

于 2010-12-22T18:52:35.297 に答える
3

2番目の例は、このコードが意味をなさない限り、何をすべきかということだと思います。

try:
    A["foo"] = B["foo"]
    A["bar"] = B["bar"]
    A["baz"] = B["baz"]
except KeyError:
    pass

にないキーがあるとすぐにコードが中止されることに注意してくださいB。このコードに意味がある場合は、例外メソッドを使用する必要があります。それ以外の場合は、テストメソッドを使用します。私の意見では、それは短く、意図を明確に表現しているので、例外メソッドよりもはるかに読みやすいです。

もちろん、使用するように言っている人updateは正しいです。辞書内包表記をサポートするバージョンのPythonを使用している場合は、次のコードを強くお勧めします。

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})
于 2010-12-22T19:23:17.180 に答える
2

他の言語の規則は、例外的な条件、つまり通常の使用では発生しないエラーの例外を予約することです。StopIterationはそのルールによって存在するべきではないため、そのルールがPythonにどのように適用されるかわかりません。

于 2010-12-22T18:56:13.177 に答える
2

を開始し、代入式(PEP 572)(演算子)をPython 3.8導入すると、条件値を変数にキャプチャして、そうでないかどうかをチェックし(関連付けられた値またはのいずれかを返すように)、の本体内で使用できます。条件::=dictB.get('hello', None)valueNonedict.get('hello', None)None

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}
于 2019-04-27T15:14:27.993 に答える
1

個人的に、私は2番目の方法に傾いています(しかし使用していhas_keyます):

if B.has_key("blah"):
  A["blah"] = B["blah"]

このように、各割り当て操作は2行だけで(try / exceptionの4行ではなく)、スローされる例外は、実際のエラーまたは見逃したものになります(そこにないキーにアクセスしようとするだけではありません)。 。

結局のところ(あなたの質問へのコメントを参照してください)、has_key非推奨になっています-したがって、次のように記述した方がよいと思います

if "blah" in B:
  A["blah"] = B["blah"]
于 2010-12-22T18:52:34.823 に答える
1

「飛躍する前に見る」という原則を強調する受け入れられた回答はほとんどの言語に当てはまるかもしれませんが、Pythonの原則に基づいて、より多くのpythonicが最初のアプローチである可能性があります。言うまでもなく、これはPythonの正当なコーディングスタイルです。重要なことは、正しいコンテキストでtryexceptブロックを使用し、ベストプラクティスに従っていることを確認することです。例えば。tryブロックで多くのことを実行したり、非常に広範な例外をキャッチしたり、さらに悪いことに、裸のexcept句などを実行したりします。

許可よりも許しを求める方が簡単です。(EAFP)

こちらのPythonドキュメントリファレンスを参照してください

また、コア開発者の1人であるBrettのこのブログでは、このほとんどに簡単に触れています。

ここで別のSOディスカッションを参照してください:

于 2019-09-26T04:37:52.547 に答える
1

読みやすさについて説明するだけでなく、一部のシナリオではパフォーマンスも重要だと思います。クイックタイムイットベンチマークは、テスト(つまり「許可を求める」)が実際には例外を処理する(つまり「許しを求める」)よりもわずかに速いことを示しています。

ベンチマークを設定するコードは次のとおりです。ランダムなキーと値のペアの大きな辞書を生成します。

setup = """
import random, string
d = {"".join(random.choices(string.ascii_letters, k=3)): "".join(random.choices(string.ascii_letters, k=3)) for _ in range(10000)}
"""

次に、ifテスト:

stmt1 = """
key = "".join(random.choices(string.ascii_letters, k=3))
if key in d:
    _ = d[key]
"""

私たちに与える:

>>> timeit.timeit(stmt=stmt1, setup=setup, number=1000000)
1.6444563979999884

一方、例外を利用したアプローチ

stmt2 = """
key = "".join(random.choices(string.ascii_letters, k=3))
try:
    _ = d[key]
except KeyError:
    pass
"""

私たちに与える:

>>> timeit.timeit(stmt=stmt2, setup=setup, number=1000000)
1.8868465850000575

興味深いことに、実際のベンチマークからセットアップに世代を引き上げ、それによって同じkeyキーを何度も探して、非常に異なる数を提供します。

>>> timeit.timeit(stmt=stmt1, setup=setup, number=100000000)
2.3290171539999847
>>> timeit.timeit(stmt=stmt2, setup=setup, number=100000000)
26.412447488999987

これがテストと例外処理の利点を強調するのか、それとも辞書が前回のルックアップの結果をバッファリングしてベンチマーク結果をテストに偏らせるのかを推測したくありません…

于 2021-01-05T10:12:08.430 に答える