web-dev-qa-db-ja.com

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

Python 2.5にcollections.defaultdictを追加すると、dictsetdefaultメソッドの必要性が大幅に減少しました。この質問は集団教育のためのものです。

  1. 今日のPython 2.6/2.7では、setdefaultは何のためにまだ有用ですか?
  2. setdefaultのどの一般的な使用例がcollections.defaultdictに置き換えられましたか?
177
Eli Bendersky

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 )
188
Jochen Ritzel

この関数のように、キーワード引数の辞書に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)

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

28
Matt Joiner

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でこれを行う方法を持っているなら、私はそれを見たいです。

15
David Kanarek

OrderedDictにデフォルト値が必要な場合は、setdefault()を使用します。両方を行う標準のPythonコレクションはありませんが、そのようなコレクションを実装するために areways があります。

9
AndyGeek

ムハンマドが言ったように、デフォルト値を設定したいときもある状況があります。この優れた例は、最初にデータが取り込まれ、次にクエリされるデータ構造です。

トライを検討してください。 Wordを追加するときに、サブノードが必要であるが存在しない場合は、トライを拡張するためにサブノードを作成する必要があります。 Wordの存在を照会する場合、サブノードが見つからないということは、Wordが存在せず、作成すべきでないことを示します。

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

7
David Kanarek

理論的には、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を取り除いて、それが役に立たないと見なされた場合は完全に削除することを除いて、変更を予測することはできません。

5

defaultdict over dictdict.setdefault)の欠点の1つは、defaultdictオブジェクトが新しい項目を作成することですEVERYTIME non existing key is(例:==print)。また、defaultdictクラスは一般にdictクラスよりも一般的ではないため、IMEをシリアル化することはより困難です。

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

3
xged

ほとんどの回答はsetdefaultまたはdefaultdict状態であるため、キーが存在しない場合にデフォルト値を設定できます。ただし、setdefaultの使用例に関する小さな注意点を指摘したいと思います。 Pythonインタープリターがsetdefaultitを実行すると、辞書にキーが存在する場合でも、関数の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は常に関数を再帰的に呼び出すため、パフォーマンスが向上することはありません。

3
picmate 涅

次に、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

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

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

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

1
Tuttle

これを取得し、辞書にデフォルト(!!!)を設定する場合、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
woodm1979

私が偶然見つけた非常に重要な使用例: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メンバーが保証されます。

1
Ethan Furman

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

[編集] 非常に間違っています! 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))
0
YvesgereY

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

#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'],}
0
Algebra

ここにある答えが好きです:

http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html

要するに、ダウンストリームの空のキーの検索をどのように処理するかに基づいて(パフォーマンスが重要でないアプリで)決定する必要があります(viz。KeyError対デフォルト値)。

0
Fred