Hiểu dict.copy () - nông hay sâu?


428

Trong khi đọc lên tài liệu cho dict.copy(), nó nói rằng nó làm cho một bản sao cạn của từ điển. Điều tương tự cũng xảy ra với cuốn sách tôi đang theo dõi (Tham khảo Python của Beazley), có nội dung:

Phương thức m.copy () tạo một bản sao nông của các mục có trong một đối tượng ánh xạ và đặt chúng vào một đối tượng ánh xạ mới.

Xem xét điều này:

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

Vì vậy, tôi cho rằng điều này sẽ cập nhật giá trị của original(và thêm 'c': 3) kể từ khi tôi đang thực hiện một bản sao nông. Giống như nếu bạn làm điều đó cho một danh sách:

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

Điều này hoạt động như mong đợi.

Vì cả hai đều là bản sao nông, tại sao dict.copy()nó không hoạt động như tôi mong đợi? Hoặc sự hiểu biết của tôi về sao chép nông và sâu là thiếu sót?


2
Quiff rằng họ không giải thích "nông". Kiến thức trong cuộc, nháy mắt. Chỉ có dict và các khóa là một bản sao trong khi các ký tự lồng nhau bên trong cấp độ đầu tiên đó là các tham chiếu, chẳng hạn có thể bị xóa trong một vòng lặp. Do đó, dict.copy () của Python trong trường hợp đó không hữu ích cũng không trực quan. Cảm ơn câu hỏi của bạn.
gseatussy

Câu trả lời:


988

Bằng cách "sao chép nông", điều đó có nghĩa là nội dung của từ điển không được sao chép theo giá trị, mà chỉ tạo ra một tham chiếu mới.

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

Ngược lại, một bản sao sâu sẽ sao chép tất cả nội dung theo giá trị.

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

Vì thế:

  1. b = a: Phân công tham chiếu, Thực hiện abtrỏ đến cùng một đối tượng.

    Minh họa của 'a = b': 'a' và 'b' đều trỏ đến '{1: L}', 'L' trỏ đến '[1, 2, 3]'.

  2. b = a.copy(): Shallow sao chép, absẽ trở thành hai đối tượng bị cô lập, nhưng nội dung của họ vẫn chia sẻ tài liệu tham khảo cùng

    Minh họa 'b = a.copy ()': 'a' điểm đến '{1: L}', 'b' điểm đến '{1: M}', 'L' và 'M' cả hai điểm đến '[ 1, 2, 3] '.

  3. b = copy.deepcopy(a): Sao chép sâu, abcấu trúc và nội dung của nó trở nên hoàn toàn cô lập.

    Minh họa của 'b = copy.deepcopy (a)': 'a' trỏ đến '{1: L}', 'L' trỏ đến '[1, 2, 3]';  'b' trỏ đến '{1: M}', 'M' trỏ đến một trường hợp khác của '[1, 2, 3]'.


Câu trả lời hay, nhưng bạn có thể cân nhắc sửa lỗi ngữ pháp trong câu đầu tiên. Và không có lý do để không sử dụng Llại b. Làm như vậy sẽ đơn giản hóa ví dụ.
Tom Russell

@kennytm: Sự khác biệt giữa hai ví dụ đầu tiên, trên thực tế là gì? Bạn nhận được cùng một kết quả, nhưng thực hiện bên trong hơi khác nhau, nhưng đối với những gì nó quan trọng?
JavaSa

@TomRussell: Hoặc bất cứ ai, vì câu hỏi này khá cũ, câu hỏi làm rõ của tôi là dành cho tất cả mọi người
JavaSa

@JavaSa Nó quan trọng nếu, nói, bạn làm b[1][0] = 5. Nếu blà một bản sao nông, bạn vừa thay đổi a[1][0].
Tom Russell

2
Giải thích tuyệt vời, ... thực sự đã cứu ngày của tôi! Cảm ơn ... Điều này có thể được áp dụng tương tự tp danh sách, str và các kiểu dữ liệu khác của python không?
Bhuro

38

Đây không phải là vấn đề của bản sao sâu hoặc bản sao nông, không có gì bạn đang làm là bản sao sâu.

Đây:

>>> new = original 

bạn đang tạo một tham chiếu mới cho danh sách / dict được tham chiếu theo bản gốc.

trong khi ở đây:

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

bạn đang tạo một danh sách / dict mới chứa đầy một bản sao các tham chiếu của các đối tượng có trong thùng chứa ban đầu.


31

Lấy ví dụ này:

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

Bây giờ, hãy thay đổi một giá trị ở cấp độ 'nông' (đầu tiên):

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

Bây giờ hãy thay đổi một giá trị sâu hơn một cấp:

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Điều này. Nó thực sự trả lời câu hỏi.
CivilFan

7

Thêm vào câu trả lời của kennytm. Khi bạn thực hiện một bản sao gốc Parent.copy (), một từ điển mới được tạo với cùng các khóa, nhưng các giá trị không được sao chép chúng sẽ được tham chiếu. Nếu bạn thêm một giá trị mới vào Parent_copy, nó sẽ không có hiệu lực cha mẹ bởi vì Parent_copy là một từ điển mới không tham khảo.

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

Giá trị băm (id) của cha mẹ [1] , Parent_copy [1] giống hệt nhau, ngụ ý [1,2,3] của cha mẹ [1]Parent_copy [1] được lưu trữ tại id 140690938288400.

Nhưng hash của cha mẹparent_copy là khác nhau trong đó hàm ý Họ là những từ điển khác nhau và parent_copy là một từ điển mới có giá trị tham chiếu đến giá trị của cha mẹ


5

"mới" và "nguyên bản" là các ký hiệu khác nhau, đó là lý do tại sao bạn có thể cập nhật chỉ một trong số chúng .. Các mục được sao chép nông, không phải bản chính.


2

Nội dung được sao chép nông.

Vì vậy, nếu bản gốc dictchứa một listhoặc cái khác dictionary, sửa đổi chúng trong bản gốc hoặc bản sao nông của nó sẽ sửa đổi chúng (cái listhoặc dict) trong cái khác.


1

Trong phần thứ hai của bạn, bạn nên sử dụng new = original.copy()

.copy=là những thứ khác nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.