Apakah ada cara cerdas untuk meneruskan kunci ke default_factory defaultdict?

95

Kelas memiliki konstruktor yang mengambil satu parameter:

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

Di suatu tempat di kode, ini berguna untuk nilai-nilai dalam sebuah dict untuk mengetahui kuncinya.
Saya ingin menggunakan defaultdict dengan kunci yang diteruskan ke nilai default baru lahir:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Ada saran?

Benjamin Nitlehoo
sumber

Jawaban:

128

Ini hampir tidak memenuhi syarat sebagai pintar - tetapi subclass adalah teman Anda:

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)
Jochen Ritzel
sumber
16
Itulah keburukan yang saya coba hindari ... Bahkan menggunakan dikt sederhana dan memeriksa keberadaan kunci jauh lebih bersih.
Benjamin Nitlehoo
1
@Paul: namun ini adalah jawaban Anda. Kejelekan? Ayolah!
tzot
4
Saya pikir saya hanya akan mengambil sedikit kode itu dan memasukkannya ke dalam modul utilitas umum pribadi saya sehingga saya dapat menggunakannya kapan pun saya mau. Tidak terlalu jelek seperti itu ...
weronika
24
+1 Secara langsung menjawab pertanyaan OP dan tidak terlihat "jelek" bagi saya. Juga jawaban yang baik karena banyak tampaknya tidak menyadari bahwa defaultdict's __missing__()metode dapat ditimpa (karena dapat di setiap subclass dari built-in dictkelas sejak versi 2.5).
martineau
7
+1 Keseluruhan tujuan __missing__ adalah untuk menyesuaikan perilaku kunci yang hilang. Pendekatan dict.setdefault () yang disebutkan oleh @silentghost juga akan berfungsi (di sisi positifnya, setdefault () pendek dan sudah ada; di sisi minus, ia mengalami masalah efisiensi dan tidak ada yang benar-benar menyukai nama "setdefault") .
Raymond Hettinger
26

Tidak, tidak ada.

The defaultdictimplementasi tidak dapat dikonfigurasi untuk lulus hilang keydengan default_factoryout-of-the-box. Satu-satunya pilihan Anda adalah menerapkan defaultdictsubkelas Anda sendiri , seperti yang disarankan oleh @JochenRitzel, di atas.

Tapi itu tidak "pintar" atau hampir sebersih solusi perpustakaan standar (jika ada). Jadi jawaban singkat Anda, pertanyaan ya / tidak jelas "Tidak".

Sayang sekali perpustakaan standar kehilangan alat yang sering dibutuhkan.

Stuart Berg
sumber
Ya, itu akan menjadi pilihan desain yang lebih baik membiarkan pabrik mengambil kunci (fungsi unary daripada nullary). Sangat mudah untuk membuang argumen saat kita ingin mengembalikan konstanta.
YvesgereY
6

Saya tidak berpikir Anda perlu defaultdictdi sini sama sekali. Mengapa tidak menggunakan dict.setdefaultmetode saja?

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

Itu tentu saja akan menciptakan banyak contoh C. Jika itu adalah masalah, saya pikir pendekatan yang lebih sederhana akan dilakukan:

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

Ini akan lebih cepat dari pada defaultdictatau alternatif lain sejauh yang saya bisa lihat.

ETA terkait kecepatan inpengujian vs. menggunakan klausul coba-kecuali:

>>> 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
SilentGhost
sumber
7
Ini sangat boros dalam kasus di mana d diakses berkali-kali, dan jarang kehilangan kunci: C (key) dengan demikian akan membuat banyak objek yang tidak dibutuhkan untuk dikumpulkan oleh GC. Juga, dalam kasus saya ada rasa sakit tambahan, karena membuat objek C baru lambat.
Benjamin Nitlehoo
@ Paul: itu benar. Saya akan menyarankan metode yang lebih sederhana lagi, lihat hasil edit saya.
SilentGhost
Saya tidak yakin ini lebih cepat dari defaultdict, tetapi inilah yang biasanya saya lakukan (lihat komentar saya untuk jawaban THC4k). Saya berharap ada cara sederhana untuk meretas fakta default_factory tidak membutuhkan argumen, untuk menjaga kode sedikit lebih elegan.
Benjamin Nitlehoo
5
@SilentGhost: Saya tidak mengerti - bagaimana ini menyelesaikan masalah OP? Saya pikir OP ingin mencoba membaca d[key]untuk kembali d[key] = C(key)jika key not in d. Tetapi solusi Anda mengharuskan dia untuk benar-benar pergi dan mengatur d[key]sebelumnya? Bagaimana dia tahu mana yang keydia butuhkan?
maks
2
Karena setdefault sangat jelek dan defaultdict dari collection HARUS mendukung fungsi pabrik yang menerima kuncinya. Kesempatan yang terbuang percuma dari para desainer Python!
jgomo3
0

Berikut adalah contoh kerja kamus yang secara otomatis menambahkan nilai. Tugas demonstrasi dalam menemukan file duplikat di / usr / include. Perhatikan kamus kustomisasi PathDict hanya membutuhkan empat baris:

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)))
gerardw
sumber