Как скопировать словарь и редактировать только копию

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

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

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

25 ответов

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

Если вы хотите скопировать диктовку (что редко), вы должны сделать это явно с

dict2 = dict(dict1)

или же

dict2 = dict1.copy()

Когда вы назначаете dict2 = dict1 Вы не делаете копию dict1, это приводит к dict2 быть просто другим именем для dict1,

Чтобы скопировать изменяемые типы, такие как словари, используйте copy / deepcopy из copy модуль.

import copy

dict2 = copy.deepcopy(dict1)
>>> x={'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> u=x.copy()
>>> v=dict(x)
>>> import copy
>>> w=copy.deepcopy(x)
>>> x['a']=10
>>> x
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> u
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> v
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> w
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> x['b']['m']=40
>>> x
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> u
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> v
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> w
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}

Потому что 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.

В python 3.5+ существует более простой способ получить мелкую копию с помощью оператора ** распаковки **. Определено Пепом 448.

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

** распаковывает словарь в новый словарь, который затем назначается dict2.

Мы также можем подтвердить, что каждый словарь имеет отдельный идентификатор.

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

Если необходима глубокая копия, то метод copy.deepcopy() по-прежнему остается верным.

Лучший и самый простой способ создать копию dict в Python 2.7 и 3 - это...

Чтобы создать копию простого (одноуровневого) словаря:

1. Использование метода dict() вместо генерации ссылки, которая указывает на существующий dict.

my_dict1 = dict()
my_dict1["message"] = "Hello Python"
print(my_dict1)  # {'message':'Hello Python'}

my_dict2 = dict(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

2. Использование встроенного метода update() в словаре Python.

my_dict2 = dict()
my_dict2.update(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

Чтобы создать копию вложенного или сложного словаря:

Используйте встроенный модуль копирования, который обеспечивает общие операции мелкого и глубокого копирования. Этот модуль присутствует в Python 2.7 и 3.*

import copy

my_dict2 = copy.deepcopy(my_dict1)

Вы также можете просто сделать новый словарь с пониманием словаря. Это позволяет избежать импорта копии.

dout = dict((k,v) for k,v in mydict.items())

Конечно, в python >= 2.7 вы можете сделать:

dout = {k:v for k,v in mydict.items()}

Но для обратного сравнения лучший метод лучше.

В дополнение к другим предоставленным решениям вы можете использовать ** интегрировать словарь в пустой словарь, например,

shallow_copy_of_other_dict = {**other_dict},

Теперь у вас будет "мелкая" копия other_dict,

Применительно к вашему примеру:

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

Указатель: разница между мелкими и глубокими копиями

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

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

если вы хотите скопировать диктовку, вы можете использовать copy module, Модуль копирования имеет два интерфейса:

copy.copy(x)
Return a shallow copy of x.

copy.deepcopy(x)
Return a deep copy of x.

Разница между мелким и глубоким копированием относится только к составным объектам (объектам, которые содержат другие объекты, такие как списки или экземпляры классов):

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

Глубокая копия создает новый составной объект, а затем рекурсивно вставляет в него копии объектов, найденных в оригинале.

Например, в Python 2.7.9:

>>> import copy
>>> a = [1,2,3,4,['a', 'b']]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> a.append(5)
>>> a[4].append('c')

и результат:

>>> a
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> b
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> c
[1, 2, 3, 4, ['a', 'b', 'c']]
>>> d
[1, 2, 3, 4, ['a', 'b']]

Вы можете копировать и редактировать вновь созданную копию за один раз, вызвав dict конструктор с дополнительными ключевыми аргументами:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}

Поначалу меня это тоже смутило, потому что я происходил из языка Си.

В Си переменная - это место в памяти с определенным типом. Присвоение переменной копирует данные в ячейку памяти переменной.

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

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)

Каждая переменная в 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 много - обычно плохая практика писать код, который нуждается в этом (на мой взгляд).

>>> 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)

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

Просто для записей. Я использую следующий код, который на диктовках, который использует синтаксис JSON более чем в 3 раза быстрее, чем Deepcopy

def CopyDict(dSrc):
    try:
        return json.loads(json.dumps(dSrc))
    except Exception as e:
        Logger.warning("Can't copy dict the preferred way:"+str(dSrc))
        return deepcopy(dSrc)

Как объяснили другие, встроенный 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 имеет смысл.

для вложенных словарей не используйтеdict(srcData) or srcData.copy() or {**srcData}потому что, если вы измените второй уровень и более, он также изменит исходный словарь

      srcData = {
  'first': {
    'second': 'second Value'
  }
}
newData = dict(srcData) # srcData.copy() or {**srcData}
newData['first']['second'] = 'new Second Value'

print(srcData)
print(newData)

# it will print
# srcData: {'first': {'second': 'new Second Value'}}
# newData:{'first': {'second': 'new Second Value'}}

# but it should be
# srcData: {'first': {'second': 'second Value'}}
# newData:{'first': {'second': 'new Second Value'}}

другой вариант для глубокого копирования используетjsonтрюк, как JavascriptJSON.parse(JSON.stringify(obj))

      import json

srcData = {'first': {'second': 'second Value'}}
newData = json.loads(json.dumps(srcData))
newData['first']['second'] = 'new Second Value'

print(srcData)
print(newData)

# srcData: {'first': {'second': 'second Value'}}
# newData: {'first': {'second': 'new Second Value'}}

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

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

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

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

Я столкнулся со странным поведением при попытке глубоко скопировать свойство словаря класса без присвоения его переменной

new = copy.deepcopy(my_class.a) не работает т.е. изменение new изменяет my_class.a

но если вы это сделаете old = my_class.a а потом new = copy.deepcopy(old) он работает отлично, т.е. модифицирует new не влияет my_class.a

Я не уверен, почему это происходит, но надеюсь, что это поможет сэкономить несколько часов!:)

Если вашdictнабирается какMapping, ты не можешь.copy()это, но ты можешь

      dict2 = dict1 | {}

Это немного загадочно, и я не могу говорить о производительности по сравнению сcopy.copy(dict1), но очень лаконично.

Копирование с использованием цикла for:

orig = {"X2": 674.5, "X3": 245.0}

copy = {}
for key in orig:
    copy[key] = orig[key]

print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 674.5, 'X3': 245.0}
copy["X2"] = 808
print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 808, 'X3': 245.0}

Другой более чистый способ - использовать json. см. ниже код

      >>> a = [{"name":"Onkar","Address": {"state":"MH","country":"India","innerAddress":{"city":"Pune"}}}]
>>> b = json.dumps(a)
>>> b = json.loads(b)
>>> id(a)
2334461105416
>>> id(b)
2334461105224
>>> a[0]["Address"]["innerAddress"]["city"]="Nagpur"
>>> a
[{'name': 'Onkar', 'Address': {'state': 'MH', 'country': 'India', 'innerAddress': {'city': 'Nagpur'}}}]
>>> b
[{'name': 'Onkar', 'Address': {'state': 'MH', 'country': 'India', 'innerAddress': {'city': 'Pune'}}}]
>>> id(a[0]["Address"]["innerAddress"])
2334460618376
>>> id(b[0]["Address"]["innerAddress"])
2334424569880

Чтобы создать другой словарь, выполните json.dumps(), а затем json.loads () для того же объекта словаря. У вас будет отдельный объект dict.

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

dict2 = eval(repr(dict1))

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

Это работает для любого типа объекта.

Другие вопросы по тегам