Как мне инициализировать словарь пустых списков в Python?

88

Моя попытка программно создать словарь списков не позволяет мне индивидуально адресовать ключи словаря. Когда я создаю словарь списков и пытаюсь добавить к одному ключу, все они обновляются. Вот очень простой тестовый пример:

data = {}
data = data.fromkeys(range(2),[])
data[1].append('hello')
print data

Фактический результат: {0: ['hello'], 1: ['hello']}

Ожидаемый результат: {0: [], 1: ['hello']}

Вот что работает

data = {0:[],1:[]}
data[1].append('hello')
print data

Фактический и ожидаемый результат: {0: [], 1: ['hello']}

Почему fromkeysметод работает не так, как ожидалось?

Мартин Берч
источник

Ответы:

111

Передача в []качестве второго аргумента в dict.fromkeys()дает довольно бесполезный результат - все значения в словаре будут одним и тем же объектом списка.

В Python 2.7 или выше вы можете вместо этого использовать словарное понимание:

data = {k: [] for k in range(2)}

В более ранних версиях Python вы можете использовать

data = dict((k, []) for k in range(2))
Свен Марнах
источник
3
Это довольно неинтуитивное поведение, есть идеи, почему один и тот же объект используется для всех ключей?
Bar
2
@Bar Потому что в семантике языка Python функции больше нечего делать. Вы передаете один объект, который будет использоваться в качестве значения для всех ключей, так что один объект используется для всех ключей. Было бы лучше, если бы fromkeys()метод принял вместо этого фабричную функцию, чтобы мы могли передать ее listкак функцию, и эта функция вызывалась бы один раз для каждого созданного ключа, но это не фактический API dict.fromkeys().
Свен Марнах,
3
Это совсем не интуитивно. Это заняло у меня час, чтобы найти. Спасибо
Астрид
1
То же самое произойдет, если вы передадите dict () в качестве второго аргумента. Очень непонятное поведение.
Орли
@Orly Это потому, что сначала создается один пустой словарь, а затем ссылка на него передается во все инициализации.
Dr_Zaszuś
83

Использовать defaultdict этого :

from collections import defaultdict
data = defaultdict(list)
data[1].append('hello')

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

В вашем примере происходит то, что вы используете один (изменяемый) список:

alist = [1]
data = dict.fromkeys(range(2), alist)
alist.append(2)
print data

будет выводить {0: [1, 2], 1: [1, 2]}.

Мартейн Питерс
источник
2
В моем случае мне нужно заранее инициализировать все ключи, чтобы остальная часть логики программы могла работать должным образом, но в противном случае это было бы хорошим решением. Спасибо.
Martin Burch
Я предполагаю, что в этом ответе не хватает того, что это решение работает, в отличие от OP, потому что listздесь не пустой список, а тип (или вы можете увидеть его как вызываемый конструктор, я думаю). Таким образом, каждый раз, когда передается отсутствующий ключ, создается новый список вместо того, чтобы повторно использовать тот же самый.
Dr_Zaszuś
8

Вы заполняете словари ссылками на один список, поэтому при его обновлении обновление отражается во всех ссылках. Вместо этого попробуйте словарное понимание. См. Раздел Создание словаря с пониманием списка в Python

d = {k : v for k in blah blah blah}
Коби
источник
отличное предложение по инициализации значений словаря ... спасибо, Коби! Я расширил ваш пример, чтобы сбросить значения в существующем словаре, d. Я выполнил это следующим образом: d = {k: 0 for k in d}
John
Что vв этом ответе?
Dr_Zaszuś
-2

Вы можете использовать это:

data[:1] = ['hello']
Коннер Дассен
источник
2
OP может быть полезно объяснить, почему это работает. В исходном вопросе спрашивается, почему он не работает должным образом.
william.taylor 09
@ william.taylor.09 вроде очевидно, почему это работает, не так ли?
Коннер Дассен
OP спрашивает (спрашивал): "Почему метод fromkeys не работает должным образом?"
william.taylor 09