`dict in dict`対` try / except`-どちらがより読みやすいイディオムですか?


93

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

ディクショナリBからディクショナリAを作成したい。Bに特定のキーが存在しない場合は、何もせずに続行する。

どっちがいい?

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

または

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

「許しをしなさい」対「単純さと明快さ」。

どちらがより良いのですか、そしてなぜですか?


1
2番目の例はif "blah" in B.keys()、またはと書く方がよいでしょうif B.has_key("blah")
Girasquid 2010

2
うまくA.update(B)いきませんか?
SilentGhost 2010

21
@Luke:O(1)オペレーションからO(n)オペレーションへの変更をhas_key優先しinてチェックB.keys()することで非推奨になりました。
キンドール

4
@ルーク:そうではありません。.has_key非推奨でありkeys、py2kで不要なリストを作成し、py3kで冗長です
SilentGhost

2
'ビルド' Aのように、最初はAが空ですか?そして、特定のキーだけが必要ですか?内包表記を使用しますA = dict((k, v) for (k, v) in B if we_want_to_include(k))
Karl Knechtel、2010

回答:


74

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

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

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

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


3
同意しません。「Xを実行し、それが機能しない場合はYを実行する」と言います。ここでの条件付きソリューションに対する主な理由は、"blah"より頻繁に書き込む必要があるため、エラーが発生しやすい状況につながることです。
glglgl 2013

6
そして、特にPythonでは、EAFPが非常に広く使用されています。
glglgl 2013

8
この答えは、Pythonを除いて、私が知っているどの言語にも当てはまります。
トマーシュ・ザト-モニカを復活

3
例外をPythonの条件付きであるかのように使用している場合、他の誰もそれを読む必要がないことを願っています。
Glenn Maynard

それで、最終的な評決は何ですか?:)
floatingpurr

59

例外と二重ルックアップの両方を回避する3番目の方法もあります。これは、ルックアップに負荷がかかる場合に重要です。

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

辞書に含まれていると思われる場合 NoneNotImplementedEllipsisなどの難解な定数を使用するか、新しい定数を作成します。

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)

14

私が理解していることから、dict Aをdict Bのキーと値のペアで更新したい

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

「特定のキーがBに存在しない場合」申し訳ありませんが、もっと明確にする必要がありますが、値をコピーしたいのは、Bに特定のキーが存在する場合のみです。Bのすべてではない
LeeMobile

1
@LeeMobile -A.update({k: v for k, v in B.iteritems() if k in specificset})
Omnifarious

8

Python Performance Wikiからの直接の引用:

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

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


それは興味深い読み物ですが、私は多少不完全だと思います。使用されているdictは要素が1つしかなく、dictsが大きいとパフォーマンスに大きな影響があると思います
user2682863

3

私はここでの一般的なルールはA["blah"]通常存在するだろうと思うので、もしそうならtry-exceptが良いですif "blah" in b:

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


10
デフォルトでは、最適化の観点からコードにアプローチしないでください。読みやすさと保守性の観点からアプローチします。目標が特に最適化でない限り、これは間違った基準です(最適化の場合、答えは推測ではなくベンチマークです)。
Glenn Maynard

私はおそらく最後の点を括弧で括るか、なんとなく曖昧にすべきでした-私の主な点は最初の点でした、そして私それが2番目の追加の利点を持っていると思います
ニール

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

「Bにないキーがあるとすぐにコードが中止されることに注意してください。」-これが、try:ブロックに絶対最小値のみを入れることがベストプラクティスである理由です。通常、これは1行です。最初の例のような、ループの一部として良いだろうfor key in ["foo", "bar", "baz"]: try: A[key] = B[key]
Zimを

2

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


この栗は、例外処理にコストがかかり、パフォーマンスに大きな影響を与える可能性がある言語に由来していると思います。その背後にある真の正当化や推論を見たことがありません。
John La Rooy、

@JohnLaRooyいいえ、パフォーマンスは本当に理由ではありません。例外は一種の非ローカルgotoであり、一部の人々はコードの可読性を妨げると考えています。ただし、この方法での例外の使用は、Pythonでは慣用的と見なされるため、上記は当てはまりません。
Ian Goldby 2017年

条件付きリターンも「非ローカルgoto」であり、多くの人々はコードブロックの最後にある歩哨を検査するのではなく、そのスタイルを好みます。
カウベルト

1

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

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

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

結局のところ(あなたの質問のコメントを参照)、has_key非推奨です-だから私はそれは

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

1

開始Python 3.8、との導入の代入式(PEP 572) :=オペレータ)、我々は条件値キャプチャすることができますdictB.get('hello', None)変数ではvalueそうでない場合の両方をチェックするためにNone(としてdict.get('hello', None)戻って、関連する値またはいずれかNone)と、その後の身体の中にそれを使用します状態:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}

値== 0の場合、これは失敗します
Eric

0

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

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

こちらのpython docsリファレンスを参照してください

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.