web-dev-qa-db-ja.com

python dict:get vs setdefault

次の2つの式は私と同等のようです。どちらが好ましいですか?

data = [('a', 1), ('b', 1), ('b', 2)]

d1 = {}
d2 = {}

for key, val in data:
    # variant 1)
    d1[key] = d1.get(key, []) + [val]
    # variant 2)
    d2.setdefault(key, []).append(val)

結果は同じですが、どちらのバージョンがより優れているか、またはよりPython的ですか?

個人的には、バージョン2を理解するのは難しいと感じています。私にとっては、setdefaultの把握は非常に難しいです。正しく理解できれば、辞書で「キー」の値を検索し、利用できない場合は「[]」を辞書に入力し、値または「[]」への参照を返し、それに「val」を追加します参照。確かにスムーズですが、少なくとも直観的ではありません(少なくとも私にとって)。

私の考えでは、バージョン1の方がわかりやすい(利用可能な場合は「キー」の値を取得し、そうでない場合は「[]」を取得し、[val]で構成されるリストに参加して結果を「key」 )。しかし、より直感的に理解できる一方で、このバージョンはパフォーマンスが低下し、このリストがすべて作成されるのではないかと心配しています。もう1つの欠点は、式で「d1」が2回発生することで、かなりエラーが発生しやすいことです。おそらくgetを使用したより良い実装がありますが、現在は私を避けています。

私の推測では、バージョン2は、経験の浅い人にとっては把握がより困難ですが、より高速であり、したがって望ましいと考えられます。ご意見?

44
Cerno

2つの例は同じことを行いますが、それはgetsetdefaultが同じことを意味するわけではありません。

2つの違いは、基本的に、リストを指すようにd[key]を毎回手動で設定するのに対して、setdefaultがリストに設定されていないときにのみ自動的にd[key]を設定することです。

2つの方法を可能な限り類似させて、私は走りました

from timeit import timeit

print timeit("c = d.get(0, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("c = d.get(1, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(0, []).extend([1])", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(1, []).extend([1])", "d = {1: []}", number = 1000000)

そして得た

0.794723378711
0.811882272256
0.724429205999
0.722129751973

このため、setdefaultgetよりも約10%高速です。

getメソッドを使用すると、setdefaultを使用した場合よりもlessを実行できます。キーを設定しない場合でも、キーが存在しない場合(頻繁に発生する場合)、KeyErrorを取得しないようにするために使用できます。

2つのメソッドの詳細については、「 default.dict)メソッドのユースケース および dict.get()メソッドがポインターを返す 」を参照してください。

setdefaultに関するスレッドは、ほとんどの場合、defaultdictを使用したいと結論付けます。 getについてのスレッドは、それが遅いと結論づけます。そして、多くの場合、(辞書のサイズと用途に応じて)defaultdictを使用するか、エラーを処理する二重ルックアップを行うほうが(速度的に)良いでしょう場合)。

25
agf

Agfから受け入れられた答えは、likeと比較していません。後:

_print timeit("d[0] = d.get(0, []) + [1]", "d = {1: []}", number = 10000)
_

_d[0]_には10,000項目のリストが含まれますが、その後は次のようになります。

_print timeit("d.setdefault(0, []) + [1]", "d = {1: []}", number = 10000)
_

_d[0]_は、単に_[]_です。つまり、_d.setdefault_バージョンはdに保存されているリストを変更しません。実際のコードは次のとおりです。

_print timeit("d.setdefault(0, []).append(1)", "d = {1: []}", number = 10000)
_

そして実際には、障害のあるsetdefaultの例よりも高速です。

ここでの違いは、連結を使用して追加すると、リスト全体が毎回コピーされるためです(そして、測定可能になり始める要素が10,000になると、appendを使用してリストの更新が償却されますO(1)実質的に一定の時間。

最後に、元の質問では考慮されていない他の2つのオプションがあります:defaultdictまたは単に辞書をテストして、既にキーが含まれているかどうかを確認します。

したがって、d3, d4 = defaultdict(list), {}

_# variant 1 (0.39)
d1[key] = d1.get(key, []) + [val]
# variant 2 (0.003)
d2.setdefault(key, []).append(val)
# variant 3 (0.0017)
d3[key].append(val)
# variant 4 (0.002)
if key in d4:
    d4[key].append(val)
else:
    d4[key] = [val]
_

バリアント1はリストを毎回コピーするため、バリアント2は2番目に遅く、バリアント3は最速ですが、Python 2.5より古い場合は動作しません。バリアント4は、バリアント3よりもわずかに遅いだけです。

可能であれば、バリアント3を使用します。バリアント4は、defaultdictが完全に適合しない場合があります。元のバリアントの両方を避けてください。

14
Duncan

defaultdictモジュールのcollectionsを確認することをお勧めします。以下はあなたの例と同等です。

from collections import defaultdict

data = [('a', 1), ('b', 1), ('b', 2)]

d = defaultdict(list)

for k, v in data:
    d[k].append(v)

こちら がさらにあります。

10
grifaton

1.ここで良い例を説明:
http://code.activestate.com/recipes/66516-add-an-entry-to-a-dictionary-unless-the-entry-is-a/

dict。setdefault典型的な使用法
somedict.setdefault(somekey,[]).append(somevalue)

dict。get典型的な使用法
theIndex[Word] = 1 + theIndex.get(Word,0)


2。詳細な説明: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html

dict.setdefault()getまたは_set & get_と同等です。または_set if necessary then get_。辞書キーの計算や入力に時間がかかる場合、特に効率的です。

Dict.setdefault()の唯一の問題は、必要かどうかに関係なく、デフォルト値が常に評価されることです。デフォルト値がcomputeの場合は、mattersのみです。その場合は、defaultdictを使用します。


3。最後に、違いが強調された公式ドキュメント http://docs.python.org/2/library/stdtypes.html

get(key[, default])
キーがディクショナリにある場合はキーの値を返し、そうでない場合はデフォルトを返します。デフォルトが指定されない場合、デフォルトはNoneになり、このメソッドはKeyErrorを発生させません。

setdefault(key[, default])
キーが辞書にある場合、その値を返します。そうでない場合、デフォルト値のキーを挿入し、デフォルトを返します。デフォルトはNoneです。

3
user

これら2つの用語の理解にまだ苦労している人のために、get()とsetdefault()メソッドの基本的な違いを教えてください-

シナリオ-1

_root = {}
root.setdefault('A', [])
print(root)
_

シナリオ-2

_root = {}
root.get('A', [])
print(root)
_

シナリオ1の出力は_{'A': []}_になり、シナリオ2の出力は_{}_になります

したがって、setdefault()は辞書に不在キーを設定しますが、get()はデフォルト値のみを提供しますが、辞書は変更しません。

ここで、これが便利になる場所に行きましょう。値がリストである辞書内の要素を検索していて、見つかった場合はリストを変更します。

setdefault()を使用

_def fn1(dic, key, lst):
    dic.setdefault(key, []).extend(lst)
_

get()を使用

_def fn2(dic, key, lst):
    dic[key] = dic.get(key, []) + (lst) #Explicit assigning happening here
_

タイミングを調べましょう-

_dic = {}
%%timeit -n 10000 -r 4
fn1(dic, 'A', [1,2,3])
_

288 nsかかった

_dic = {}
%%timeit -n 10000 -r 4
fn2(dic, 'A', [1,2,3])
_

128秒かかった

そのため、これら2つのアプローチには非常に大きなタイミングの違いがあります。

1
pyAddict
In [1]: person_dict = {}

In [2]: person_dict['liqi'] = 'LiQi'

In [3]: person_dict.setdefault('liqi', 'Liqi')
Out[3]: 'LiQi'

In [4]: person_dict.setdefault('Kim', 'kim')
Out[4]: 'kim'

In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}

In [8]: person_dict.get('Dim', '')
Out[8]: ''

In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}
1
youtoce

dict.getのロジックは次のとおりです。

if key in a_dict:
    value = a_dict[key] 
else: 
    value = default_value

例を挙げましょう:

In [72]: a_dict = {'mapping':['dict', 'OrderedDict'], 'array':['list', 'Tuple']}
In [73]: a_dict.get('string', ['str', 'bytes'])
Out[73]: ['str', 'bytes']
In [74]: a_dict.get('array', ['str', 'byets'])
Out[74]: ['list', 'Tuple']

setdefaultのメカニズムは次のとおりです。

    levels = ['master', 'manager', 'salesman', 'accountant', 'assistant']
    #group them by the leading letter
    group_by_leading_letter = {}
    # the logic expressed by obvious if condition
    for level in levels:
        leading_letter = level[0]
        if leading_letter not in group_by_leading_letter:
            group_by_leading_letter[leading_letter] = [level]
        else:
            group_by_leading_letter[leading_letter].append(Word)
    In [80]: group_by_leading_letter
    Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}

Setdefault dictメソッドは、まさにこの目的のためのものです。上記のforループは次のように書き換えることができます。

In [87]: for level in levels:
    ...:     leading = level[0]
    ...:     group_by_leading_letter.setdefault(leading,[]).append(level)
Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}

これは非常に簡単です。つまり、null以外のリストが要素を追加するか、nullリストが要素を追加します。

defaultdict。これにより、さらに簡単になります。作成するには、dictの各スロットのデフォルト値を生成するための型または関数を渡します:

from collections import defualtdict
group_by_leading_letter = defaultdict(list)
for level in levels:
    group_by_leading_letter[level[0]].append(level)
0
Algebra