Як скопіювати словник і лише редагувати копію


855

Може хтось, будь ласка, пояснить це мені? Це не має для мене сенсу.

Я копіюю словник в інший і редагую другий, і обидва змінюються. Чому це відбувається?

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

4
PythonTutor чудово підходить для візуалізації посилань на Python. Ось цей код на останньому кроці . Ви можете бачити dict1і dict2вказувати на той самий дікт.
wjandrea

Відповіді:


883

Python ніколи неявно не копіює об'єкти. Коли ви встановлюєте dict2 = dict1, ви змушуєте їх посилатися на той самий точний дікт-об'єкт, тому коли ви мутуєте його, усі посилання на нього продовжують посилатися на об'єкт у його поточному стані.

Якщо ви хочете скопіювати дікт (що є рідкісним), ви повинні це зробити прямо

dict2 = dict(dict1)

або

dict2 = dict1.copy()

26
Можливо, краще сказати "dict2 та dict1 вказують на один і той же словник", ви не змінюєте dict1 чи dict2, але те, на що вони вказують.
GrayWizardx

275
Також зауважте, що dict.copy () дрібний, якщо там є вкладений список / тощо, зміни будуть застосовані до обох. ІІРК. Глибока копія цього уникне.
Will

16
Не зовсім коректно, що пітон ніколи неявно не копіює об'єкти. Примітивні типи даних, такі як int, float та bool, також розглядаються як об'єкти (просто зробіть це, dir(1)щоб побачити це), але вони неявно скопіюються.
daniel kullmann

17
@danielkullmann, я думаю, у вас можуть виникнути непорозуміння щодо Python, виходячи з того, як інші мови ви мали справу з роботою. У Python, а) Не існує поняття "примітивні типи даних". int, floatі boolекземпляри - це реальні об'єкти Python; б) об'єкти цих типів не копіюються неявно, коли ви передаєте їх, не на певному рівні семантичного Python і навіть не як деталі реалізації в CPython.
Майк Грехем

39
Обґрунтована риторика на кшталт "Глибока копія вважається шкідливою" є непосильною. За умови рівності, дрібне копіювання складної структури даних значно імовірніше спричинить несподівані проблеми кращого випадку, ніж глибоке копіювання тієї ж структури. Копія, в якій модифікації модифікують вихідний об'єкт, не є копією; це помилка. Ergo, в більшості випадків використовують абсолютно необхідно зателефонувати , copy.deepcopy()а не dict()чи dict.copy(). Imran «s коротку відповідь на правій стороні здорового глузду, в відміну від цієї відповіді.
Сесіль Карі

646

Коли ви призначаєте dict2 = dict1, ви не створюєте копію dict1, це призводить до dict2того, що це просто інша назва dict1.

Для того, щоб скопіювати змінювані типи , як словники, використання copy/ deepcopyв copyмодулі.

import copy

dict2 = copy.deepcopy(dict1)

80
Для будь-якого словника, з яким я коли-небудь працюю, глибоке копіювання - це те, що мені потрібно ... Я просто втратив кілька годин через помилку, яка була через те, що я не отримував повної копії вкладеного словника, і мої зміни вкладених записів впливали на оригінал .
flutefreak7

7
Те ж саме. deepcopy () робить трюк. Я зіпсував мої вкладені дикти всередині обертового кешу, додавши часову позначку до "копії" оригінальної події. Дякую!
fxstein

8
Це насправді має бути позначено як правильну відповідь; Ця відповідь є загальною, вона працює і для словника словників.
орезвані

30
Це має бути прийнятою відповіддю. Необгрунтована риторика «Глибока копія вважається шкідливою», закладена в розділі коментарів поточної прийнятої відповіді, кричуще запрошує проблеми синхронізації при копіюванні вкладених словників (таких, як тут, задокументовані), і їх слід оскаржувати як такі.
Сесіль Карі

Глубока копія - це шлях у випадку складної структури словника. dict1.copy () просто копіює значення ключів як посилання, а не як об'єкти.
Рохіт N

182

Хоча dict.copy()і dict(dict1)створює копію, вони є лише дрібними копіями. Якщо ви хочете глибоку копію, copy.deepcopy(dict1)потрібно. Приклад:

>>> source = {'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> copy1 = x.copy()
>>> copy2 = dict(x)
>>> import copy
>>> copy3 = copy.deepcopy(x)
>>> source['a'] = 10  # a change to first-level properties won't affect copies
>>> source
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy3
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> source['b']['m'] = 40  # a change to deep properties WILL affect shallow copies 'b.m' property
>>> source
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy3  # Deep copy's 'b.m' property is unaffected
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}

Що стосується дрібних та глибоких копій, з документів модуля Pythoncopy :

Різниця між дрібним і глибоким копіюванням стосується лише складних об'єктів (об'єктів, що містять інші об'єкти, наприклад, списки або екземпляри класу):

  • Неглибока копія конструює новий складений об’єкт і потім (наскільки це можливо) вставляє в нього посилання на об'єкти, знайдені в оригіналі.
  • Глибока копія конструює новий складений об'єкт і потім, рекурсивно, вставляє в нього копії об'єктів, знайдених в оригіналі.

2
це має бути правильною відповіддю, оскільки воно не перебуває в прямому переході на дікт і може бути використане для інших первинних структур.
Nikkolasg

27
Просто для уточнення: w=copy.deepcopy(x)це ключовий рядок.
алкоголідей

У чому різниця між dict2 = dict1і dict2 = copy.deepcopy(dict1)?
TheTank

1
@TheTank, y = x змушує два імені (посилання) посилатися на один і той же об'єкт, тобто "y є x" - це "True". Будь-яка зміна, здійснена на об'єкті через x, еквівалентна тій же зміні через y. Однак u, v, w - це посилання на нові різні об'єкти, які мають значення, скопійовані з x під час інстанції. Що стосується відмінностей між u, v (дрібна копія) та w (глибока копія), будь ласка, перевірте docs.python.org/2/library/copy.html
gpanda

63

На 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 () .


3
Це виглядає жахливо, як покажчики на C ++. Приємно для виконання завдання, але з розумною читальністю я схильний не любити цей тип операторів.
Ернесто

1
Він має вигляд c'ish ... але при об'єднанні кількох словників синтаксис виглядає досить гладко.
PabTorre

2
Будьте обережні з цим, він виконує лише дрібну копію.
Себастьян Dressler

ви праві @SebastianDressler, я буду робити коригування. thnx.
PabTorre

2
Корисно, якщо ви хочете створити копію за допомогою деяких dict2 = {**dict1, 'key3':'value3'}
спецій

47

Найкращі і найлегші способи створити копію у вигляді Словника як в Python 2.7 і 3 є ...

Щоб створити копію простого (однорівневого) словника:

1. Використовуючи метод 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)

6
Я вважаю, що dict()створюється неглибока копія, а не глибока копія. Це означає, що якщо у вас вкладений, dictто зовнішній dictбуде копією, але внутрішній дік буде посиланням на початковий внутрішній дікт.
шмуельс

@shmuels так, обидва ці способи створять дрібну, а не глибоку. Дивіться, оновлена ​​відповідь.
AKay Nirala

37

Ви також можете просто скласти новий словник із розумінням словника. Це дозволяє уникнути імпорту копії.

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

Звичайно, в python> = 2.7 ви можете зробити:

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

Але для зворотного співтовариства - найкращий метод.


4
Це особливо корисно, якщо ви хочете більше контролювати, як і що саме копіюється. +1
Наближення

14
Зауважте, що цей метод не виконує глибоку копію, і якщо ви хочете дрібну копію, не потребуючи копіювання ключів для копіювання, d2 = dict.copy(d1)також не потрібно імпорту.
Jarek Piórkowski

1
@ JarekPiórkowski: або ви можете назвати такий метод, як метод:d2 = d1.copy()
Азат Ібраков

Зауважте, що вам не потрібно розуміння в першому прикладі. dict.itemsвже повертає ключ-значення пари, ітерабельного. Тож можна просто використовувати dict(mydict.items())(можна також просто використовувати dict(mydict)). Зрозуміти це може бути корисно, якщо ви хочете відфільтрувати записи.
Пол Руні

22

Крім інших наданих рішень, ви можете використовувати **для інтеграції словника в порожній словник, наприклад,

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'}
>>>

Вказівник: різниця між дрібними та глибокими копіями


1
Це призводить до отримання дрібної, а не глибокої копії.
sytech

1
Я намагався це, але мав проблеми. Це працює лише для python 3.5 і вище. python.org/dev/peps/pep-0448
ThatGuyRob

19

Виписки про призначення в 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']]

10

Ви можете скопіювати та відредагувати щойно створену копію за один раз, зателефонувавши dictконструктору за допомогою додаткових аргументів ключового слова:

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

9

Це спочатку мене також бентежило, бо я походив із С.

В C змінна - це місце в пам'яті з визначеним типом. Призначення змінної копіює дані в пам'ять змінної.

Але в Python змінні діють більше, як вказівники на об’єкти. Таким чином, присвоєння однієї змінної іншій не робить копію, вона просто робить те, що ім'я змінної вказує на той самий об'єкт.


5
змінні python діють більше, як c ++ посилання
Ruggero Turra

7
Тому що все в Python - це об’єкт! diveintopython.net/getting_to_know_python/… (так, ця відповідь запізнюється на багато років, але, можливо, комусь це
приносить користь

1
Я вважаю, що семантика мови Python говорить про відсутність "змінних". Їх називають «названими посиланнями»; що означає посилання на об'єкт - синтаксичний рядок у коді. Об'єкт може мати безліч посилань на нього. У таких об'єктах, як ints, floats та str, екземпляри мають лише один примірник цього процесу. Інт в пам'яті 1 не змінюється на 2 або якесь інше значення за тією ж адресою пам'яті, коли ви робите це myvalue = 1 myvalue = 2
DevPlayer,

7

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


Я щойно зрозумів, що потрібно завжди використовувати глибоку копію, щоб коли я копіював вкладений словник і почав змінювати вкладені записи, ефекти виникають лише на копії, а не на оригіналі.
flutefreak7

6

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)

5

dict2 = dict1не копіює словник. Це просто дає програмісту другий спосіб ( dict2) звернутися до того ж словника.


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

12
dict_2 = dict_1.copy()набагато ефективніше і логічніше.
Жан-Франсуа Фабре

2
Зауважте, що якщо у вас є dict усередині dict1, з dict_1.copy () зміни, які ви робите у внутрішньому dict у dict_2, також застосовуються до внутрішнього dict у dict_1. У цьому випадку слід використовувати замість copy.deepcopy (dict_1).
queise

1

Як пояснили інші, вбудований 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має сенс.


0

наступний код, який знаходиться на диктах, який слідує за синтаксисом 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)

0

Я зіткнувся з особливою поведінкою, намагаючись глибоко скопіювати властивість словника класу без присвоєння йому змінної

new = copy.deepcopy(my_class.a)не працює, тобто модифікує newзміниmy_class.a

але якщо ви це зробите, old = my_class.aто new = copy.deepcopy(old)це прекрасно працює, тобто зміна newне впливаєmy_class.a

Я не впевнений, чому це відбувається, але сподіваюся, що це допоможе зекономити кілька годин! :)


То як зробити глибоку копію my_class.a?
Ентоні

Не найкращий спосіб. Хороша відповідь - нижче.
Девід Бошемін

-1

тому що, 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]}

Цей приклад має намір отримати всі ідентифікатори користувачів, включаючи заблоковані ідентифікатори. Це ми отримали зі змінної id, але ми також ненавмисно оновили значення my_users . коли ви продовжені ідентифікатори з blocked_ids my_users отримав оновлений , оскільки ідентифікатори відносяться до my_users .


-1

Копіювання за допомогою циклу:

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}

1
Це працює лише для простих словників. Чому б не використовувати deepcopy, яка побудована прямо для цієї мети?
Антоній

Не найкращий спосіб. Хороша відповідь - нижче.
Девід Бошемін

-6

Ви можете використовувати безпосередньо:

dict2 = eval(repr(dict1))

де об'єкт dict2 є незалежною копією dict1, тому ви можете змінювати dict2, не впливаючи на dict1.

Це працює для будь-якого виду об’єктів.


4
Ця відповідь невірна і не повинна використовуватися. Наприклад, визначений користувачем клас, може не мати відповідного __repr__реконструкції eval, а також клас об'єкта не може бути в поточному масштабі, який потрібно викликати. Навіть дотримуючись вбудованих типів, це не вдасться, якщо один і той же об’єкт зберігається під декількома клавішами, як dict2і тоді два окремі об’єкти. dict1Натомість буде містити самореференційний словник, де він міститься Ellipsis. Було б краще використовуватиdict1.copy()
сир Eldritch

Не слід очікувати, що об'єкти (або "значення") завжди матимуть вірну репрезентацію за допомогою символьних рядків, а не у звичайному для людини читанні способі.
Олексій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.