Баг со словарем в Python 3
Заметил баг при работе со словарём начиная с версии Python 3.6 до 3.9.9, а именно если использовать создание словаря со списком внутри с помощью dict.fromkeys(arr, []) , то при добавлении значений к определенному ключу, они добавляются ко всем ключам в словаре. Подробнее на примерах ниже:
arr = [i for i in range(1, 6)]
my_dict = {1: [], 2: [], 3: [], 4: [], 5: []}
for _ in range(1, 3):
my_dict[1] += [50]
print(my_dict) # {1: [50, 50], 2: [], 3: [], 4: [], 5: []}
Как и ожидалось, код отработал корректно, дважды добавив к ключу "1" значение "50". В следующем примере, создаем словарь с использованием dict.fromkeys(arr, []) :
arr = [i for i in range(1, 6)]
my_dict = dict.fromkeys(arr, [])
for _ in range(1, 3):
my_dict[1] += [50]
print(my_dict) # {1: [50, 50], 2: [50, 50], 3: [50, 50], 4: [50, 50], 5: [50, 50]}
Код отработал непредсказуемым образом, дважды добавив значение "50" ко всем ключам в словаре. Кто может объяснить почему так происходит?
Ответы (2 шт):
Рассмотрим на примере.
Вы думаете, что инициализация словаря пройдёт по этому сценарию:
my_dict = {}
for x in range(1, 6):
my_dict[x] = []
print(my_dict) # {1: [], 2: [], 3: [], 4: [], 5: []}
my_dict[1].append("A")
print(my_dict) # {1: ['A'], 2: [], 3: [], 4: [], 5: []}
И вроде всё должно работать как надо.
Но нет. Когда вы определяете словарь вот так dict.fromkeys(arr, []), то список, по сути, создаётся всего раз и ссылка на него идёт во все элементы словаря. Т.е. поведение эквивалентно данному коду:
my_dict = {}
my_list = []
for x in range(1, 6):
my_dict[x] = my_list
print(my_dict) # {1: [], 2: [], 3: [], 4: [], 5: []}
my_dict[1].append("A")
print(my_dict) # {1: ['A'], 2: ['A'], 3: ['A'], 4: ['A'], 5: ['A']}
Казалось бы, нюанс всего лишь в том, что пустой список задаётся через промежуточную переменную... Но зато какая разница в результате!
Поэтому правильным будет создавать каждый элемент со своим пустым списком. Например, через цикл.
my_dict = {x:[] for x in arr}
В дополнение к уже сказанному. Легко ведь проверить, одинаковые элементы-списки лежат в словаре или разные:
...
print(*map(id, my_dict.values()))
Вывод в первом примере:
{1: [50, 50], 2: [], 3: [], 4: [], 5: []}
140422771453616 140422771564048 140422771557632 140422771606224 140422833252864
Вывод во втором примере:
{1: [50, 50], 2: [50, 50], 3: [50, 50], 4: [50, 50], 5: [50, 50]}
140422771613856 140422771613856 140422771613856 140422771613856 140422771613856
Видно, что в первом случае id списков разные, а во втором у всех один id, т.е. это просто один и тот же список.