Dlaczego dict.get (klucz) działa, ale nie dyktuje [klucz]?

17

Próbuję pogrupować ciągi binarne określonych liczb na podstawie liczby 1 w ciągu.

To nie działa:

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]

Oczekiwany słownik one_groupsmusi być

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

Ale rozumiem

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

Jak dotąd jedyne, co zadziałało, to jeśli użyję one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]zamiastone_groups[x.count('1')] += [x]

Ale dlaczego tak jest? Jeśli dobrze pamiętam, czy nie dict[key]powinno zwracać wartości tego słownika, podobnie jak dict.get(key)działa? Widziałem ten wątek Dlaczego dict.get (klucz) zamiast dict [klucz]? ale nie odpowiedziałem na moje pytanie w tym konkretnym przypadku, ponieważ wiem na pewno, że program nie jest przeznaczony do uzyskaniaKeyError

Próbowałem też, one_groups[x.count('1')].append(x)ale to też nie działa.

SpectraXCD
źródło
8
getzwraca, Nonejeśli klucz nie istnieje lub podana jest wartość domyślna, a operator indeksu []zgłasza błąd, jeśli klucz nie istnieje.
adnanmuttaleb
Sidenote, bin(x)[2:].rjust(4, '0')można uprościć '{:0>4b}'.format(x).
wjandrea,
1
BTW pomaga stworzyć minimalny, powtarzalny przykład . W tym przypadku sposób, w jaki tworzysz, binariesnie ma związku z pytaniem, więc możesz po prostu podać jego wartość.
wjandrea,
1
Czy to odpowiada na twoje pytanie? dict.fromkeys wszystkie wskazują na tę samą listę
Georgy

Odpowiedzi:

24

Problemem jest zmienność:

one_groups = dict.fromkeys(range(5), [])- przekazuje tę samą listę jako wartość do wszystkich kluczy . Więc jeśli zmienisz jedną wartość, zmienisz je wszystkie.

Jest to w zasadzie to samo, co powiedzenie:

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

Jeśli chcesz użyć nowej listy, musisz to zrobić w pętli - albo w forpętli jawnej , albo w zrozumieniu nagrania:

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

Ta funkcja „wykona” [](co jest równe list()) dla każdego klucza, tworząc wartości z różnymi listami.


Dlaczego getdziała Ponieważ jawnie bierzesz aktualną listę, ale +tworzy nową listę wyników. I nie ma znaczenia, czy jest to one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]albo one_groups[x.count('1')] = one_groups[x.count('1')] + [x]- co ważne jest to, że nie ma +.

Wiem, że wszyscy mówią, że a+=bjest po prostu słuszny a=a+b, ale implementacja może być inna w celu optymalizacji - w przypadku list +=wynika tylko z .extendtego, że wiemy, że chcemy, aby nasz wynik był w bieżącej zmiennej, więc tworzenie nowej listy byłoby marnowaniem pamięci.

h4z3
źródło
Ach, tak, zrozumiałe. Pamiętam też, że miałem podobny problem, kiedy chciałem utworzyć listę 2D mylist = [[] * 5] * 5i jak mylist = [[] for x in range(5)] * 5to naprawiłem. Zrozumiałem, że dla szybkiego wyjaśnienia dzieje się tak ze względu na zmienne wskazujące adres pamięci tej pustej listy. Czy to oznacza również, że problem nie wystąpiłby, jeśli zamiast tego użyłem prymitywów?
SpectraXCD,
1
Tak, jeśli użyłeś prymitywów, rozwiąże to problem, ale się zepsuje, one_groups[x.count('1')] += [x]ponieważ nie możesz dodać listy do typu prymitywnego. Lepszym rozwiązaniem jest użycie defaultdict.
Fakher Mokadem
4
w szczególności +wywołuje __add__i zwraca nowy obiekt, podczas gdy +=wywołuje __iadd__, i nie jest wymagane, aby zwrócić nowy obiekt
njzk2,
8

Problem polega na użyciu one_groups = dict.fromkeys(range(5), [])

(To przekazuje tę samą listę jako wartość do wszystkich kluczy. Więc jeśli zmienisz jedną wartość, zmienisz je wszystkie)


Możesz użyć tego zamiast: one_groups = {i:[] for i in range(5)}

(Ta funkcja „wykona” [] (co jest równe list ()) dla każdego klucza, tworząc wartości z różnymi listami.)

Hameda169
źródło
6
Masz całkowitą rację, chociaż wyjaśnienie byłoby naprawdę pomocne. Naprawdę nie jest oczywiste, jaka jest różnica między dwiema liniami.
Simon Fink,
Tak, to moje złe. przepraszam
Hameda169,
4

To jest pomoc na temat fromkeysmetody dykta .

Pomoc na temat wbudowanej funkcji klawiszy:

fromkeys (iterable, value = None, /) method of builtins.type instance Utwórz nowy słownik z kluczami z iteracyjnego i wartościami ustawionymi na wartość

To znaczy, że klawisze from zaakceptują wartość, a nawet jest to możliwość wywołania, najpierw ją oceni, a następnie przypisze tę wartość do wszystkich klawiszy dict.

Listy można modyfikować w Pythonie, więc przypisuje to samo puste odwołanie do listy, a jedna zmiana wpłynie na wszystkie.

Zamiast tego użyj defaultdict:

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

Spowoduje to przyjęcie przypisań do nieistniejących kluczy, a wartości będą domyślnie puste na listach (w tym przypadku).

Fakher Mokadem
źródło