キーをdefaultdictのdefault_factoryに渡す賢い方法はありますか?


93

クラスには、1つのパラメーターを取るコンストラクターがあります。

class C(object):
    def __init__(self, v):
        self.v = v
        ...

コードのどこかで、dictの値がキーを知っていると便利です。
生まれたばかりのデフォルト値に渡されるキーでdefaultdictを使用したいと思います。

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

助言がありますか?

回答:


127

それは賢いとは言えませんが、サブクラス化はあなたの友人です:

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)

16
それはまさに私が避けようとしている醜さです...単純な辞書を使用して、キーの存在をチェックすることでさえ、はるかにきれいです。
Benjamin Nitlehoo、

1
@Paul:そして​​、これはあなたの答えです。醜さ?いい加減にして!
tzot

4
必要なときにいつでも使用できるように、そのコードを取り出してパーソナライズされた汎用ユーティリティモジュールに配置するつもりだと思います。それほど醜くはありません...
ウェロニカ

24
+1 OPの質問に直接対応し、私にとって「醜い」とは思わない。また、良い答えは、多くのためにということを理解していないようですdefaultdictさん__missing__()(ビルトインの任意のサブクラスでそれができるような方法をオーバーライドすることができますdictクラスバージョン2.5以降)。
martineau

7
+1 __missing__の目的は、不足しているキーの動作をカスタマイズすることです。@silentghostで言及されているdict.setdefault()アプローチも機能します(プラス側では、setdefault()は短く、すでに存在しています。マイナス側では、効率の問題があり、「setdefault」という名前が好きな人はいません)。 。
レイモンドヘッティンガー2016

26

いいえ、ありません。

defaultdict実装が行方不明に合格するように設定することはできませんkeydefault_factoryすぐに。defaultdict上記の@JochenRitzelで提案されているように、あなたの唯一の選択肢はあなた自身のサブクラスを実装することです。

しかし、それは「賢い」ものではなく、または標準のライブラリソリューション(存在する場合)ほどクリーンではありません。 したがって、あなたの簡潔なイエス/ノーの質問への答えは明らかに「ノー」です。

標準ライブラリに頻繁に必要なツールが欠けているのは残念です。


そうです、ファクトリーにキー(nullaryではなく単項関数)を使用させる方が、より良い設計上の選択でした。定数を返したいときに引数を破棄するのは簡単です。
YvesgereY

6

defaultdictここには必要ないでしょう。dict.setdefaultメソッドを使用しないのはなぜですか?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

もちろん、それによってのインスタンスが多数作成されますC。それが問題である場合、私はより簡単なアプローチでうまくいくと思います:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

defaultdict私が見る限り、それは他のどの方法よりも速いでしょう。

inテストの速度とtry-except句の使用に関するETA

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264

7
これは、dに何度もアクセスする場合は非常に無駄が多く、まれにキーが欠落するだけです。C(key)は、GCが収集する必要のないオブジェクトを大量に作成します。また、私の場合、新しいCオブジェクトの作成が遅いため、追加の苦痛があります。
Benjamin Nitlehoo、

@Paul:そうです。私はそれからもっと簡単な方法を提案します、私の編集を見てください。
SilentGhost 2010年

defaultdictよりも速いかどうかはわかりませんが、これは通常行うことです(THC4kの回答に対する私のコメントを参照)。コードを少しエレガントにするために、default_factoryが引数を取らないという事実をハックする簡単な方法があることを願っています。
Benjamin Nitlehoo、

5
@SilentGhost:わかりません-これはOPの問題をどのように解決しますか?OPは、もし読み返すd[key]ために何かを試みたいと思ったと思いました。しかし、あなたの解決策では、彼が実際に行って事前に事前設定する必要がありますか?どのようにして彼は必要なものを知るのでしょうか?d[key] = C(key)key not in dd[key]key
最大

2
setdefaultは地獄として醜いので、コレクションからのdefaultdictはキーを受け取るファクトリ関数をサポートする必要があります(SHOULD)。Pythonデザイナーからの無駄な機会です。
jgomo3

0

これは、自動的に値を追加する辞書の実用的な例です。/ usr / includeで重複ファイルを見つけるデモタスク。カスタマイズ辞書PathDictには4行しか必要ないことに注意してください。

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.