ссылка на __init__.__kwdefaults__ против явного написания словаря

Python 3, новый для кодирования и Python. я создал словарь класса со значениями по умолчанию, затем попытался создать вложенный словарь на основе этого словаря класса и столкнулся с неожиданным поведением:

class User:

    def __init__(self, *, name=None, age=None, hobbies=[]):
        self.name = name
        self.age = age
        self.hobbies = hobbies

counter = 0
class_dict = {}

# building the nested dicts with default values
for num in range(0, 3):
    """
    1. referencing "User.__init__.__kwdefaults__"
    vs writting the actual dict directly into the nested dict
    2. {"name": None, "age": None, "hobbies": []}
    """
    class_dict.update({f"key_{num}": User.__init__.__kwdefaults__})
    # class_dict.update({f"key_{num}": {"name": None, "age": None, "hobbies": []}})
print("Blue print: " + str(class_dict))


# updating values in the nested dicts
for x in range(0, 3):   # simplified loop
    dict_key = "key_" + str(counter)
    try:
        if 1 < 2:   # simplified if check
            class_dict[dict_key]["name"] = "updated" + str(counter)
            print("inside loop: " + str(class_dict))
            counter = counter + 1
    except:
        continue

print("<<< final result: " + str(class_dict) + ">>>")  # end-result
  1. версия User.init.kwdefaults обновит правильные вложенные ключи dicts внутри цикла, как и ожидалось, но в конечном результате все 3 вложенных ключа имени dicts сохранят update2 как значение. то, что когда-либо изменялось в последней итерации цикла, изменяется во всех вложенных словарях.

  2. фактическая версия dict {name: None, age: None, hobbies: []} также обновляет правильные вложенные ключи dict внутри цикла, как и ожидалось. однако конечный результат здесь для ключа имени во вложенном dict 1 сохраняет значение updated0, во вложенном dict 2 updated1 и во вложенном 2 updated2.

конечный результат 2. - это то, к чему я стремился, и мне потребовалось некоторое время, чтобы найти проблему. я не понимаю, почему обе версии ведут себя одинаково внутри цикла, но имеют разные конечные результаты. есть ли метод dunder/magic для ссылки на словарь классов и получения версии 2. в качестве конечного результата?


person theoka    schedule 03.09.2020    source источник
comment
Обратите внимание, что существует только один единственный объект User.__init__.__kwdefaults__. Вы просто продолжаете добавлять этот один объект снова и снова.   -  person MisterMiyagi    schedule 03.09.2020
comment
Обратите внимание, что поскольку User.__init__.__kwdefaults__ использует значения по умолчанию User.__init__, это запутанный случай изменяемых значений по умолчанию для функций.   -  person MisterMiyagi    schedule 03.09.2020
comment
Отвечает ли это на ваш вопрос? "Наименьшее удивление" и изменяемый аргумент по умолчанию   -  person MisterMiyagi    schedule 03.09.2020
comment
Отвечает ли это на ваш вопрос? Создать список словарей Python   -  person MisterMiyagi    schedule 03.09.2020


Ответы (2)


Проблема в том, что вы снова и снова назначаете один и тот же подраздел для каждой клавиши, что вы можете проверить, запустив это после своего кода:

for x in range(0, 2):   # simplified loop
    dict_key = "key_" + str(x)
    dict_key2 = "key_" + str(x+1)
    print(f'subdict of key {x} is subdict of key {x+1}: {class_dict[dict_key] is class_dict[dict_key2]}')

Результат:

subdict of key 0 is subdict of key 1: True
subdict of key 1 is subdict of key 2: True

Решением будет использование глубоких копий как:

import copy

class User:

    def __init__(self, *, name=None, age=None, hobbies=[]):
        self.name = name
        self.age = age
        self.hobbies = hobbies

counter = 0
class_dict = {}

# building the nested dicts with default values
for num in range(0, 3):
    """
    1. referencing "User.__init__.__kwdefaults__"
    vs writting the actual dict directly into the nested dict
    2. {"name": None, "age": None, "hobbies": []}
    """
    class_dict.update({f"key_{num}": copy.deepcopy(User.__init__.__kwdefaults__)})
    # class_dict.update({f"key_{num}": {"name": None, "age": None, "hobbies": []}})
print("Blue print: " + str(class_dict))
person deponovo    schedule 03.09.2020

Сведенный к минимальному примеру, ваш код сводится к следующему:

d = {'name':None}

dicts = {1: d, 2:d}

print(dicts)
# {1: {'name': None}, 2: {'name': None}}

dicts[1]['name'] = 1
dicts[2]['name'] = 2

print(dicts)
# {1: {'name': 2}, 2: {'name': 2}}

что неудивительно, так как dicts[1] и dicts[2] это один и тот же dict d.

print(d)
# {'name': 2}

Обратите внимание, что если вы вызвали __init__, создав User(...), чего вы никогда не делаете в коде своего вопроса, вы встретите Least Astonishment" и проблема изменяемого аргумента по умолчанию.

person Thierry Lathuille    schedule 03.09.2020