Чому dict.get (key) працював, а не dict [key]?


17

Я намагаюся згрупувати двійкові рядки певних чисел на основі кількості 1-х рядків у рядку.

Це не працює:

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]

Очікуваний словник one_groupsповинен бути

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

Але я отримую

{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']}

Поки що єдине, що працювало, це якщо я використовую one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]замість цьогоone_groups[x.count('1')] += [x]

Але чому це так? Якщо я пам'ятаю правильно, чи не dict[key]повинен повертати значення цього словника, як це dict.get(key)працює? Я бачив цю тему Чому dict.get (ключ) замість dict [key]? але це не відповіло на моє запитання для цього конкретного випадку, оскільки я точно знаю, що програма не призначена для отриманняKeyError

Я також спробував, one_groups[x.count('1')].append(x)але і це не працює.


8
getповернути, Noneякщо ключ не існує або якесь задане значення за замовчуванням, тоді як оператор індексу []викликає помилку, якщо ключ не існує.
adnanmuttaleb

Sidenote, bin(x)[2:].rjust(4, '0')можна спростити до '{:0>4b}'.format(x).
wjandrea

1
До речі, це допомагає зробити мінімально відтворюваний приклад . У цьому випадку те, як ви робите binaries, не стосується питання, тож ви можете просто надати його значення.
wjandrea

1
Чи відповідає це на ваше запитання? dict.fromkeys всі вказують на той самий список
Георгій

Відповіді:


24

Проблема полягає в незмінності:

one_groups = dict.fromkeys(range(5), [])- це передає той же список, що і значення для всіх клавіш . Отже, якщо ви змінюєте одне значення, ви змінюєте їх усі.

Це в основному те саме, що говорити:

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

Якщо ви хочете скористатися новим списком, вам потрібно зробити це в циклі - або в явному forциклі, або в розумінні диктату:

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

Ця річ буде "виконувати" [](що дорівнює list()) для кожного ключа, таким чином, створюючи значення з різними списками.


Чому getпрацює? Тому що ви явно приймаєте поточний список, але +створюєте новий список результатів. І це не має значення , чи є це one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]або one_groups[x.count('1')] = one_groups[x.count('1')] + [x]- важливо те , що є +.

Я знаю, як всі кажуть, що a+=bце справедливо a=a+b, але реалізація може бути різною для оптимізації - у випадку списків +=це лише .extendтому, що ми знаємо, що хочемо наш результат у поточній змінній, тому створення нового списку буде марною пам’яттю.


Ага, так, зрозумів. Я також пам’ятаю, що мав подібну проблему, коли хотів створити 2D-список, використовуючи mylist = [[] * 5] * 5і як mylist = [[] for x in range(5)] * 5би це виправити. Просто для швидкого уточнення, як я зрозумів, це відбувається через змінні, що вказують на адресу пам'яті цього порожнього списку. Це також означає, що проблема не виникне, якщо я замість цього використовую примітиви?
SpectraXCD

1
Так, якщо ви використовували примітиви, це вирішить це, але зламається, one_groups[x.count('1')] += [x]оскільки ви не можете додати список примітивного типу. Кращим рішенням є натомість використовувати засудження за замовчуванням.
Fakher Mokadem

4
конкретно, +дзвонить __add__і повертає новий об’єкт під час +=дзвінків __iadd__, і не потрібно повертати новий об’єкт
njzk2

8

Проблема в використанні one_groups = dict.fromkeys(range(5), [])

(Це передає той же список, що і значення для всіх клавіш. Отже, якщо ви зміните одне значення, ви зміните їх усі)


Ви можете використовувати це замість: one_groups = {i:[] for i in range(5)}

(Ця річ буде "виконати" [] (що дорівнює списку ()) для кожного ключа, таким чином створивши значення з різними списками.)


6
Ви абсолютно праві, хоча пояснення було б дуже корисним. Справді не очевидно, у чому різниця між двома лініями.
Simon Fink

Так, це моє погано. вибачте
Hameda169

4

Це допомога fromkeysметоду дикта .

Довідка щодо вбудованої функції з клавіш:

метод fromkeys (ітерабельний, значення = Немає, /) метод екземпляра buildins.type Створіть новий словник з ключами від ітерабельного та значеннями, встановленими на значення

Це говорить про те, що Fromkeys прийме значення, і навіть воно може викликатись, він спочатку оцінить його, а потім призначить це значення всім клавішам dict.

Списки змінені в Python, і тому він призначить однакові посилання порожнього списку, і одна зміна вплине на них усіх.

Замість цього використовуйте за замовчуванням:

>>> 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

Це призведе до призначення неіснуючих ключів, а значення за замовчуванням будуть порожніми списками (у цьому випадку).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.