Розуміння dict.copy () - дрібне чи глибоке?


429

Під час читання документації на dict.copy()це йдеться про те, що вона робить дрібну копію словника. Те саме стосується книги, яку я переглядаю (Довідник Бізлі Пітона), де сказано:

Метод m.copy () робить дрібну копію елементів, що містяться в об’єкті відображення, і розміщує їх у новому об'єкті відображення.

Врахуйте це:

>>> original = dict(a=1, b=2)
>>> new = original.copy()
>>> new.update({'c': 3})
>>> original
{'a': 1, 'b': 2}
>>> new
{'a': 1, 'c': 3, 'b': 2}

Тому я припускав, що це оновить значення original(і додасть "c": 3) також, оскільки я робив дрібну копію. Як, якщо ви робите це для списку:

>>> original = [1, 2, 3]
>>> new = original
>>> new.append(4)
>>> new, original
([1, 2, 3, 4], [1, 2, 3, 4])

Це працює як очікувалося.

Оскільки обидві є дрібними копіями, чому це dict.copy()не працює так, як я очікую? Або моє розуміння дрібного та глибокого копіювання є помилковим?


2
Чудово, що вони не пояснюють "мілко". Інсайдерські знання, підморгнути. Просто дикт і ключі є копією, тоді як вкладені дикти всередині цього першого рівня є посиланнями, наприклад, не можуть бути видалені в циклі. Таким чином, dict.copy () Python у цьому випадку не є ні корисним, ні інтуїтивно зрозумілим. Дякуємо за запитання
gseattle

Відповіді:


990

Під "дрібним копіюванням" це означає, що зміст словника не копіюється за значенням, а просто створює нову посилання.

>>> a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

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

>>> import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

Тому:

  1. b = a: Довідкове завдання, Зробити aта bвказує на один і той же об'єкт.

    Ілюстрація 'a = b': 'a' і 'b' обидва вказують на '{1: L}', 'L' вказує на '[1, 2, 3]'.

  2. b = a.copy(): Дрібне копіювання aі bстане двома ізольованими об’єктами, але їх вміст все ще має одне і те ж посилання

    Ілюстрація 'b = a.copy ()': 'a' вказує на '{1: L}', 'b' вказує на '{1: M}', 'L' і 'M' вказують на '[ 1, 2, 3] '.

  3. b = copy.deepcopy(a): Глибоке копіювання, aа також bструктура та вміст Росії стають повністю ізольованими.

    Ілюстрація 'b = copy.deepcopy (a)': 'a' вказує на '{1: L}', 'L' вказує на '[1, 2, 3]';  'b' вказує на '{1: M}', 'M' вказує на інший екземпляр '[1, 2, 3]'.


Гарна відповідь, але ви можете розглянути можливість виправлення граматичної помилки у першому реченні. І немає причин не використовувати Lзнову в b. Це може спростити приклад.
Том Расселл

@kennytm: Чим насправді різниця між першими двома прикладами? Ви отримуєте той самий результат, але дещо інший внутрішній втілення, але для чого це важливо?
JavaSa

@TomRussell: Або хтось, оскільки це запитання досить старе, моє
уточнююче

@JavaSa Це важливо, якщо, скажімо, ви це зробите b[1][0] = 5. Якщо bце неглибока копія, ви щойно змінилися a[1][0].
Том Рассел

2
Чудове пояснення, ... дійсно врятував мені день! Дякую ... Чи можна це ж застосувати до списку, str та інших типів даних python?
Бхуро

38

Це не питання глибокої копії або дрібної копії, жодне з того, що ви робите, - це не копіювання.

Тут:

>>> new = original 

ви створюєте нове посилання на список / дікт, на який посилається оригінал.

поки тут:

>>> new = original.copy()
>>> # or
>>> new = list(original) # dict(original)

ви створюєте новий список / дікт, який заповнюється копією посилань на об'єкти, що містяться в оригінальному контейнері.


31

Візьмемо цей приклад:

original = dict(a=1, b=2, c=dict(d=4, e=5))
new = original.copy()

Тепер давайте змінимо значення на рівні "дрібного" (першого) рівня:

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# no change in original, since ['a'] is an immutable integer

Тепер давайте змінимо значення на один рівень глибше:

new['c']['d'] = 40
# new = {'a': 10, 'b': 2, 'c': {'d': 40, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 40, 'e': 5}}
# new['c'] points to the same original['d'] mutable dictionary, so it will be changed

8
no change in original, since ['a'] is an immutable integerЦе. Це фактично відповідає на поставлене питання.
CivFan

8

Додавання до відповіді kennytm. Коли ви робите дрібну копію parent.copy (), новий словник створюється з тими ж ключами, але значення не скопіюються, вони посилаються. Якщо ви додасте нове значення в parent_copy, це не вплине на батьківське, оскільки parent_copy - це новий словник не посилання.

parent = {1: [1,2,3]}
parent_copy = parent.copy()
parent_reference = parent

print id(parent),id(parent_copy),id(parent_reference)
#140690938288400 140690938290536 140690938288400

print id(parent[1]),id(parent_copy[1]),id(parent_reference[1])
#140690938137128 140690938137128 140690938137128

parent_copy[1].append(4)
parent_copy[2] = ['new']

print parent, parent_copy, parent_reference
#{1: [1, 2, 3, 4]} {1: [1, 2, 3, 4], 2: ['new']} {1: [1, 2, 3, 4]}

Значення хеша (id) батьківського [1] , батьківського_копію [1] є ідентичним, що означає [1,2,3] батьківського [1] та батьківського_копію [1], збереженого в id 140690938288400.

Але хеш батьківських і батьківських_копій різний, що означає, що вони різні словники, а батьківська_копія - це новий словник, який має значення посилань на значення батьківських


5

"новий" та "оригінал" - це різні дикти, тому ви можете оновити лише один із них. Елементи є неглибоко скопійованими, а не самим диктом.


2

Зміст копіюється неглибоко.

Отже, якщо оригінал dictмістить те listчи інше dictionary, зміна одного в оригіналі або його неглибока копія модифікує їх (той listчи той dict) в іншому.


1

У другій частині ви повинні використовувати new = original.copy()

.copyі =різні речі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.