'setdefault' dictメソッドの使用例


192

の追加 collections.defaultdictPython 2.5で、dictsetdefaultメソッドの必要性が大幅に減少しました。この質問は私たちの集団教育に対するものです:

  1. setdefaultでもPython 2.6 / 2.7で何が役に立つのですか?
  2. setdefaultに取って代わられた人気のあるユースケースは何collections.defaultdictですか?

1
少しあまりにも関連stackoverflow.com/questions/7423428/...
ユーザー

回答:


208

あなたは言うことができるdefaultdict設定のデフォルト値のために有用である辞書を充填する前にし、setdefaultデフォルトを設定するために有用である間、または辞書を充填した後、

おそらく最も一般的な使用例:アイテムのグループ化(並べ替えられていないデータの場合は、を使用itertools.groupby

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
from collections import defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

辞書を作成した後、特定のキーが存在することを確認したい場合があります。defaultdict明示的なアクセスでのみキーを作成するため、この場合は機能しません。多くのヘッダーを持つHTTP風の何かを使用すると思います-一部はオプションですが、それらのデフォルトが必要です:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )

1
実際、このIMHOは、による置き換えの主な使用例ですdefaultdict。最初の段落であなたの意味の例を挙げていただけますか?
Eli Bendersky 2010

2
Muhammad Alkarouri:最初に行うのは、dictをコピーしてから、一部のアイテムを上書きします。私もそれをたくさんします、そしてそれは実際には最もイディオムが最も好む慣習だと思いsetdefaultます。defaultdict一方、A は、すべてdefaultvaluesが等しくない場合(つまり、一部が等しい場合0と一部が等しい場合)には機能しません[]
Jochen Ritzel、2010

2
@ YHC4k、はい。それが私が使用しheaders = dict(optional_headers)た理由です。デフォルト値がすべて等しくない場合。最終的な結果は、最初にHTTPヘッダーを取得してから、取得していないヘッダーのデフォルトを設定した場合と同じです。そして、すでに持っているなら、それはかなり使いやすいですoptional_headers。私の与えられた2ステップのコードを試して、それをあなたのコードと比較してください。
Muhammad Alkarouri、2010

19
またはただnew.setdefault(key, []).append(value)
fmalina 2015

2
最良の答えが要約されることdefaultdictは奇妙であることがわかります(つまり、setdefault今のユースケースはどこですか?)。また、IMO ChainMaphttp例をより適切に処理します。
YvesgereY 2016年

29

私は通常setdefault、この関数などのキーワード引数辞書に使用します。

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

キーワード引数を取る関数のラッパーで引数を微調整するのに最適です。


16

defaultdict 新しいリストのようにデフォルト値が静的である場合に最適ですが、動的な場合はそれほどではありません。

たとえば、文字列を一意のintにマップするための辞書が必要です。defaultdict(int)デフォルト値には常に0を使用します。同様に、defaultdict(intGen())常に1を生成します。

代わりに、通常の辞書を使用しました:

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

dict.get(key, nextID())後でこれらの値も参照できるようにする必要があるため、不十分であることに注意してください。

intGen 私が作成した小さなクラスで、自動的にintをインクリメントしてその値を返します。

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

誰かがこれを行う方法を持っているなら、defaultdict私はそれを見てみたいです。


:defaultdict(のサブクラス)でそれを行う方法のため、この質問を参照stackoverflow.com/questions/2912231/...
weronika

8
あなたは置き換えることができintGenitertools.count().next
アンチモン2012年

7
nextID()の値はmyDict.setdefault()、それが返す値がとして使用されていない場合でも、呼び出されるたびに増分されますstrID。これはどういうわけか無駄に思わsetdefault()れ、一般的に私が気に入らない点の1つを示しています。つまり、default実際に使用されるかどうかにかかわらず、常に引数を評価します。
martineau 2013年

あなたはそれを行うことができますdefaultdictmyDict = defaultdict(lambda: nextID())。その後、strID = myDict[myStr]ループで。
ムシフィル2015

3
defaultdictで記述した動作を取得するには、なぜmyDict = defaultdict(nextID)ですか?
forty_two 2017年


9

ほとんどの回答の状態として、setdefaultまたはdefaultdictキーが存在しない場合、デフォルト値を設定できます。ただし、の使用例に関して、いくつかの注意点を指摘したいと思いsetdefaultます。Pythonインタープリターが実行さsetdefaultれると、キーが辞書に存在する場合でも、関数の2番目の引数が常に評価されます。例えば:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

ご覧のとおりprint、辞書にすでに2つ存在するにもかかわらず、も実行されました。これはsetdefault、たとえばのような最適化に使用する場合に特に重要になりますmemoization。の2番目の引数として再帰関数呼び出しを追加するsetdefaultと、Pythonは常に再帰的に関数を呼び出すため、パフォーマンスが低下します。

メモ化について述べたので、メモ化で関数を拡張することを検討する場合は、functools.lru_cacheデコレーターを使用することをお勧めします。lru_cacheは、再帰関数のキャッシュ要件をより適切に処理します。


8

ムハンマドが言ったように、デフォルト値を設定したい場合があるだけです。この良い例は、最初にデータが入力され、次に照会されるデータ構造です。

トライを考えてみましょう。単語を追加するときに、サブノードが必要であるが存在しない場合は、トライを拡張するために作成する必要があります。単語の存在を照会するときに欠落しているサブノードは、その単語が存在せず、作成すべきでないことを示します。

defaultdictはこれを行うことができません。代わりに、getおよびsetdefaultメソッドを使用した通常の辞書を使用する必要があります。


5

理論的には、デフォルトを設定したいsetdefault場合と設定しない場合がある場合でも、これは便利です。実際には、そのようなユースケースに遭遇したことはありません。

ただし、興味深いユースケースが標準ライブラリ(Python 2.6、_threadinglocal.py)から登場します。

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

使用するの__dict__.setdefaultはかなり便利なケースだと思います。

編集:たまたま、これは標準ライブラリの唯一の例であり、コメントにあります。したがって、の存在を正当化するのに十分ではない場合がありsetdefaultます。それでも、ここに説明があります:

オブジェクトは属性に属性を格納し__dict__ます。偶然にも、__dict__属性はオブジェクトの作成後いつでも書き込み可能です。また、辞書ではありませんdefaultdict。一般的な場合のオブジェクトが__dict__として持つことは賢明ではありません。これはdefaultdict、各オブジェクトがすべての正当な識別子を属性として持つためです。そのため、Pythonオブジェクトが変更されることを予測できません__dict__.setdefault。それが役に立たないと見なされた場合は、完全に削除することは別として。


1
詳しく説明してください -_dict .setdefaultが特に便利な理由は何ですか?
Eli Bendersky 2010

1
@エリ:重要なの__dict__は、実装でdictはなくであるということdefaultdictです。
カトリエル2010

1
よし。私はsetdefaultPythonにとどまることを気にしませんが、今ではほとんど役に立たないことを知りたいと思っています。
Eli Bendersky、2010

@エリ:同意する。それがなかったとしても、それが今日紹介されるのに十分な理由があるとは思いません。しかし、すでにそこにあるので、すべてのコードがすでにそれを使用していることを考えると、それを削除することを主張するのは難しいでしょう。
Muhammad Alkarouri、2010

1
防御的プログラミングの下で​​ファイルします。setdefault存在するかもしれないし存在しないかもしれないキーを介して辞書に割り当てていることを明示し、それが存在しない場合はデフォルト値で作成したい:例えばd.setdefault(key,[]).append(value)。プログラムの他の場所で、alist=d[k]kが計算される場所で実行し、kがdにない場合に例外をスローする必要があります(defaultdictで必要になるassert k in dか、場合によってはif not ( k in d): raise KeyError
nigel222

3

欠点の1つdefaultdict以上は、dictdict.setdefault)ということであるdefaultdictオブジェクトが新しいアイテムの作成毎回の非既存のキーが与えられた(例えばとされ==print)。また、defaultdictクラスは一般的にクラスほど一般的ではdictなく、IMEをシリアル化するのがより困難です。

PS IMO関数|オブジェクトを変更することを意図していないメソッドは、オブジェクトを変更しないでください。


毎回新しいオブジェクトを作成する必要はありません。defaultdict(lambda l=[]: l)代わりに同じように簡単に行うことができます。
Artyer 2017年

6
@Artyerが提案することは絶対に行わないでください。
ブランドンハンパート2017年

2

以下に、その有用性を示すためのsetdefaultの例をいくつか示します。

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])

2

私は受け入れられた回答を書き直し、初心者のためにそれを容易にしました。

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

さらに、私は参照としてメソッドを分類しました:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}

1

これを取得し、辞書にデフォルト(!!!)を設定するとき、私は頻繁にsetdefaultを使用します。やや一般的にos.environ辞書:

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

簡潔に言うと、これは次のようになります。

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

結果の変数を使用することもできます。

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

しかし、それはdefaultdictsが存在する前よりも必要ではありません。


1

私が考えていない別のユースケースは上記で言及されました。場合によっては、プライマリインスタンスがキャッシュにあるIDによってオブジェクトのキャッシュディクショナリを保持し、見つからない場合はキャッシュを設定する必要があります。

return self.objects_by_id.setdefault(obj.id, obj)

これは、毎回objを取得する方法に関係なく、常に個別のIDごとに1つのインスタンスを保持する場合に便利です。たとえば、オブジェクト属性がメモリ内で更新され、ストレージへの保存が延期された場合。


1

私が偶然見つけた1つの非常に重要なユースケース: dict.setdefault()単一の正規オブジェクトのみが必要な場合(たまたま同じになる複数のオブジェクトではない)のマルチスレッドコードに最適です。

たとえば(Int)Flag、Python 3.6.0Enumにはバグ(Int)Flagがあります。1つの複合メンバーに対して複数のスレッドが競合している場合、複数になる可能性があります。

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

解決策はsetdefault()、計算された複合メンバーを保存する最後のステップとして使用することです。別のメンバーが既に保存されている場合は、新しいメンバーの代わりに使用され、一意のEnumメンバーが保証されます。


0

[編集] 非常に間違っている!setdefaultは常にlong_computationをトリガーし、Pythonは熱心です。

タトルの答えを拡張します。私にとって最良のユースケースはキャッシュメカニズムです。の代わりに:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

これは3行と2または3回のルックアップを消費します、私は喜んで書くでしょう

return memo.setdefault(x, long_computation(x))

良い例え。私はまだ3行の方がわかりやすいと思いますが、おそらく脳がsetdefaultを理解するように成長するでしょう。
Bob Stein

5
それらは同等ではありません。最初のlong_computation(x)場合のみ呼び出されますx not in memo。一方、2番目でlong_computation(x)は、常に呼び出されます。割り当てのみが条件付きであり、同等のコードsetdefaultv = long_computation(x)/ if x not in memo:/のようになりますmemo[x] = v
Dan D.


0

の別の使用例setdefault()、上書きしたくない場合です既に設定されているキーの値を。defaultdict上書きしますが、上書きしsetdefault()ません。ネストされた辞書の場合、現在のサブ辞書を削除したくないため、キーがまだ設定されていない場合にのみデフォルトを設定したい場合がよくあります。これはを使用する場合ですsetdefault()

の例defaultdict

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault 上書きしません:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.