Как скопировать словарь и редактировать только копию - PullRequest
667 голосов
/ 18 марта 2010

Может кто-нибудь, пожалуйста, объясните мне это? Это не имеет никакого смысла для меня.

Я копирую словарь в другой и редактирую второй, и оба меняются. Почему это происходит?

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}

Ответы [ 19 ]

7 голосов
/ 18 марта 2010

Каждая переменная в python (например, dict1 или str или __builtins__ является указателем на какой-то скрытый платонический "объект" внутри машины.

Если вы установите dict1 = dict2, вы просто указываете dict1 на тот же объект (или область памяти, или любую другую аналогию), как dict2. Теперь объект, на который ссылается dict1, является тем же объектом, на который ссылается dict2.

Вы можете проверить: dict1 is dict2 должно быть True. Кроме того, id(dict1) должен совпадать с id(dict2).

Вы хотите dict1 = copy(dict2) или dict1 = deepcopy(dict2).

Разница между copy и deepcopy? deepcopy убедится, что элементы dict2 (вы указали его в списке?) Также являются копиями.

Я не использую deepcopy много - обычно плохая практика писать код, который нуждается в этом (по моему мнению).

6 голосов
/ 18 марта 2010

dict2 = dict1 не копирует словарь. Он просто дает программисту второй способ (dict2) ссылаться на тот же словарь.

5 голосов
/ 23 декабря 2016

dict1 - это символ, который ссылается на базовый объект словаря. Присвоение dict1 dict2 просто назначает одну и ту же ссылку. Изменение значения ключа с помощью символа dict2 приводит к изменению базового объекта, что также влияет на dict1. Это сбивает с толку.

Гораздо проще рассуждать о неизменных значениях, чем ссылках, поэтому по возможности делайте копии:

person = {'name': 'Mary', 'age': 25}
one_year_later = {**person, 'age': 26}  # does not mutate person dict

Это синтаксически аналогично:

one_year_later = dict(person, age=26)
3 голосов
/ 30 сентября 2018
>>> dict2 = dict1
# dict2 is bind to the same Dict object which binds to dict1, so if you modify dict2, you will modify the dict1

Есть много способов скопировать объект Dict, я просто использую

dict_1 = {
           'a':1,
           'b':2
         }
dict_2 = {}
dict_2.update(dict_1)
1 голос
/ 17 сентября 2017

Как объяснили другие, встроенный dict не делает то, что вы хотите. Но в Python2 (и, вероятно, тоже 3) вы можете легко создать класс ValueDict, который копирует с =, так что вы можете быть уверены, что оригинал не изменится.

class ValueDict(dict):

    def __ilshift__(self, args):
        result = ValueDict(self)
        if isinstance(args, dict):
            dict.update(result, args)
        else:
            dict.__setitem__(result, *args)
        return result # Pythonic LVALUE modification

    def __irshift__(self, args):
        result = ValueDict(self)
        dict.__delitem__(result, args)
        return result # Pythonic LVALUE modification

    def __setitem__(self, k, v):
        raise AttributeError, \
            "Use \"value_dict<<='%s', ...\" instead of \"d[%s] = ...\"" % (k,k)

    def __delitem__(self, k):
        raise AttributeError, \
            "Use \"value_dict>>='%s'\" instead of \"del d[%s]" % (k,k)

    def update(self, d2):
        raise AttributeError, \
            "Use \"value_dict<<=dict2\" instead of \"value_dict.update(dict2)\""


# test
d = ValueDict()

d <<='apples', 5
d <<='pears', 8
print "d =", d

e = d
e <<='bananas', 1
print "e =", e
print "d =", d

d >>='pears'
print "d =", d
d <<={'blueberries': 2, 'watermelons': 315}
print "d =", d
print "e =", e
print "e['bananas'] =", e['bananas']


# result
d = {'apples': 5, 'pears': 8}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
d = {'apples': 5, 'pears': 8}
d = {'apples': 5}
d = {'watermelons': 315, 'blueberries': 2, 'apples': 5}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
e['bananas'] = 1

# e[0]=3
# would give:
# AttributeError: Use "value_dict<<='0', ..." instead of "d[0] = ..."

Пожалуйста, обратитесь к шаблону модификации lvalue, обсуждаемому здесь: Python 2.7 - чистый синтаксис для модификации lvalue . Ключевое наблюдение заключается в том, что str и int ведут себя как значения в Python (даже если они на самом деле неизменные объекты под капотом). Пока вы наблюдаете это, пожалуйста, также обратите внимание, что в str или int нет ничего особенного. dict можно использовать во многом таким же образом, и я могу вспомнить много случаев, когда ValueDict имеет смысл.

1 голос
/ 29 марта 2019

Хорошие объяснения, я хочу добавить простейшее правило, на которое вы можете ссылаться при рассмотрении переменных Python, которым вы назначаете равные = Если тип данных является неизменяемым, вам не нужно беспокоиться о неожиданном поведении, с которым вы столкнулись. Если тип данных изменчив, вы должны обязательно сделать его копию, чтобы предотвратить непредвиденное поведение, с которым вы столкнулись.

Неизменяемые типы данных : строка (кортеж символов), кортеж

Изменяемые типы данных : список, массив, словарь

1 голос
/ 23 декабря 2016

Поскольку python работает со ссылкой, поэтому, когда вы сделали dict2 = dict1, вы передаете ссылку на dict2, это было то же самое, что и dict1. Таким образом, когда вы вносите изменения в dict1 или dict2, вы меняете ссылку, и оба дикта изменяются. Извините, если я ошибаюсь на английском.

0 голосов
/ 31 июля 2017

потому что, dict2 = dict1, dict2 содержит ссылку на dict1. Оба dict1 и dict2 указывают на одно и то же место в памяти. Это обычный случай при работе с изменяемыми объектами в Python. Когда вы работаете с изменяемыми объектами в Python, вы должны быть осторожны, так как это трудно отладить. Например, следующий пример.

 my_users = {
        'ids':[1,2],
        'blocked_ids':[5,6,7]
 }
 ids = my_users.get('ids')
 ids.extend(my_users.get('blocked_ids')) #all_ids
 print ids#output:[1, 2, 5, 6, 7]
 print my_users #output:{'blocked_ids': [5, 6, 7], 'ids': [1, 2, 5, 6, 7]}

В этом примере предполагается получить все идентификаторы пользователей, включая заблокированные идентификаторы. Это мы получили из переменной ids, но мы также непреднамеренно обновили значение my_users . когда вы расширили идентификаторы с заблокированными my_users были обновлены, поскольку идентификаторы относятся my_users .

0 голосов
/ 02 августа 2017

Вы можете использовать напрямую:

dict2 = eval(repr(dict1))

где объект dict2 является независимой копией dict1, поэтому вы можете изменять dict2, не затрагивая dict1.

Это работает для любых объектов.

...