เข้าใจ dict.copy () - ตื้นหรือลึก?


429

ในขณะที่อ่านเอกสารสำหรับdict.copy()มันบอกว่ามันทำสำเนาตื้นของพจนานุกรม กันไปสำหรับหนังสือที่ฉันกำลังติดตาม (อ้างอิง Python ของ Beazley) ซึ่งกล่าวว่า:

กระบวนการ 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 และปุ่มเป็นสำเนาในขณะที่ dicts ที่ซ้อนกันภายในระดับแรกเป็นการอ้างอิงไม่สามารถลบในลูปตัวอย่างเช่น ดังนั้น 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การทำเช่นนั้นจะทำให้ตัวอย่างง่ายขึ้น
Tom Russell

@ kennytm: ความแตกต่างระหว่างสองตัวอย่างแรกในความเป็นจริงคืออะไร? คุณได้รับผลลัพธ์เดียวกัน แต่มีการนำไปใช้ภายในที่แตกต่างกันเล็กน้อย แต่มันมีความสำคัญอย่างไร?
JavaSa

@TomRussell: หรือใครก็ได้เนื่องจากคำถามนี้ค่อนข้างเก่าคำถามที่ชัดเจนของฉันสำหรับทุกคน
JavaSa

@JavaSa b[1][0] = 5มันเรื่องถ้าพูดที่คุณทำ หากเป็นสำเนาตื้นคุณได้เปลี่ยนแปลงเพียงb a[1][0]
Tom Russell

2
คำอธิบายที่ดี ... ช่วยชีวิตฉันไว้จริงๆ! ขอบคุณ ... สิ่งนี้สามารถนำไปใช้กับรายการ tp, str และประเภทข้อมูลอื่น ๆ ของ python ได้หรือไม่?
Bhuro

38

ไม่ใช่เรื่องของการทำสำเนาแบบลึกหรือแบบตื้น ๆ สิ่งที่คุณกำลังทำอยู่ก็คือการทำสำเนาแบบลึก

ที่นี่:

>>> new = original 

คุณกำลังสร้างการอ้างอิงใหม่ไปยังรายการ / dict ที่อ้างอิงโดยต้นฉบับ

ในขณะที่ที่นี่:

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

คุณกำลังสร้างรายการ / dict ใหม่ซึ่งเต็มไปด้วยสำเนาของการอ้างอิงของวัตถุที่มีอยู่ในภาชนะเดิม


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) ของparent [1] , parent_copy [1]เหมือนกันซึ่งหมายถึง [1,2,3] ของparent [1]และparent_copy [1]เก็บไว้ที่ id 140690938288400

แต่กัญชาของผู้ปกครองและparent_copyจะแตกต่างกันซึ่งหมายถึงพวกเขามีความแตกต่างกันและพจนานุกรมparent_copyเป็นพจนานุกรมใหม่ที่มีค่าการอ้างอิงถึงค่านิยมของผู้ปกครอง


5

"ใหม่" และ "ต้นฉบับ" แตกต่างกันนั่นคือสาเหตุที่คุณสามารถอัปเดตเพียงหนึ่งรายการ .. รายการเหล่านี้คัดลอกมาจากน้ำตื้นไม่ใช่ตัวเลือก


2

เนื้อหาจะถูกคัดลอกตื้น

ดังนั้นหากเดิมdictมีlistหรืออื่นdictionaryปรับเปลี่ยนหนึ่งพวกเขาในต้นฉบับหรือสำเนาตื้นจะปรับเปลี่ยนพวกเขา ( listหรือdict) ในอื่น ๆ


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.