วิธีโคลนหรือคัดลอกรายการ


2547

ตัวเลือกในการโคลนหรือคัดลอกรายการใน Python มีอะไรบ้าง

ในขณะที่ใช้new_list = my_listการปรับเปลี่ยนใด ๆ ที่จะnew_listเปลี่ยนแปลงmy_listทุกครั้ง ทำไมนี้

คำตอบ:


3327

ด้วยnew_list = my_listคุณไม่มีสองรายการจริง ๆ การมอบหมายเพียงคัดลอกการอ้างอิงไปยังรายการไม่ใช่รายการจริงดังนั้นทั้งสองnew_listและmy_listอ้างอิงถึงรายการเดียวกันหลังจากการกำหนด

ในการคัดลอกรายการคุณมีความเป็นไปได้หลายอย่าง:

  • คุณสามารถใช้list.copy()วิธีการbuiltin (มีให้ตั้งแต่ Python 3.3):

    new_list = old_list.copy()
  • คุณสามารถฝานมัน:

    new_list = old_list[:]

    อเล็กซ์เทลของความคิดเห็น (อย่างน้อยกลับในปี 2007 ) เกี่ยวกับเรื่องนี้ก็คือว่ามันเป็นไวยากรณ์ที่แปลกและก็ไม่ได้ทำให้ความรู้สึกที่จะใช้มันเคย ;) (ในความเห็นของเขาอันถัดไปสามารถอ่านได้มากขึ้น)

  • คุณสามารถใช้list()ฟังก์ชั่นในตัว:

    new_list = list(old_list)
  • คุณสามารถใช้งานทั่วไปcopy.copy():

    import copy
    new_list = copy.copy(old_list)
    

    นี่ช้ากว่าเล็กน้อยlist()เพราะต้องค้นหาประเภทข้อมูลold_listก่อน

  • หากรายการมีวัตถุและคุณต้องการที่จะคัดลอกเช่นกันใช้ทั่วไปcopy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

    เห็นได้ชัดว่าเป็นวิธีที่ช้าที่สุดและต้องการหน่วยความจำมากที่สุด แต่บางครั้งก็หลีกเลี่ยงไม่ได้

ตัวอย่าง:

import copy

class Foo(object):
    def __init__(self, val):
         self.val = val

    def __repr__(self):
        return 'Foo({!r})'.format(self.val)

foo = Foo(1)

a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)

# edit orignal list and instance 
a.append('baz')
foo.val = 5

print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
      % (a, b, c, d, e, f))

ผลลัพธ์:

original: ['foo', Foo(5), 'baz']
list.copy(): ['foo', Foo(5)]
slice: ['foo', Foo(5)]
list(): ['foo', Foo(5)]
copy: ['foo', Foo(5)]
deepcopy: ['foo', Foo(1)]

7
ถ้าฉันไม่เข้าใจผิด: newlist = [*mylist]ก็เป็นไปได้ที่ Python 3 newlist = list(mylist)อาจจะชัดเจนกว่านี้
Stéphane

9
ความเป็นไปได้อีกอย่างคือ new_list = old_list * 1
aris

4
วิธีใดของวิธีการเหล่านี้คือการทำสำเนาแบบตื้นและวิธีใดที่เป็นการคัดลอกแบบลึก
Eswar

4
@Eswar: ทั้งหมด แต่คนสุดท้ายทำสำเนาตื้น
Felix Kling

3
@Eswar มันเป็นสำเนาตื้น ๆ
juanpa.arrivillaga

602

เฟลิกซ์ให้คำตอบที่ยอดเยี่ยมแล้ว แต่ฉันคิดว่าฉันจะทำการเปรียบเทียบความเร็วของวิธีการต่าง ๆ :

  1. 10.59 วินาที (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 วินาที (101.6us / itn) - Copy()วิธีไพ ธ อนแท้ๆคัดลอกคลาสที่มี Deepcopy
  3. 1.488 วินาที (14.88us / itn) - Copy()วิธีpython แท้ไม่คัดลอกคลาส (เฉพาะ dicts / list / tuples)
  4. 0.325 วินาที (3.25us / itn) - for item in old_list: new_list.append(item)
  5. 0.217 วินาที (2.17us / itn) - [i for i in old_list](ความเข้าใจในรายการ )
  6. 0.186 วินาที (1.86us / itn) - copy.copy(old_list)
  7. 0.075 วินาที (0.75us / itn) - list(old_list)
  8. 0.053 วินาที (0.53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0.039 วินาที (0.39us / itn) - old_list[:](การแบ่งส่วนรายการ )

ดังนั้นที่เร็วที่สุดคือการแบ่งรายชื่อ แต่ทราบว่าcopy.copy(), list[:]และlist(list)แตกต่างจากcopy.deepcopy()รุ่นหลามไม่ได้คัดลอกรายชื่อใด ๆ , พจนานุกรมและอินสแตนซ์ชั้นในรายการดังนั้นหากต้นฉบับเปลี่ยนพวกเขาจะมีการเปลี่ยนแปลงในรายการคัดลอกเกินไปและในทางกลับกัน

(นี่คือสคริปต์ถ้าใครสนใจหรือต้องการที่จะเพิ่มปัญหาใด ๆ :)

from copy import deepcopy

class old_class:
    def __init__(self):
        self.blah = 'blah'

class new_class(object):
    def __init__(self):
        self.blah = 'blah'

dignore = {str: None, unicode: None, int: None, type(None): None}

def Copy(obj, use_deepcopy=True):
    t = type(obj)

    if t in (list, tuple):
        if t == tuple:
            # Convert to a list if a tuple to 
            # allow assigning to when copying
            is_tuple = True
            obj = list(obj)
        else: 
            # Otherwise just do a quick slice copy
            obj = obj[:]
            is_tuple = False

        # Copy each item recursively
        for x in xrange(len(obj)):
            if type(obj[x]) in dignore:
                continue
            obj[x] = Copy(obj[x], use_deepcopy)

        if is_tuple: 
            # Convert back into a tuple again
            obj = tuple(obj)

    elif t == dict: 
        # Use the fast shallow dict copy() method and copy any 
        # values which aren't immutable (like lists, dicts etc)
        obj = obj.copy()
        for k in obj:
            if type(obj[k]) in dignore:
                continue
            obj[k] = Copy(obj[k], use_deepcopy)

    elif t in dignore: 
        # Numeric or string/unicode? 
        # It's immutable, so ignore it!
        pass 

    elif use_deepcopy: 
        obj = deepcopy(obj)
    return obj

if __name__ == '__main__':
    import copy
    from time import time

    num_times = 100000
    L = [None, 'blah', 1, 543.4532, 
         ['foo'], ('bar',), {'blah': 'blah'},
         old_class(), new_class()]

    t = time()
    for i in xrange(num_times):
        Copy(L)
    print 'Custom Copy:', time()-t

    t = time()
    for i in xrange(num_times):
        Copy(L, use_deepcopy=False)
    print 'Custom Copy Only Copying Lists/Tuples/Dicts (no classes):', time()-t

    t = time()
    for i in xrange(num_times):
        copy.copy(L)
    print 'copy.copy:', time()-t

    t = time()
    for i in xrange(num_times):
        copy.deepcopy(L)
    print 'copy.deepcopy:', time()-t

    t = time()
    for i in xrange(num_times):
        L[:]
    print 'list slicing [:]:', time()-t

    t = time()
    for i in xrange(num_times):
        list(L)
    print 'list(L):', time()-t

    t = time()
    for i in xrange(num_times):
        [i for i in L]
    print 'list expression(L):', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(L)
    print 'list extend:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        for y in L:
            a.append(y)
    print 'list append:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(i for i in L)
    print 'generator expression extend:', time()-t

9
เนื่องจากคุณกำลังทำการเปรียบเทียบอาจเป็นประโยชน์ที่จะรวมจุดอ้างอิง ตัวเลขเหล่านี้ยังคงมีความถูกต้องในปี 2560 ด้วยการใช้ Python 3.6 กับรหัสที่รวบรวมอย่างสมบูรณ์หรือไม่? ฉันกำลังสังเกตคำตอบด้านล่าง ( stackoverflow.com/a/17810305/26219 ) ตั้งคำถามกับคำตอบนี้แล้ว
Mark Edington

4
ใช้timeitโมดูล นอกจากนี้คุณไม่สามารถสรุปได้มากจากเกณฑ์มาตรฐานไมโครโดยพลการเช่นนี้
Corey Goldberg

3
หากคุณต้องการรวมตัวเลือกใหม่สำหรับ 3.5+ [*old_list]ควรจะเท่ากับคร่าวๆlist(old_list)แต่เนื่องจากเป็นไวยากรณ์ไม่ใช่เส้นทางการเรียกใช้ฟังก์ชันทั่วไปจึงช่วยประหยัดเวลาใช้งานได้เล็กน้อย (ซึ่งแตกต่างจากold_list[:]ประเภทที่ไม่แปลง[*old_list]ทำงานบน iterable ใด ๆ และผลิต a list)
ShadowRanger

3
@CoreyGoldberg เพื่อหาเกณฑ์มาตรฐานขนาดเล็ก (ใช้timeitงาน 50 เมตรแทนที่จะเป็น 100k) ดูstackoverflow.com/a/43220129/3745896
แม่น้ำ

1
@ShadowRanger [*old_list]ดูเหมือนจะมีประสิทธิภาพสูงกว่าวิธีอื่นเกือบทั้งหมด (ดูคำตอบของฉันเชื่อมโยงในการแสดงความคิดเห็นก่อนหน้า)
แม่น้ำ

150

ฉันได้รับแจ้งว่า Python 3.3+ เพิ่มlist.copy()วิธีการซึ่งควรจะเร็วพอ ๆ กับการแบ่งส่วน:

newlist = old_list.copy()


6
ใช่และตามเอกสารdocs.python.org/3/library/stdtypes.html#mutable-sequence-types , s.copy()สร้างสำเนาตื้นs(เหมือนs[:])
CyberMew

อันที่จริงมันก็ดูเหมือนว่าในขณะนี้python3.8, .copy()จะเร็วขึ้นเล็กน้อยกว่าหั่น ดูด้านล่าง @AaronsHall ตอบ
รัก

125

ตัวเลือกในการโคลนหรือคัดลอกรายการใน Python มีอะไรบ้าง

ใน Python 3 สามารถทำสำเนาตื้นได้ด้วย:

a_copy = a_list.copy()

ใน Python 2 และ 3 คุณสามารถรับสำเนาตื้น ๆ พร้อมกับส่วนเต็มของต้นฉบับ:

a_copy = a_list[:]

คำอธิบาย

การคัดลอกรายการมีสองวิธีความหมาย สำเนาตื้นสร้างรายการใหม่ของวัตถุเดียวกันสำเนาลึกสร้างรายการใหม่ที่มีวัตถุเทียบเท่าใหม่

คัดลอกรายการตื้น

สำเนาตื้นคัดลอกรายการตัวเองเท่านั้นซึ่งเป็นที่เก็บของการอ้างอิงไปยังวัตถุในรายการ หากวัตถุที่มีอยู่นั้นไม่แน่นอนและมีการเปลี่ยนแปลงการเปลี่ยนแปลงจะมีผลในรายการทั้งสอง

มีวิธีที่แตกต่างในการทำเช่นนี้ใน Python 2 และ 3 วิธี Python 2 จะทำงานใน Python 3 ด้วย

Python 2

ใน Python 2 วิธีการทำสำเนาแบบตื้นของรายการนั้นมีทั้งส่วนที่เป็นต้นฉบับ:

a_copy = a_list[:]

คุณสามารถทำสิ่งเดียวกันให้สำเร็จโดยการส่งรายการผ่านตัวสร้างรายการ

a_copy = list(a_list)

แต่การใช้ตัวสร้างมีประสิทธิภาพน้อยกว่า:

>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844

Python 3

ใน Python 3 ลิสต์รับlist.copyเมธอด:

a_copy = a_list.copy()

ใน Python 3.5:

>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125

การสร้างตัวชี้อื่นไม่ได้คัดลอก

ใช้ new_list = my_list จากนั้นแก้ไข new_list ทุกครั้งที่ my_list เปลี่ยนแปลง ทำไมนี้

my_listเป็นเพียงชื่อที่ชี้ไปยังรายการจริงในหน่วยความจำ เมื่อคุณบอกว่าnew_list = my_listคุณไม่ได้ทำสำเนาคุณเพียงแค่เพิ่มชื่ออื่นที่ชี้ไปยังรายการเดิมในหน่วยความจำ เราสามารถมีปัญหาที่คล้ายกันเมื่อเราทำสำเนาของรายการ

>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]

รายการนี้เป็นเพียงพอยน์เตอร์พอยน์เตอร์ไปยังเนื้อหาดังนั้นการคัดลอกตื้นเพียงแค่คัดลอกพอยน์เตอร์ดังนั้นคุณจึงมีสองรายการที่แตกต่างกัน แต่มีเนื้อหาเดียวกัน ในการทำสำเนาเนื้อหาคุณต้องทำสำเนาลึก

สำเนาลึก

ในการทำสำเนาแบบลึกของรายการใน Python 2 หรือ 3 ให้ใช้deepcopyในcopyโมดูล :

import copy
a_deep_copy = copy.deepcopy(a_list)

เพื่อสาธิตวิธีการนี้ทำให้เราสามารถสร้างรายการย่อยใหม่ได้:

>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]

ดังนั้นเราจึงเห็นว่ารายการที่คัดลอกอย่างลึกซึ้งนั้นเป็นรายการที่แตกต่างไปจากเดิมอย่างสิ้นเชิง คุณสามารถหมุนฟังก์ชั่นของคุณเอง - แต่ทำไม่ได้ คุณมีโอกาสที่จะสร้างบั๊กที่คุณไม่สามารถทำได้โดยใช้ฟังก์ชั่น deepcopy ของไลบรารีมาตรฐาน

อย่าใช้ eval

คุณอาจเห็นสิ่งนี้ใช้เป็นวิธีในการทำสำเนาลึก แต่อย่าทำ:

problematic_deep_copy = eval(repr(a_list))
  1. มันอันตรายโดยเฉพาะถ้าคุณกำลังประเมินบางสิ่งจากแหล่งที่คุณไม่เชื่อถือ
  2. ไม่น่าเชื่อถือหากองค์ประกอบย่อยที่คุณคัดลอกไม่มีตัวแทนที่สามารถ eval'd ในการทำซ้ำองค์ประกอบที่เทียบเท่า
  3. นอกจากนี้ยังมีประสิทธิภาพน้อยกว่า

ใน Python 64 บิต 64:

>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206

บน Python 3.5 บิต 64:

>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644

1
คุณไม่จำเป็นต้องใช้ deepcopy ถ้ารายการนั้นเป็นแบบ 2D หากเป็นรายการของรายการและรายการเหล่านั้นไม่มีรายการอยู่ในรายการคุณสามารถใช้การวนซ้ำได้ ปัจจุบันฉันใช้ list_copy=[] for item in list: list_copy.append(copy(item))และเร็วขึ้นมาก
John Locke

53

มีคำตอบมากมายที่บอกวิธีการทำสำเนาที่ถูกต้อง แต่ไม่มีคำตอบใด ๆ ว่าทำไม 'การคัดลอก' ต้นฉบับของคุณล้มเหลว

Python ไม่เก็บค่าในตัวแปร มันผูกชื่อกับวัตถุ การมอบหมายดั้งเดิมของคุณนำวัตถุที่อ้างถึงโดยmy_listและผูกไว้กับnew_listเช่นกัน ไม่ว่าคุณจะใช้ชื่อยังคงมีเพียงหนึ่งรายการเพื่อให้การเปลี่ยนแปลงที่ทำเมื่อพูดถึงว่ามันเป็นจะยังคงมีอยู่เมื่อพูดถึงว่ามันเป็นmy_list new_listแต่ละคำตอบอื่น ๆ new_listคำถามนี้ให้คุณมีวิธีการที่แตกต่างกันของการสร้างวัตถุใหม่ที่จะผูกกับ

องค์ประกอบของรายการแต่ละรายการทำหน้าที่เหมือนชื่อซึ่งในแต่ละองค์ประกอบจะผูกกับวัตถุ สำเนาตื้นสร้างรายการใหม่ที่มีองค์ประกอบที่ผูกกับวัตถุเดียวกันเหมือนก่อน

new_list = list(my_list)  # or my_list[:], but I prefer this syntax
# is simply a shorter way of:
new_list = [element for element in my_list]

ในการคัดลอกรายการของคุณไปอีกขั้นหนึ่งให้คัดลอกแต่ละวัตถุที่รายการของคุณอ้างถึงและผูกองค์ประกอบเหล่านั้นไปยังรายการใหม่

import copy  
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]

สิ่งนี้ยังไม่ได้คัดลอกอย่างลึกเพราะองค์ประกอบแต่ละรายการอาจอ้างถึงวัตถุอื่น ๆ เช่นเดียวกับรายการที่ถูกผูกไว้กับองค์ประกอบ เมื่อต้องการคัดลอกทุกองค์ประกอบในรายการซ้ำแล้วจึงวัตถุอื่น ๆ ที่อ้างอิงโดยแต่ละองค์ประกอบและอื่น ๆ : ทำสำเนาลึก

import copy
# each element must have __deepcopy__ defined for this...
new_list = copy.deepcopy(my_list)

ดูเอกสารประกอบสำหรับข้อมูลเพิ่มเติมเกี่ยวกับเคสมุมในการคัดลอก



34

เริ่มจากจุดเริ่มต้นและสำรวจคำถามนี้

ดังนั้นสมมติว่าคุณมีสองรายการ:

list_1=['01','98']
list_2=[['01','98']]

และเราต้องคัดลอกรายการทั้งสองตอนนี้เริ่มจากรายการแรก:

ดังนั้นก่อนอื่นลองตั้งค่าตัวแปรcopyเป็นลิสต์ดั้งเดิมของเราlist_1:

copy=list_1

ตอนนี้ถ้าคุณคิดว่าจะคัดลอกรายการ list_1 แสดงว่าคุณคิดผิด idฟังก์ชั่นสามารถแสดงให้เราถ้าสองตัวแปรสามารถชี้ไปที่วัตถุเดียวกัน ลองทำสิ่งนี้:

print(id(copy))
print(id(list_1))

ผลลัพธ์คือ:

4329485320
4329485320

ตัวแปรทั้งสองเป็นอาร์กิวเมนต์เดียวกัน คุณประหลาดใจไหม?

ดังนั้นเมื่อเรารู้ว่าไพ ธ อนไม่ได้เก็บอะไรไว้ในตัวแปรตัวแปรก็แค่อ้างถึงค่าที่เก็บวัตถุและวัตถุ นี่คือวัตถุlistแต่เราสร้างสองการอ้างอิงไปยังวัตถุเดียวกันโดยชื่อตัวแปรที่แตกต่างกันสองรายการ ซึ่งหมายความว่าตัวแปรทั้งสองชี้ไปที่วัตถุเดียวกันโดยมีชื่อแตกต่างกัน

เมื่อคุณทำcopy=list_1มันเป็นจริงทำ:

ป้อนคำอธิบายรูปภาพที่นี่

ที่นี่ในภาพ list_1 และการคัดลอกเป็นชื่อตัวแปรสองตัว แต่วัตถุนั้นเหมือนกันสำหรับทั้งสองตัวแปรซึ่งก็คือ list

ดังนั้นหากคุณพยายามที่จะแก้ไขรายการที่ถูกคัดลอกมันจะแก้ไขรายการเดิมเช่นกันเพราะรายการนั้นมีเพียงรายการเดียวคุณจะแก้ไขรายการนั้นไม่ว่าคุณจะทำจากรายการที่คัดลอกหรือจากรายการเดิม:

copy[0]="modify"

print(copy)
print(list_1)

เอาท์พุท:

['modify', '98']
['modify', '98']

ดังนั้นจึงแก้ไขรายการเดิม:

ทีนี้เรามาดูวิธีการแบบ pythonic สำหรับการคัดลอกรายการ

copy_1=list_1[:]

วิธีนี้แก้ไขปัญหาแรกที่เรามี:

print(id(copy_1))
print(id(list_1))

4338792136
4338791432

ดังที่เราเห็นรายการทั้งสองของเรามีรหัสที่แตกต่างกันและหมายความว่าตัวแปรทั้งสองชี้ไปยังวัตถุต่าง ๆ ดังนั้นสิ่งที่เกิดขึ้นจริงที่นี่คือ:

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้ลองแก้ไขรายการและดูว่าเรายังคงประสบปัญหาก่อนหน้านี้หรือไม่:

copy_1[0]="modify"

print(list_1)
print(copy_1)

ผลลัพธ์คือ:

['01', '98']
['modify', '98']

อย่างที่คุณเห็นมันจะแก้ไขรายการคัดลอกเท่านั้น นั่นหมายความว่ามันใช้งานได้

คุณคิดว่าเราทำเสร็จแล้วหรือ ไม่ลองคัดลอกรายการซ้อนกันของเรา

copy_2=list_2[:]

list_2list_2ควรอ้างอิงกับวัตถุอื่นซึ่งเป็นสำเนา ตรวจสอบกันเลย:

print(id((list_2)),id(copy_2))

เราได้รับผลลัพธ์:

4330403592 4330403528

ตอนนี้เราสามารถสมมติว่าทั้งสองรายการชี้วัตถุที่แตกต่างกันดังนั้นตอนนี้เราลองแก้ไขและดูว่ามันให้สิ่งที่เราต้องการ:

copy_2[0][1]="modify"

print(list_2,copy_2)

สิ่งนี้ทำให้เราได้ผลลัพธ์:

[['01', 'modify']] [['01', 'modify']]

นี่อาจดูสับสนเล็กน้อยเพราะวิธีการเดียวกันกับที่เราเคยใช้งานมาก่อน ลองทำความเข้าใจกับสิ่งนี้

เมื่อคุณทำ:

copy_2=list_2[:]

คุณคัดลอกรายการภายนอกเท่านั้นไม่ใช่รายการภายใน เราสามารถใช้idฟังก์ชั่นอีกครั้งเพื่อตรวจสอบสิ่งนี้

print(id(copy_2[0]))
print(id(list_2[0]))

ผลลัพธ์คือ:

4329485832
4329485832

เมื่อเราทำสิ่งcopy_2=list_2[:]นี้จะเกิดขึ้น:

ป้อนคำอธิบายรูปภาพที่นี่

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

ทางออกคืออะไร? การแก้ปัญหาคือdeepcopyฟังก์ชั่น

from copy import deepcopy
deep=deepcopy(list_2)

ตรวจสอบสิ่งนี้:

print(id((list_2)),id(deep))

4322146056 4322148040

ทั้งสองรายการด้านนอกมีรหัสที่แตกต่างกันลองในรายการด้านในแบบซ้อนกัน

print(id(deep[0]))
print(id(list_2[0]))

ผลลัพธ์คือ:

4322145992
4322145800

เนื่องจากคุณสามารถเห็น ID ทั้งสองแตกต่างกันซึ่งหมายความว่าเราสามารถสรุปได้ว่าทั้งสองรายการซ้อนกันชี้ไปยังวัตถุที่แตกต่างกันในขณะนี้

ซึ่งหมายความว่าเมื่อคุณทำdeep=deepcopy(list_2)สิ่งที่เกิดขึ้นจริง:

ป้อนคำอธิบายรูปภาพที่นี่

ทั้งสองรายการซ้อนกันจะชี้วัตถุที่แตกต่างและพวกเขามีสำเนาของรายการซ้อนกันในขณะนี้

ตอนนี้ลองแก้ไขรายการที่ซ้อนกันและดูว่าจะแก้ไขปัญหาก่อนหน้านี้หรือไม่:

deep[0][1]="modify"
print(list_2,deep)

มันออก:

[['01', '98']] [['01', 'modify']]

อย่างที่คุณเห็นมันไม่ได้แก้ไขรายการซ้อนแบบดั้งเดิม แต่จะแก้ไขเฉพาะรายการที่คัดลอก



33

Python 3.6 การกำหนดเวลา

นี่คือผลการจับเวลาโดยใช้ Python 3.6.8 โปรดทราบว่าช่วงเวลาเหล่านี้สัมพันธ์กันไม่สมบูรณ์

ฉันติดอยู่กับการทำสำเนาตื้นเท่านั้นและยังได้เพิ่มวิธีการใหม่บางอย่างที่ไม่สามารถทำได้ใน Python2 เช่นlist.copy()( เทียบเท่ากับ Python3 slice ) และรูปแบบรายการสองรายการที่เปิดออกมา ( *new_list, = listและnew_list = [*list]):

METHOD                  TIME TAKEN
b = [*a]                2.75180600000021
b = a * 1               3.50215399999990
b = a[:]                3.78278899999986  # Python2 winner (see above)
b = a.copy()            4.20556500000020  # Python3 "slice equivalent" (see above)
b = []; b.extend(a)     4.68069800000012
b = a[0:len(a)]         6.84498999999959
*b, = a                 7.54031799999984
b = list(a)             7.75815899999997
b = [i for i in a]      18.4886440000000
b = copy.copy(a)        18.8254879999999
b = []
for item in a:
  b.append(item)        35.4729199999997

เราสามารถเห็นผู้ชนะ Python2 ทำได้ดี แต่ก็ไม่ได้เปรียบ Python3 list.copy()มากนักโดยเฉพาะอย่างยิ่งเมื่อพิจารณาถึงความสามารถในการอ่านที่เหนือกว่า

ม้ามืดเป็นวิธีการแกะและบรรจุหีบห่อ ( b = [*a]) ซึ่งเร็วกว่าการหั่นแบบดิบประมาณ 25% และเร็วกว่าวิธีการแกะแบบอื่นมากกว่าสองเท่า ( *b, = a)

b = a * 1 ก็ทำได้ดีอย่างน่าประหลาดใจเช่นกัน

โปรดทราบว่าวิธีการเหล่านี้จะไม่ส่งผลผลลัพธ์ที่เทียบเท่าสำหรับอินพุตอื่นนอกเหนือจากรายการ พวกมันใช้งานได้กับวัตถุที่เชือดได้บางส่วนทำงานซ้ำ ๆ ได้ แต่copy.copy()ใช้ได้กับวัตถุ Python ทั่วไปเท่านั้น


นี่คือรหัสทดสอบสำหรับผู้สนใจ ( เทมเพลตจากที่นี่ ):

import timeit

COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'

print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []; for item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a:  b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))

1
สามารถยืนยันเรื่องราวที่คล้ายกันใน 3.8 b=[*a]- วิธีที่ชัดเจนในการทำ;)
SuperShoot

19

ผู้ให้ข้อมูลอื่น ๆ ทั้งหมดให้คำตอบที่ดีซึ่งทำงานเมื่อคุณมีรายการมิติเดียว (ระดับ) อย่างไรก็ตามวิธีการที่กล่าวถึงในตอนนี้copy.deepcopy()ทำงานเพื่อโคลน / คัดลอกรายการเท่านั้นและไม่ได้ชี้ไปที่listวัตถุที่ซ้อนกันเมื่อคุณ ทำงานกับหลายมิติรายการซ้อนกัน (รายการของรายการ) ขณะที่เฟลิกซ์แพรวหมายถึงในคำตอบของเขามีนิด ๆ หน่อย ๆ deepcopyที่จะออกและอาจจะเป็นวิธีแก้ปัญหาที่ใช้ในตัวโปรแกรมเพิ่มเติมที่อาจพิสูจน์ทางเลือกที่เร็วกว่า

ในขณะที่new_list = old_list[:], copy.copy(old_list)'และ Py3k old_list.copy()ทำงานสำหรับรายการเดียวจ่อพวกเขาย้อนกลับไปชี้ที่listวัตถุซ้อนกันภายในold_listและnew_listและการเปลี่ยนแปลงให้เป็นหนึ่งในlistวัตถุที่มีการชุลมุนในอื่น ๆ

แก้ไข: ข้อมูลใหม่ที่นำมาสู่แสงสว่าง

ดังที่Aaron HallและPM 2 ระบุไว้ว่าการ ใช้วงแหวนนั้นไม่ได้เป็นเพียงความคิดที่ไม่ดีเท่านั้น แต่ยังช้ากว่ามากeval()copy.deepcopy()

copy.deepcopy()ซึ่งหมายความว่าสำหรับรายชื่อหลายมิติที่ตัวเลือกเดียวคือ เมื่อกล่าวถึงนี้มันไม่ได้เป็นตัวเลือกเนื่องจากประสิทธิภาพจะลดลงเมื่อคุณพยายามที่จะใช้มันในอาเรย์หลายมิติที่มีขนาดปานกลาง ฉันพยายามtimeitใช้อาเรย์ 42x42 ไม่ใช่ที่ไม่เคยได้ยินมาก่อนหรือแม้กระทั่งแอพพลิเคชั่นด้านชีวสารสนเทศขนาดใหญ่และฉันก็ยอมแพ้ต่อการรอการตอบรับและเริ่มพิมพ์แก้ไขในโพสต์นี้

ดูเหมือนว่าตัวเลือกจริงเท่านั้นจากนั้นคือการเริ่มต้นรายการหลายรายการและทำงานกับพวกเขาอย่างอิสระ หากใครมีข้อเสนอแนะอื่น ๆ สำหรับวิธีการจัดการการคัดลอกรายการหลายมิติก็จะได้รับการชื่นชม

ขณะที่คนอื่น ๆ ได้กล่าวว่ามี ความสำคัญปัญหาประสิทธิภาพการทำงานโดยใช้copyโมดูลและสำหรับรายการหลายมิติcopy.deepcopy


5
สิ่งนี้จะไม่ทำงานเสมอไปเนื่องจากไม่มีการรับประกันว่าสตริงที่ส่งคืนโดยrepr()มีเพียงพอที่จะสร้างวัตถุอีกครั้ง นอกจากนี้ยังeval()เป็นเครื่องมือสุดท้าย เห็นEval อันตรายจริงๆโดยทหารผ่านศึก Ned Batchelder เพื่อดูรายละเอียด ดังนั้นเมื่อคุณสนับสนุนการใช้งานeval()คุณจริงๆควรจะพูดถึงว่ามันอาจเป็นอันตรายได้
PM 2Ring

1
จุดยุติธรรม แม้ว่าฉันคิดว่าประเด็นของ Batchelder ก็คือการมีeval()ฟังก์ชั่นใน Python โดยทั่วไปมีความเสี่ยง มันไม่มากนักไม่ว่าคุณจะใช้ประโยชน์จากฟังก์ชั่นในโค้ดหรือไม่ แต่มันก็เป็นช่องโหว่ด้านความปลอดภัยใน Python ในตัวมันเอง ตัวอย่างของฉันไม่ได้ใช้มันด้วยฟังก์ชั่นที่ได้รับข้อมูลจากinput(), sys.agrvหรือแม้กระทั่งไฟล์ข้อความ มันเป็นไปตามเส้นของการเริ่มต้นรายการหลายมิติที่ว่างเปล่าเพียงครั้งเดียวและจากนั้นก็มีวิธีการคัดลอกในลูปแทนการเริ่มต้นใหม่ในแต่ละการวนซ้ำของลูป
AMR

1
@AaronHall ได้ชี้ให้เห็นว่าอาจมีปัญหาเรื่องประสิทธิภาพที่สำคัญในการใช้new_list = eval(repr(old_list))งานดังนั้นนอกเหนือจากความคิดที่ไม่ดีมันอาจช้าเกินไปที่จะทำงาน
AMR

12

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

คุณสามารถดำเนินการแยกรายการด้วย "ตัวดำเนินการเครื่องหมาย": *ซึ่งจะคัดลอกองค์ประกอบของรายการของคุณ

old_list = [1, 2, 3]

new_list = [*old_list]

new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]

ข้อเสียที่ชัดเจนของวิธีนี้คือมีเฉพาะใน Python 3.5+ เท่านั้น

แม้ว่าเวลาจะฉลาด แต่สิ่งนี้ดูเหมือนจะทำงานได้ดีกว่าวิธีการทั่วไปอื่น ๆ

x = [random.random() for _ in range(1000)]

%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]

%timeit a = [*x]

#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

1
วิธีการนี้ทำงานอย่างไรเมื่อแก้ไขสำเนา
not2qubit

2
@ not2qubit คุณหมายถึงการผนวกหรือแก้ไของค์ประกอบของรายการใหม่ ในตัวอย่างold_listและnew_listเป็นสองรายการที่แตกต่างกันการแก้ไขอย่างใดอย่างหนึ่งจะไม่เปลี่ยนแปลงอีกรายการหนึ่ง (เว้นแต่ว่าคุณกำลังกลายพันธุ์องค์ประกอบเหล่านั้นโดยตรง (เช่นรายการของรายการ) วิธีการเหล่านี้จะไม่มีการคัดลอกแบบลึก)
SCB

7

วิธีการง่ายๆที่ไม่ขึ้นอยู่กับเวอร์ชันของงูหลามนั้นหายไปจากคำตอบที่ได้รับซึ่งคุณสามารถใช้เวลาส่วนใหญ่ (อย่างน้อยฉันก็ทำได้):

new_list = my_list * 1       #Solution 1 when you are not using nested lists

อย่างไรก็ตามหาก my_list มีคอนเทนเนอร์อื่น ๆ (เช่นรายการที่ซ้อนกัน) คุณต้องใช้ deepcopy ตามที่คนอื่นแนะนำในคำตอบข้างต้นจากไลบรารีการคัดลอก ตัวอย่างเช่น:

import copy
new_list = copy.deepcopy(my_list)   #Solution 2 when you are using nested lists

. โบนัส : หากคุณไม่ต้องการคัดลอกองค์ประกอบที่ใช้ (หรือสำเนาตื้น):

new_list = my_list[:]

มาทำความเข้าใจความแตกต่างระหว่าง Solution # 1 กับ Solution # 2

>>> a = range(5)
>>> b = a*1
>>> a,b
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> a[2] = 55 
>>> a,b
([0, 1, 55, 3, 4], [0, 1, 2, 3, 4])

อย่างที่คุณเห็นว่า Solution # 1 ทำงานได้อย่างสมบูรณ์แบบเมื่อเราไม่ได้ใช้รายการซ้อนกัน ตรวจสอบสิ่งที่จะเกิดขึ้นเมื่อเราใช้โซลูชัน # 1 กับรายการที่ซ้อนกัน

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   #Solution#1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       #Solution #2 - DeepCopy worked in nested list

7

โปรดทราบว่ามีบางกรณีที่หากคุณกำหนดคลาสที่คุณกำหนดเองและคุณต้องการคงไว้ซึ่งแอตทริบิวต์คุณควรใช้copy.copy()หรือcopy.deepcopy()ไม่ใช้ทางเลือกตัวอย่างเช่นใน Python 3:

import copy

class MyList(list):
    pass

lst = MyList([1,2,3])

lst.name = 'custom list'

d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}


for k,v in d.items():
    print('lst: {}'.format(k), end=', ')
    try:
        name = v.name
    except AttributeError:
        name = 'NA'
    print('name: {}'.format(name))

ขาออก:

lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list

4
new_list = my_list[:]

new_list = my_list พยายามเข้าใจสิ่งนี้ สมมุติว่า my_list อยู่ในหน่วยความจำฮีปที่ตำแหน่ง X นั่นคือ my_list ชี้ไปที่ X ตอนนี้โดยการกำหนดnew_list = my_listคุณปล่อย new_list ชี้ไปที่ X ซึ่งเรียกว่า Copy ตื้น

ตอนนี้ถ้าคุณกำหนดnew_list = my_list[:]คุณเพียงแค่คัดลอกแต่ละวัตถุของ my_list ไปยัง new_list นี้เรียกว่าสำเนาลึก

วิธีอื่นที่คุณสามารถทำได้คือ:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)

2

ฉันต้องการโพสต์สิ่งที่แตกต่างกันเล็กน้อยแล้วบางคำตอบอื่น ๆ แม้ว่านี่จะไม่ใช่ตัวเลือกที่เข้าใจได้หรือเร็วที่สุด แต่ก็มีมุมมองภายในเกี่ยวกับการทำงานของการทำสำเนาแบบลึกรวมถึงตัวเลือกอื่นสำหรับการทำสำเนาแบบลึก ไม่สำคัญว่าฟังก์ชันของฉันจะมีข้อบกพร่องหรือไม่เนื่องจากจุดประสงค์นี้คือการแสดงวิธีการคัดลอกวัตถุเช่นคำตอบของคำถาม

ที่แกนกลางของฟังก์ชั่นการทำสำเนาลึก ๆ นั้นเป็นวิธีการทำสำเนาแบบตื้น อย่างไร? ง่าย ฟังก์ชั่นการทำสำเนาแบบลึกใด ๆ จะทำซ้ำการบรรจุวัตถุที่ไม่เปลี่ยนรูปเท่านั้น เมื่อคุณคัดลอกรายการที่ซ้อนกันคุณจะทำซ้ำรายการภายนอกเท่านั้นไม่ใช่วัตถุที่เปลี่ยนแปลงได้ภายในรายการ คุณทำซ้ำคอนเทนเนอร์เท่านั้น งานเดียวกันสำหรับคลาสด้วย เมื่อคุณทำคลาสให้ลึกลงไปคุณจะทำการคัดคุณสมบัติที่เปลี่ยนแปลงได้ทั้งหมด ดังนั้นวิธีการที่? ทำไมคุณต้องคัดลอกคอนเทนเนอร์เช่นรายการ dicts, tuples, iters, คลาสและอินสแตนซ์ของคลาส?

มันง่ายมาก ไม่สามารถทำซ้ำวัตถุที่ไม่แน่นอนได้ มันไม่สามารถเปลี่ยนแปลงได้ดังนั้นจึงเป็นเพียงค่าเดียว นั่นหมายความว่าคุณไม่ต้องทำซ้ำสตริง, ตัวเลข, คนโง่หรือคนอื่น ๆ แต่คุณจะทำซ้ำตู้คอนเทนเนอร์อย่างไร ง่าย คุณสร้างค่าเริ่มต้นคอนเทนเนอร์ใหม่ด้วยค่าทั้งหมด Deepcopy อาศัยการเรียกซ้ำ มันทำซ้ำภาชนะทั้งหมดแม้แต่ภาชนะที่มีอยู่ภายในจนกว่าจะไม่มีคอนเทนเนอร์เหลืออยู่ คอนเทนเนอร์เป็นวัตถุที่ไม่เปลี่ยนรูป

เมื่อคุณรู้ว่าการทำซ้ำวัตถุโดยไม่มีการอ้างอิงนั้นเป็นเรื่องง่าย นี่คือฟังก์ชั่นสำหรับการทำสำเนาข้อมูลขั้นพื้นฐานให้ลึกลงไป (จะไม่ทำงานกับคลาสที่กำหนดเอง แต่คุณสามารถเพิ่มได้)

def deepcopy(x):
  immutables = (str, int, bool, float)
  mutables = (list, dict, tuple)
  if isinstance(x, immutables):
    return x
  elif isinstance(x, mutables):
    if isinstance(x, tuple):
      return tuple(deepcopy(list(x)))
    elif isinstance(x, list):
      return [deepcopy(y) for y in x]
    elif isinstance(x, dict):
      values = [deepcopy(y) for y in list(x.values())]
      keys = list(x.keys())
      return dict(zip(keys, values))

ไพ ธ อนในตัวของไพ ธ นั้นมีพื้นฐานมาจากตัวอย่างนั้น ความแตกต่างเพียงอย่างเดียวก็คือมันรองรับประเภทอื่น ๆ และยังสนับสนุนผู้ใช้คลาสโดยการทำซ้ำแอตทริบิวต์ลงในคลาสที่ซ้ำกันใหม่และยังบล็อกการเรียกซ้ำแบบไม่สิ้นสุดโดยอ้างอิงถึงวัตถุที่เห็นอยู่แล้วโดยใช้รายการบันทึกหรือพจนานุกรม และนั่นคือการทำสำเนาที่ลึกล้ำ ที่แกนกลางการทำสำเนาแบบลึกนั้นเป็นเพียงการทำสำเนาแบบตื้น ๆ ฉันหวังว่าคำตอบนี้จะเพิ่มสิ่งที่คำถาม

ตัวอย่าง

สมมติว่าคุณมีรายการนี้: [1, 2, 3] ตัวเลขที่เปลี่ยนไม่ได้ไม่สามารถทำซ้ำได้ แต่เลเยอร์อื่นสามารถ คุณสามารถทำสำเนาได้โดยใช้ list comprehension: [x for x in [1, 2, 3]

ตอนนี้คิดว่าคุณมีรายการนี้: [[1, 2], [3, 4], [5, 6]] เวลานี้คุณต้องการสร้างฟังก์ชั่นซึ่งใช้การเรียกซ้ำเพื่อคัดลอกเลเยอร์ทั้งหมดของรายการ แทนที่จะเข้าใจรายการก่อนหน้า:

[x for x in _list]

มันใช้ใหม่สำหรับรายการ:

[deepcopy_list(x) for x in _list]

และdeepcopy_listมีลักษณะเช่นนี้:

def deepcopy_list(x):
  if isinstance(x, (str, bool, float, int)):
    return x
  else:
    return [deepcopy_list(y) for y in x]

จากนั้นตอนนี้คุณมีฟังก์ชั่นที่สามารถคัดลอกรายชื่อของstrs, bools, floast, intsและแม้กระทั่งรายการไปยังหลาย ๆ เลเยอร์โดยใช้การเรียกซ้ำ และที่นั่นคุณมีมันลึกล้ำ

TLDR : Deepcopy ใช้การเรียกซ้ำเพื่อวัตถุที่ซ้ำกันและเพียงส่งคืนวัตถุที่ไม่เปลี่ยนรูปแบบเช่นเดียวกับเมื่อก่อนเนื่องจากวัตถุที่ไม่เปลี่ยนรูปไม่สามารถทำซ้ำได้ อย่างไรก็ตามมันลึกลงไปในเลเยอร์ภายในสุดของวัตถุที่เปลี่ยนแปลงไม่ได้จนกว่ามันจะไปถึงชั้นที่เปลี่ยนแปลงได้ของวัตถุ


2

มุมมองที่ใช้งานได้จริงเล็กน้อยในการมองเข้าไปในหน่วยความจำผ่าน id และ gc

>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']

>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272) 
     |           |
      -----------

>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
     |           |           |
      -----------------------

>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
     |           |           |
      -----------------------

>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word'])  # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
     |           |
      -----------

>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
     |           |           |
      -----------------------

>>> import gc
>>> gc.get_referrers(a[0]) 
[['hell', 'word'], ['hell', 'word']]  # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None) 

2

จำไว้ว่าใน Python เมื่อคุณ:

    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2 ไม่ได้จัดเก็บรายการจริง แต่เป็นการอ้างอิงถึง list1 ดังนั้นเมื่อคุณทำอะไรกับ list1 list2 ก็จะเปลี่ยนเช่นกัน ใช้โมดูลคัดลอก (ไม่ใช่ค่าเริ่มต้นดาวน์โหลดบน pip) เพื่อทำสำเนาต้นฉบับของรายการ ( copy.copy()สำหรับรายการแบบง่ายสำหรับรายการcopy.deepcopy()ซ้อน) สิ่งนี้ทำให้สำเนาที่ไม่เปลี่ยนแปลงกับรายการแรก


0

ตัวเลือก Deepcopy เป็นวิธีเดียวที่ใช้ได้สำหรับฉัน:

from copy import deepcopy

a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = deepcopy(a)
b[0][1]=[3]
print('Deep:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = a*1
b[0][1]=[3]
print('*1:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ] ]
b = a[:]
b[0][1]=[3]
print('Vector copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = list(a)
b[0][1]=[3]
print('List copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a.copy()
b[0][1]=[3]
print('.copy():')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a
b[0][1]=[3]
print('Shallow:')
print(a)
print(b)
print('-----------------------------')

นำไปสู่ผลลัพธ์ของ:

Deep:
[[[1, 2], [1, 2], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
*1:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Vector copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
List copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
.copy():
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Shallow:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------

0

นี่เป็นเพราะเส้นnew_list = my_listกำหนดการอ้างอิงใหม่ให้กับตัวแปรmy_listซึ่งก็คือnew_list นี้จะคล้ายกับCรหัสที่ระบุด้านล่าง

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

คุณควรใช้โมดูลคัดลอกเพื่อสร้างรายการใหม่โดย

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