Python 2.5にcollections.defaultdict
を追加すると、dict
のsetdefault
メソッドの必要性が大幅に減少しました。この質問は集団教育のためのものです。
setdefault
は何のためにまだ有用ですか?setdefault
のどの一般的な使用例がcollections.defaultdict
に置き換えられましたか?defaultdict
はデフォルトの設定に役立ちますdictを埋める前およびsetdefault
はデフォルトの設定に役立ちますdictを埋めている間または埋めた後。
おそらく最も一般的な使用例:アイテムのグループ化(並べ替えられていないデータの場合、そうでない場合は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
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 )
この関数のように、キーワード引数の辞書に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)
キーワード引数を取る関数のラッパーで引数を微調整するのに最適です。
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
でこれを行う方法を持っているなら、私はそれを見たいです。
ムハンマドが言ったように、デフォルト値を設定したいときもある状況があります。この優れた例は、最初にデータが取り込まれ、次にクエリされるデータ構造です。
トライを検討してください。 Wordを追加するときに、サブノードが必要であるが存在しない場合は、トライを拡張するためにサブノードを作成する必要があります。 Wordの存在を照会する場合、サブノードが見つからないということは、Wordが存在せず、作成すべきでないことを示します。
Defaultdictはこれを行うことができません。代わりに、getおよびsetdefaultメソッドを備えた通常の辞書を使用する必要があります。
理論的には、sometimesデフォルトを設定したい場合とそうでない場合は、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
を取り除いて、それが役に立たないと見なされた場合は完全に削除することを除いて、変更を予測することはできません。
defaultdict
over dict
(dict.setdefault
)の欠点の1つは、defaultdict
オブジェクトが新しい項目を作成することですEVERYTIME non existing key is(例:==
、print
)。また、defaultdict
クラスは一般にdict
クラスよりも一般的ではないため、IMEをシリアル化することはより困難です。
追伸オブジェクトを変更することを意図していないIMO関数|メソッドは、オブジェクトを変更しないでください。
ほとんどの回答はsetdefault
またはdefaultdict
状態であるため、キーが存在しない場合にデフォルト値を設定できます。ただし、setdefault
の使用例に関する小さな注意点を指摘したいと思います。 Pythonインタープリターがsetdefault
itを実行すると、辞書にキーが存在する場合でも、関数の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
ご覧のとおり、辞書に2がすでに存在していても、print
も実行されました。これは、setdefault
のような最適化などにmemoization
を使用する場合に特に重要になります。 setdefault
の2番目の引数として再帰的な関数呼び出しを追加する場合、Pythonは常に関数を再帰的に呼び出すため、パフォーマンスが向上することはありません。
次に、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'])
私が考えていない別のユースケースは、上記で言及されました。場合によっては、プライマリインスタンスがキャッシュ内にあるIDでオブジェクトのキャッシュ辞書を保持し、見つからない場合はキャッシュを設定したいことがあります。
return self.objects_by_id.setdefault(obj.id, obj)
これは、毎回objを取得する方法に関係なく、常に異なるIDごとに単一のインスタンスを保持する場合に役立ちます。たとえば、オブジェクト属性がメモリ内で更新され、ストレージへの保存が延期される場合。
これを取得し、辞書にデフォルト(!!!)を設定する場合、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が存在する前よりも必要ではありません。
私が偶然見つけた非常に重要な使用例:dict.setdefault()
は、(同じオブジェクトが複数あるのではなく)単一の標準オブジェクトだけが必要な場合のマルチスレッドコードに最適です。
たとえば、 (Int)Flag
Enum in Python 3.6.0にはバグがあります :複合(Int)Flag
メンバーを求めて複数のスレッドが競合している場合、終了する可能性があります複数の場合:
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メンバーが保証されます。
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}
[編集] 非常に間違っています! 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))
私は受け入れられた答えを書き直し、初心者のためにそれを容易にしました。
#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'],}
ここにある答えが好きです:
http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html
要するに、ダウンストリームの空のキーの検索をどのように処理するかに基づいて(パフォーマンスが重要でないアプリで)決定する必要があります(viz。KeyError
対デフォルト値)。