Mengapa dict.get (kunci) berfungsi tetapi tidak dict [kunci]?

17

Saya mencoba untuk mengelompokkan string biner angka-angka tertentu berdasarkan berapa banyak 1 yang ada dalam string.

Ini tidak berfungsi:

s = "0 1 3 7 8 9 11 15"
numbers = map(int, s.split())
binaries = [bin(x)[2:].rjust(4, '0') for x in numbers]

one_groups = dict.fromkeys(range(5), [])
for x in binaries:
    one_groups[x.count('1')] += [x]

Kamus yang diharapkan one_groupsperlu

{0: ['0000'], 
 1: ['0001', '1000'], 
 2: ['0011', '1001'], 
 3: ['0111', '1011'], 
 4: ['1111']}

Tapi saya mengerti

{0: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 1: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 2: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 3: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 4: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111']}

Sejauh ini satu-satunya hal yang berhasil adalah jika saya menggunakan one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]alih-alihone_groups[x.count('1')] += [x]

Tapi mengapa begitu? Jika saya ingat dengan benar, bukankah dict[key]seharusnya mengembalikan nilai kamus itu, mirip dengan cara dict.get(key)kerjanya? Saya telah melihat utas ini Mengapa dict.get (kunci) alih-alih dict [kunci]? tetapi tidak menjawab pertanyaan saya untuk kasus khusus ini, karena saya tahu pasti program ini tidak dimaksudkan untuk mendapatkanKeyError

Saya juga sudah mencoba one_groups[x.count('1')].append(x)tetapi ini tidak berhasil.

SpectraXCD
sumber
8
getkembali Nonejika kunci tidak ada atau nilai default yang disediakan, sementara operator indeks []meningkatkan kesalahan jika kunci tidak ada.
adnanmuttaleb
Sidenote, bin(x)[2:].rjust(4, '0')bisa disederhanakan menjadi '{:0>4b}'.format(x).
wjandrea
1
BTW itu membantu untuk membuat contoh direproduksi minimal . Dalam hal ini cara Anda membuat binariestidak relevan dengan pertanyaan, jadi Anda bisa memberikan nilainya.
wjandrea
1
Apakah ini menjawab pertanyaan Anda? dict.fromkeys semua menunjuk ke daftar yang sama
Georgy

Jawaban:

24

Masalahnya adalah mutabilitas:

one_groups = dict.fromkeys(range(5), [])- ini melewati daftar yang sama dengan nilai untuk semua kunci . Jadi, jika Anda mengubah satu nilai, Anda mengubah semuanya.

Pada dasarnya sama dengan mengatakan:

tmp = []
one_groups = dict.fromkeys(range(5), tmp)
del tmp

Jika Anda ingin menggunakan daftar baru, Anda perlu melakukannya dalam satu lingkaran - baik forlingkaran eksplisit atau dalam pemahaman dict:

one_groups = {key: [] for key in range(5)}

Hal ini akan "mengeksekusi" [](yang sama dengan list()) untuk setiap kunci, sehingga membuat nilainya dengan daftar yang berbeda.


Kenapa getberhasil? Karena Anda secara eksplisit mengambil daftar saat ini, tetapi +membuat daftar hasil baru. Dan tidak masalah apakah itu one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]atau one_groups[x.count('1')] = one_groups[x.count('1')] + [x]- yang penting ada +.

Saya tahu bagaimana semua orang mengatakan a+=badil a=a+b, tetapi implementasinya mungkin berbeda untuk optimasi - jika daftar, +=hanya .extendkarena kami tahu kami ingin hasil kami dalam variabel saat ini, jadi membuat daftar baru akan membuang-buang memori.

h4z3
sumber
Ah, ya, mengerti. Saya juga ingat memiliki masalah yang sama ketika saya ingin membuat daftar 2D menggunakan mylist = [[] * 5] * 5dan bagaimana mylist = [[] for x in range(5)] * 5memperbaikinya. Hanya untuk klarifikasi cepat, dari yang saya mengerti, ini terjadi karena variabel yang menunjuk ke alamat memori daftar kosong itu. Apakah ini juga berarti masalah tidak akan terjadi jika saya menggunakan primitif?
SpectraXCD
1
Ya, jika Anda menggunakan primitif ini akan menyelesaikannya, tetapi akan rusak one_groups[x.count('1')] += [x]karena Anda tidak dapat menambahkan daftar ke tipe primitif. Solusi yang lebih baik adalah dengan menggunakan defaultdict sebagai gantinya.
Fakher Mokadem
4
khusus, +panggilan __add__dan mengembalikan objek baru, sementara +=panggilan __iadd__, dan tidak diharuskan untuk mengembalikan objek baru
njzk2
8

Masalahnya menggunakan one_groups = dict.fromkeys(range(5), [])

(Ini melewati daftar yang sama dengan nilai untuk semua kunci. Jadi jika Anda mengubah satu nilai, Anda mengubah semuanya)


Anda bisa menggunakan ini sebagai gantinya: one_groups = {i:[] for i in range(5)}

(Hal ini akan "mengeksekusi" [] (yang sama dengan daftar ()) untuk setiap kunci, sehingga membuat nilai dengan daftar yang berbeda.)

Hameda169
sumber
6
Anda memang benar, meskipun penjelasan akan sangat membantu. Benar-benar tidak mengabaikan apa perbedaan antara dua garis itu.
Simon Fink
Ya, ini salahku. maaf
Hameda169
4

Ini adalah bantuan pada fromkeysmetode dikt .

Bantuan pada fungsi bawaan dari tombol:

fromkeys (iterable, value = None, /) metode builtins.type instance Buat kamus baru dengan kunci dari iterable dan nilai-nilai diatur ke nilai

Yang mengatakan bahwa fromkey akan menerima nilai, dan bahkan itu adalah callable, itu akan mengevaluasinya terlebih dahulu, dan kemudian menetapkan nilai itu ke semua kunci dikt.

Daftar bisa berubah dalam Python, dan itu akan menetapkan referensi daftar kosong yang sama dan satu perubahan akan mempengaruhi semuanya.

Gunakan defaultdict sebagai gantinya:

>>> from collections import defaultdict
>>> one_groups = defaultdict(list)
>>> for x in binaries:
      one_groups[x.count('1')] += [x]
>>> one_groups = dict(one_groups) # to stop default dict behavior

Ini akan menerima penugasan untuk kunci yang tidak ada dan nilai akan default ke daftar kosong (dalam hal ini).

Fakher Mokadem
sumber