辞書とデフォルト値


213

connectionDetailsPython辞書であると仮定して、このようなコードをリファクタリングするための最良で最もエレガントで最も「パイソン的な」方法は何ですか?

if "host" in connectionDetails:
    host = connectionDetails["host"]
else:
    host = someDefaultValue

回答:


311

このような:

host = connectionDetails.get('host', someDefaultValue)

40
2番目の引数は値ではなく、キーであることに注意してください。
Marcin

7
読みやすくするために+1しif/elseますが、はるかに高速です。それは役割を果たすかもしれないし、しないかもしれない。
Tim Pietzcker 2013

7
@ティム、なぜif/else速いのかについてのリファレンスを提供できますか?
nishantjr 2014年

2
@Tim:私は、高水準言語を使用する利点の1つは、インタープリターが関数内を「確認」して最適化できること、つまりユーザーがマイクロ最適化にそれほど対処する必要がないことを想定していました。 。それはJITコンパイルのようなもののためのものではありませんか?
nishantjr 2014年

3
@nishantjr:Python(少なくともCPython、最も一般的なバリアント)にはJITコンパイルがありません。PyPyは確かにこれをより速く解決するかもしれませんが、標準のPythonは常にこれまでのところ私の目的に十分な速さであったため、インストールされていません。一般に、現実の世界では問題になりそうにありません。タイムクリティカルな数値計算を行う必要がある場合、Pythonはおそらく選択する言語ではありません...
Tim Pietzcker

99

同様に使用することもできますdefaultdict

from collections import defaultdict
a = defaultdict(lambda: "default", key="some_value")
a["blabla"] => "default"
a["key"] => "some_value"

ラムダの代わりに通常の関数を渡すことができます:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"

7
私はOPの質問とは異なるいくつかの問題のためにここに来ました、そしてあなたの解決策はそれを正確に解決します。
0xc0de 2015

+1するつもりですが、残念ながらget、それと似たような方法には適合しません。
0xc0de 2015

この回答は、辞書にデフォルトのキーが含まれていることを確認するのに役立ちました。私の実装は、StackOverflowの回答で説明するには少し長すぎるので、ここで書きました。persagen.com/2020/03/05/...
ビクトリアスチュアート

24

一方で.get()素敵なイディオムで、それはより遅いのですif/else(そしてより遅いtry/except辞書内のキーの存在は、ほとんどの時間の期待できるのであれば):

>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.07691968797894333
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.4583777282275605
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(1, 10)")
0.17784020746671558
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(2, 10)")
0.17952161730158878
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.10071221458065338
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.06966537335119938

3
なぜ if/thenもっと速くなるのまだわかりません。どちらの場合も辞書検索が必要ですが、の呼び出しget()非常に遅い場合を除いて、スローダウンの原因は他にありますか?
Jens

1
@Jens:関数呼び出しは高価です。
Tim Pietzcker 2015年

1
人口の多い辞書では、どれが大したことではありませんか?実際の検索にコストがかかる場合、関数呼び出しの意味はそれほど重要ではありません。それはおそらくおもちゃの例でのみ重要です。
AturSams

2
@zehelvion:辞書の検索はO(1)辞書のサイズに関係なく、関数呼び出しのオーバーヘッドが関係します。
Tim Pietzcker、2015年

35
関数を呼び出すオーバーヘッドによってgetの使用を拒否するのは奇妙です。仲間のチームメンバーが最も読みやすいものを使用します。
Jochen Bedersdorfer 2016

19

複数の異なるデフォルトの場合、これを試してください:

connectionDetails = { "host": "www.example.com" }
defaults = { "host": "127.0.0.1", "port": 8080 }

completeDetails = {}
completeDetails.update(defaults)
completeDetails.update(connectionDetails)
completeDetails["host"]  # ==> "www.example.com"
completeDetails["port"]  # ==> 8080

3
これは良い慣用的な解決策ですが、落とし穴があります。Noneキーと値のペアの値の1つとしてconnectionDetailsが指定されている場合、またはemptyString が指定されている場合、予期しない結果が生じる可能性があります。defaults辞書には、潜在的に意図せずブランクにその値のいずれかを持つことができます。(stackoverflow.com/questions/6354436も参照)
dreftymac '29年

9

これを行うためのpython辞書のメソッドがあります: dict.setdefault

connectionDetails.setdefault('host',someDefaultValue)
host = connectionDetails['host']

ただし、このメソッドは、質問が尋ねたのとは異なり、キーがまだ定義されていない場合にconnectionDetails['host']to の値を設定します。someDefaultValuehost


1
setdefault()は値を返すので、これも機能することに注意してくださいhost = connectionDetails.setdefault('host', someDefaultValue)connectionDetails['host']キーが存在しない場合はデフォルト値に設定されることに注意してください。
ash108 2016年

7

(これは遅い答えです)

dict__missing__()方法は、次のように、クラスをサブクラス化してメソッドを実装することです。

class ConnectionDetails(dict):
    def __missing__(self, key):
        if key == 'host':
            return "localhost"
        raise KeyError(key)

例:

>>> connection_details = ConnectionDetails(port=80)

>>> connection_details['host']
'localhost'

>>> connection_details['port']
80

>>> connection_details['password']
Traceback (most recent call last):
  File "python", line 1, in <module>
  File "python", line 6, in __missing__
KeyError: 'password'

4

Python 3.3.5のPyPy(5.2.0-alpha0)の状況について@Tim Pietzckerの疑いをテストする.get()と、実際に両方とif/のelse方法が似ていることがわかります。実際、if / elseの場合、条件と割り当てに同じキーが含まれる場合、ルックアップは1つしかないようです(2つのルックアップがある最後のケースと比較してください)。

>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.011889292989508249
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.07310474599944428
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(1, 10)")
0.010391917996457778
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(2, 10)")
0.009348208011942916
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.011475925013655797
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.009605801998986863
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=d[1]")
0.017342638995614834

1

このためのlamba関数をワンライナーとして使用できます。connectionDetails2関数のようにアクセスされる新しいオブジェクト を作成します...

connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT"

今使う

connectionDetails2(k)

の代わりに

connectionDetails[k]

kキーにある場合はディクショナリ値を返し、それ以外の場合は返します"DEFAULT"


私はあなたに賛成しましたが、あなたの解決策の問題は、dictsは[]で動作しますが、ラムダはwith()で動作することです
yukashima huksay
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.