Apa cara terbaik untuk mengimplementasikan kamus bersarang di Python?
Ini ide yang buruk, jangan lakukan itu. Sebagai gantinya, gunakan kamus reguler dan gunakan di dict.setdefault
mana yang sesuai, jadi ketika kunci hilang dalam penggunaan normal Anda mendapatkan yang diharapkanKeyError
. Jika Anda bersikeras untuk mendapatkan perilaku ini, berikut cara menembak diri sendiri:
Terapkan __missing__
pada adict
subclass untuk mengatur dan mengembalikan instance baru.
Pendekatan ini telah tersedia (dan didokumentasikan) sejak Python 2.5, dan (terutama berharga bagi saya) itu cukup mencetak seperti dict normal , alih-alih pencetakan jelek dari defaultdict autovivified otomatis:
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)() # retain local pointer to value
return value # faster to return than dict lookup
(Catatan self[key]
ada di sisi kiri penugasan, jadi tidak ada rekursi di sini.)
dan katakan Anda memiliki beberapa data:
data = {('new jersey', 'mercer county', 'plumbers'): 3,
('new jersey', 'mercer county', 'programmers'): 81,
('new jersey', 'middlesex county', 'programmers'): 81,
('new jersey', 'middlesex county', 'salesmen'): 62,
('new york', 'queens county', 'plumbers'): 9,
('new york', 'queens county', 'salesmen'): 36}
Inilah kode penggunaan kami:
vividict = Vividict()
for (state, county, occupation), number in data.items():
vividict[state][county][occupation] = number
Dan sekarang:
>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
Kritik
Kritik terhadap jenis wadah ini adalah jika pengguna salah mengeja kunci, kode kami bisa gagal secara diam-diam:
>>> vividict['new york']['queens counyt']
{}
Dan juga sekarang kita akan memiliki county yang salah eja dalam data kami:
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36},
'queens counyt': {}}}
Penjelasan:
Kami hanya menyediakan contoh lain dari kelas kami Vividict
setiap kali kunci diakses tetapi tidak ada. (Mengembalikan penugasan nilai berguna karena ia menghindari kami juga memanggil pengambil pada dikt, dan sayangnya, kami tidak dapat mengembalikannya ketika sedang ditetapkan.)
Catatan, ini adalah semantik yang sama dengan jawaban yang paling banyak dipilih tetapi dalam setengah baris kode - implementasi nosklo:
class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
Demonstrasi Penggunaan
Di bawah ini adalah contoh bagaimana dict ini dapat dengan mudah digunakan untuk membuat struktur dict bersarang dengan cepat. Ini dapat dengan cepat membuat struktur pohon hierarkis sedalam yang Anda inginkan.
import pprint
class Vividict(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
d = Vividict()
d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)
Output yang mana:
{'fizz': {'buzz': {}},
'foo': {'bar': {}, 'baz': {}},
'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}
Dan seperti yang ditunjukkan baris terakhir, itu cukup mencetak dengan indah dan untuk inspeksi manual. Tetapi jika Anda ingin secara visual memeriksa data Anda, menerapkan __missing__
untuk menetapkan contoh baru dari kelasnya ke kunci dan mengembalikannya adalah solusi yang jauh lebih baik.
Alternatif lain, untuk kontras:
dict.setdefault
Meskipun penanya berpikir ini tidak bersih, saya merasa lebih baik daripada Vividict
saya sendiri.
d = {} # or dict()
for (state, county, occupation), number in data.items():
d.setdefault(state, {}).setdefault(county, {})[occupation] = number
dan sekarang:
>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
'programmers': 81},
'middlesex county': {'programmers': 81,
'salesmen': 62}},
'new york': {'queens county': {'plumbers': 9,
'salesmen': 36}}}
Salah mengeja akan gagal dengan ribut, dan tidak mengacaukan data kami dengan informasi yang buruk:
>>> d['new york']['queens counyt']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'
Selain itu, saya pikir setdefault berfungsi dengan baik ketika digunakan dalam loop dan Anda tidak tahu apa yang akan Anda dapatkan untuk kunci, tetapi penggunaan berulang menjadi cukup memberatkan, dan saya tidak berpikir ada orang yang ingin mengikuti yang berikut:
d = dict()
d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})
Kritik lain adalah bahwa setdefault membutuhkan contoh baru apakah itu digunakan atau tidak. Namun, Python (atau setidaknya CPython) agak pintar menangani kasus baru yang tidak digunakan dan tidak direferensikan, misalnya, menggunakan kembali lokasi dalam memori:
>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)
Sebuah defaultdict vivified otomatis
Ini adalah implementasi yang tampak rapi, dan penggunaan dalam skrip yang tidak Anda periksa datanya akan sama bermanfaatnya dengan penerapan __missing__
:
from collections import defaultdict
def vivdict():
return defaultdict(vivdict)
Tetapi jika Anda perlu memeriksa data Anda, hasil dari default-vivified defaultdict diisi dengan data dengan cara yang sama terlihat seperti ini:
>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint;
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar':
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>,
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})
Output ini cukup tidak elegan, dan hasilnya cukup tidak dapat dibaca. Solusi yang biasanya diberikan adalah mengkonversi secara rekursif ke dikt untuk inspeksi manual. Solusi non-sepele ini dibiarkan sebagai latihan bagi pembaca.
Performa
Akhirnya, mari kita lihat kinerja. Saya mengurangi biaya instantiation.
>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747
Berdasarkan kinerja, dict.setdefault
bekerja yang terbaik. Saya sangat merekomendasikannya untuk kode produksi, jika Anda peduli dengan kecepatan eksekusi.
Jika Anda memerlukan ini untuk penggunaan interaktif (dalam notebook IPython, mungkin) maka kinerja tidak terlalu penting - dalam hal ini, saya akan menggunakan Vividict untuk keterbacaan output. Dibandingkan dengan objek AutoVivification (yang menggunakan __getitem__
alih-alih __missing__
, yang dibuat untuk tujuan ini) jauh lebih unggul.
Kesimpulan
Menerapkan __missing__
pada subclass dict
untuk mengatur dan mengembalikan contoh baru sedikit lebih sulit daripada alternatif tetapi memiliki manfaat
- Instansiasi mudah
- populasi data mudah
- tampilan data mudah
dan karena kurang rumit dan lebih berkinerja daripada memodifikasi __getitem__
, itu harus lebih disukai daripada metode itu.
Namun demikian, ia memiliki kekurangan:
- Pencarian buruk akan gagal secara diam-diam.
- Pencarian buruk akan tetap ada di kamus.
Jadi saya pribadi lebih suka setdefault
solusi lain, dan ada dalam setiap situasi di mana saya membutuhkan perilaku semacam ini.
Vividict
? Misalnya3
danlist
untuk dict dari dict dari daftar yang dapat diisi dengand['primary']['secondary']['tertiary'].append(element)
. Saya dapat mendefinisikan 3 kelas berbeda untuk setiap kedalaman tetapi saya ingin menemukan solusi yang lebih bersih.d['primary']['secondary'].setdefault('tertiary', []).append('element')
- ?? Terima kasih atas pujiannya, tetapi biarkan saya jujur - saya tidak pernah benar-benar menggunakan__missing__
- saya selalu menggunakansetdefault
. Saya mungkin harus memperbarui kesimpulan / intro saya ...The bad lookup will remain in the dictionary.
saya mempertimbangkan untuk menggunakan solusi ini? Sangat dihargai. Thxsetdefault
ketika bersarang lebih dari dua level. Sepertinya tidak ada struktur di Python yang dapat menawarkan vivifikasi sejati seperti yang dijelaskan. Saya harus puas dengan dua metode menyatakan satu untukget_nested
& satuset_nested
yang menerima referensi untuk dict dan daftar atribut bersarang.Pengujian:
Keluaran:
sumber
pickle
mengerikan antara versi python. Hindari menggunakannya untuk menyimpan data yang ingin Anda simpan. Gunakan hanya untuk cache dan barang-barang yang bisa Anda buang dan buat ulang sesuka hati. Bukan sebagai metode penyimpanan atau serialisasi jangka panjang.sqlite
database untuk menyimpannya.Hanya karena saya belum melihat yang sekecil ini, inilah dict yang bersarang sebanyak yang Anda inginkan, tanpa keringat:
sumber
yodict = lambda: defaultdict(yodict)
.dict
, jadi untuk menjadi sepenuhnya setara kita perlux = Vdict(a=1, b=2)
bekerja.dict
bukan persyaratan yang dinyatakan oleh OP, yang hanya meminta "cara terbaik" untuk mengimplementasikannya - dan selain itu, itu tidak / tidak seharusnya masalah sebanyak itu di Python.Anda bisa membuat file YAML dan membacanya menggunakan PyYaml .
Langkah 1: Buat file YAML, "employment.yml":
Langkah 2: Baca dengan Python
dan sekarang
my_shnazzy_dictionary
memiliki semua nilai Anda. Jika Anda perlu melakukan ini dengan cepat, Anda dapat membuat YAML sebagai string dan memasukkannya ke dalamyaml.safe_load(...)
.sumber
Karena Anda memiliki desain skema bintang, Anda mungkin ingin menyusunnya lebih seperti tabel relasional dan kurang seperti kamus.
Hal semacam itu bisa sangat berarti untuk menciptakan desain seperti data warehouse tanpa overhead SQL.
sumber
Jika jumlah level bersarang kecil, saya gunakan
collections.defaultdict
untuk ini:Menggunakan
defaultdict
seperti ini menghindari banyak berantakansetdefault()
,get()
dllsumber
Ini adalah fungsi yang mengembalikan kamus bersarang dari kedalaman arbitrer:
Gunakan seperti ini:
Ulangi semuanya dengan sesuatu seperti ini:
Ini mencetak:
Anda mungkin pada akhirnya ingin membuatnya sehingga item baru tidak dapat ditambahkan ke dikt. Sangat mudah untuk secara rekursif mengubah semua ini
defaultdict
menjadi normaldict
.sumber
Saya menemukan
setdefault
cukup berguna; Ia memeriksa apakah ada kunci dan menambahkannya jika tidak:setdefault
selalu mengembalikan kunci yang relevan, sehingga Anda benar-benar memperbarui nilai 'd
' di tempat.Ketika datang ke iterasi, saya yakin Anda bisa menulis generator dengan cukup mudah jika belum ada di Python:
sumber
Seperti yang disarankan orang lain, basis data relasional bisa lebih bermanfaat bagi Anda. Anda bisa menggunakan database sqlite3 di memori sebagai struktur data untuk membuat tabel dan kemudian meminta mereka.
Ini hanyalah contoh sederhana. Anda bisa menentukan tabel terpisah untuk negara, kabupaten, dan jabatan.
sumber
collections.defaultdict
dapat di-sub-class untuk membuat dict bersarang. Kemudian tambahkan metode iterasi yang berguna ke kelas itu.sumber
Adapun "blok coba / tangkap yang menjengkelkan":
hasil panen
Anda dapat menggunakan ini untuk mengonversi dari format kamus Anda ke format terstruktur:
sumber
Anda dapat menggunakan Addict: https://github.com/mewwts/addict
sumber
defaultdict()
adalah temanmu!Untuk kamus dua dimensi yang dapat Anda lakukan:
Untuk lebih banyak dimensi Anda dapat:
sumber
Untuk memudahkan pengulangan pada kamus bersarang Anda, mengapa tidak menulis generator sederhana?
Jadi, jika Anda memiliki kamus bersarang yang dikompilasi, iterasi menjadi sederhana:
Jelas generator Anda dapat menghasilkan format data apa pun yang berguna bagi Anda.
Mengapa Anda menggunakan coba tangkap balok untuk membaca pohon? Cukup mudah (dan mungkin lebih aman) untuk menanyakan apakah ada kunci dalam dict sebelum mencoba mengambilnya. Fungsi yang menggunakan klausa penjaga mungkin terlihat seperti ini:
Atau, metode yang mungkin agak bertele-tele, adalah menggunakan metode get:
Tetapi untuk cara yang agak lebih ringkas, Anda mungkin ingin melihat menggunakan collections.defaultdict , yang merupakan bagian dari pustaka standar sejak python 2.5.
Saya membuat asumsi tentang arti struktur data Anda di sini, tetapi seharusnya mudah untuk menyesuaikan dengan apa yang sebenarnya ingin Anda lakukan.
sumber
Saya suka ide membungkus ini di kelas dan mengimplementasikan
__getitem__
dan__setitem__
sedemikian rupa sehingga mereka menerapkan bahasa permintaan sederhana:Jika Anda ingin menjadi mewah, Anda juga bisa menerapkan sesuatu seperti:
tapi kebanyakan saya pikir hal seperti itu akan sangat menyenangkan untuk diterapkan: D
sumber
Kecuali jika dataset Anda akan tetap sangat kecil, Anda mungkin ingin mempertimbangkan untuk menggunakan basis data relasional. Ini akan melakukan apa yang Anda inginkan: membuatnya mudah untuk menambah jumlah, memilih himpunan bagian dari jumlah, dan bahkan jumlah agregat berdasarkan negara, wilayah, pekerjaan, atau kombinasi dari semua ini.
sumber
Contoh:
Sunting: Sekarang kembali kamus ketika kueri dengan wild card (
None
), dan nilai-nilai tunggal sebaliknya.sumber
Saya memiliki hal serupa terjadi. Saya memiliki banyak kasus di mana saya melakukannya:
Tetapi pergi ke banyak level. Ini ".get (item, {})" itulah kuncinya karena akan membuat kamus lain jika belum ada. Sementara itu, saya sudah memikirkan cara untuk menghadapi ini dengan lebih baik. Saat ini, ada banyak
Jadi sebagai gantinya, saya membuat:
Yang memiliki efek yang sama jika Anda melakukannya:
Lebih baik? Aku pikir begitu.
sumber
Anda dapat menggunakan rekursi dalam lambdas dan defaultdict, tidak perlu mendefinisikan nama:
Ini sebuah contoh:
sumber
Saya dulu menggunakan fungsi ini. ini aman, cepat, mudah dirawat.
Contoh:
sumber