Баг со словарем в 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 шт):

Автор решения: GrAnd

Рассмотрим на примере.
Вы думаете, что инициализация словаря пройдёт по этому сценарию:

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} 
→ Ссылка
Автор решения: CrazyElf

В дополнение к уже сказанному. Легко ведь проверить, одинаковые элементы-списки лежат в словаре или разные:

...
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, т.е. это просто один и тот же список.

→ Ссылка