ตัวเลือกในการโคลนหรือคัดลอกรายการใน Python มีอะไรบ้าง
ในขณะที่ใช้new_list = my_list
การปรับเปลี่ยนใด ๆ ที่จะnew_list
เปลี่ยนแปลงmy_list
ทุกครั้ง ทำไมนี้
ตัวเลือกในการโคลนหรือคัดลอกรายการใน Python มีอะไรบ้าง
ในขณะที่ใช้new_list = my_list
การปรับเปลี่ยนใด ๆ ที่จะnew_list
เปลี่ยนแปลงmy_list
ทุกครั้ง ทำไมนี้
คำตอบ:
ด้วย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)]
เฟลิกซ์ให้คำตอบที่ยอดเยี่ยมแล้ว แต่ฉันคิดว่าฉันจะทำการเปรียบเทียบความเร็วของวิธีการต่าง ๆ :
copy.deepcopy(old_list)
Copy()
วิธีไพ ธ อนแท้ๆคัดลอกคลาสที่มี DeepcopyCopy()
วิธีpython แท้ไม่คัดลอกคลาส (เฉพาะ dicts / list / tuples)for item in old_list: new_list.append(item)
[i for i in old_list]
(ความเข้าใจในรายการ )copy.copy(old_list)
list(old_list)
new_list = []; new_list.extend(old_list)
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
timeit
โมดูล นอกจากนี้คุณไม่สามารถสรุปได้มากจากเกณฑ์มาตรฐานไมโครโดยพลการเช่นนี้
[*old_list]
ควรจะเท่ากับคร่าวๆlist(old_list)
แต่เนื่องจากเป็นไวยากรณ์ไม่ใช่เส้นทางการเรียกใช้ฟังก์ชันทั่วไปจึงช่วยประหยัดเวลาใช้งานได้เล็กน้อย (ซึ่งแตกต่างจากold_list[:]
ประเภทที่ไม่แปลง[*old_list]
ทำงานบน iterable ใด ๆ และผลิต a list
)
timeit
งาน 50 เมตรแทนที่จะเป็น 100k) ดูstackoverflow.com/a/43220129/3745896
[*old_list]
ดูเหมือนจะมีประสิทธิภาพสูงกว่าวิธีอื่นเกือบทั้งหมด (ดูคำตอบของฉันเชื่อมโยงในการแสดงความคิดเห็นก่อนหน้า)
ฉันได้รับแจ้งว่า Python 3.3+ เพิ่มlist.copy()
วิธีการซึ่งควรจะเร็วพอ ๆ กับการแบ่งส่วน:
newlist = old_list.copy()
s.copy()
สร้างสำเนาตื้นs
(เหมือนs[:]
)
python3.8
, .copy()
จะเร็วขึ้นเล็กน้อยกว่าหั่น ดูด้านล่าง @AaronsHall ตอบ
ตัวเลือกในการโคลนหรือคัดลอกรายการใน Python มีอะไรบ้าง
ใน Python 3 สามารถทำสำเนาตื้นได้ด้วย:
a_copy = a_list.copy()
ใน Python 2 และ 3 คุณสามารถรับสำเนาตื้น ๆ พร้อมกับส่วนเต็มของต้นฉบับ:
a_copy = a_list[:]
การคัดลอกรายการมีสองวิธีความหมาย สำเนาตื้นสร้างรายการใหม่ของวัตถุเดียวกันสำเนาลึกสร้างรายการใหม่ที่มีวัตถุเทียบเท่าใหม่
สำเนาตื้นคัดลอกรายการตัวเองเท่านั้นซึ่งเป็นที่เก็บของการอ้างอิงไปยังวัตถุในรายการ หากวัตถุที่มีอยู่นั้นไม่แน่นอนและมีการเปลี่ยนแปลงการเปลี่ยนแปลงจะมีผลในรายการทั้งสอง
มีวิธีที่แตกต่างในการทำเช่นนี้ใน Python 2 และ 3 วิธี Python 2 จะทำงานใน Python 3 ด้วย
ใน 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 ลิสต์รับ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))
ใน 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
list_copy=[]
for item in list: list_copy.append(copy(item))
และเร็วขึ้นมาก
มีคำตอบมากมายที่บอกวิธีการทำสำเนาที่ถูกต้อง แต่ไม่มีคำตอบใด ๆ ว่าทำไม 'การคัดลอก' ต้นฉบับของคุณล้มเหลว
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)
ดูเอกสารประกอบสำหรับข้อมูลเพิ่มเติมเกี่ยวกับเคสมุมในการคัดลอก
ใช้ thing[:]
>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>>
เริ่มจากจุดเริ่มต้นและสำรวจคำถามนี้
ดังนั้นสมมติว่าคุณมีสองรายการ:
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_2
list_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']]
อย่างที่คุณเห็นมันไม่ได้แก้ไขรายการซ้อนแบบดั้งเดิม แต่จะแก้ไขเฉพาะรายการที่คัดลอก
สำนวนที่ไพ ธ อนทำเช่นนี้คือ newList = oldList[:]
นี่คือผลการจับเวลาโดยใช้ 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))
b=[*a]
- วิธีที่ชัดเจนในการทำ;)
ผู้ให้ข้อมูลอื่น ๆ ทั้งหมดให้คำตอบที่ดีซึ่งทำงานเมื่อคุณมีรายการมิติเดียว (ระดับ) อย่างไรก็ตามวิธีการที่กล่าวถึงในตอนนี้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
repr()
มีเพียงพอที่จะสร้างวัตถุอีกครั้ง นอกจากนี้ยังeval()
เป็นเครื่องมือสุดท้าย เห็นEval อันตรายจริงๆโดยทหารผ่านศึก Ned Batchelder เพื่อดูรายละเอียด ดังนั้นเมื่อคุณสนับสนุนการใช้งานeval()
คุณจริงๆควรจะพูดถึงว่ามันอาจเป็นอันตรายได้
eval()
ฟังก์ชั่นใน Python โดยทั่วไปมีความเสี่ยง มันไม่มากนักไม่ว่าคุณจะใช้ประโยชน์จากฟังก์ชั่นในโค้ดหรือไม่ แต่มันก็เป็นช่องโหว่ด้านความปลอดภัยใน Python ในตัวมันเอง ตัวอย่างของฉันไม่ได้ใช้มันด้วยฟังก์ชั่นที่ได้รับข้อมูลจากinput()
, sys.agrv
หรือแม้กระทั่งไฟล์ข้อความ มันเป็นไปตามเส้นของการเริ่มต้นรายการหลายมิติที่ว่างเปล่าเพียงครั้งเดียวและจากนั้นก็มีวิธีการคัดลอกในลูปแทนการเริ่มต้นใหม่ในแต่ละการวนซ้ำของลูป
new_list = eval(repr(old_list))
งานดังนั้นนอกเหนือจากความคิดที่ไม่ดีมันอาจช้าเกินไปที่จะทำงาน
มันทำให้ฉันประหลาดใจที่เรื่องนี้ยังไม่ได้พูดถึงดังนั้นเพื่อความสมบูรณ์ ...
คุณสามารถดำเนินการแยกรายการด้วย "ตัวดำเนินการเครื่องหมาย": *
ซึ่งจะคัดลอกองค์ประกอบของรายการของคุณ
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)
old_list
และnew_list
เป็นสองรายการที่แตกต่างกันการแก้ไขอย่างใดอย่างหนึ่งจะไม่เปลี่ยนแปลงอีกรายการหนึ่ง (เว้นแต่ว่าคุณกำลังกลายพันธุ์องค์ประกอบเหล่านั้นโดยตรง (เช่นรายการของรายการ) วิธีการเหล่านี้จะไม่มีการคัดลอกแบบลึก)
วิธีการง่ายๆที่ไม่ขึ้นอยู่กับเวอร์ชันของงูหลามนั้นหายไปจากคำตอบที่ได้รับซึ่งคุณสามารถใช้เวลาส่วนใหญ่ (อย่างน้อยฉันก็ทำได้):
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
โปรดทราบว่ามีบางกรณีที่หากคุณกำหนดคลาสที่คุณกำหนดเองและคุณต้องการคงไว้ซึ่งแอตทริบิวต์คุณควรใช้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
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)
ฉันต้องการโพสต์สิ่งที่แตกต่างกันเล็กน้อยแล้วบางคำตอบอื่น ๆ แม้ว่านี่จะไม่ใช่ตัวเลือกที่เข้าใจได้หรือเร็วที่สุด แต่ก็มีมุมมองภายในเกี่ยวกับการทำงานของการทำสำเนาแบบลึกรวมถึงตัวเลือกอื่นสำหรับการทำสำเนาแบบลึก ไม่สำคัญว่าฟังก์ชันของฉันจะมีข้อบกพร่องหรือไม่เนื่องจากจุดประสงค์นี้คือการแสดงวิธีการคัดลอกวัตถุเช่นคำตอบของคำถาม
ที่แกนกลางของฟังก์ชั่นการทำสำเนาลึก ๆ นั้นเป็นวิธีการทำสำเนาแบบตื้น อย่างไร? ง่าย ฟังก์ชั่นการทำสำเนาแบบลึกใด ๆ จะทำซ้ำการบรรจุวัตถุที่ไม่เปลี่ยนรูปเท่านั้น เมื่อคุณคัดลอกรายการที่ซ้อนกันคุณจะทำซ้ำรายการภายนอกเท่านั้นไม่ใช่วัตถุที่เปลี่ยนแปลงได้ภายในรายการ คุณทำซ้ำคอนเทนเนอร์เท่านั้น งานเดียวกันสำหรับคลาสด้วย เมื่อคุณทำคลาสให้ลึกลงไปคุณจะทำการคัดคุณสมบัติที่เปลี่ยนแปลงได้ทั้งหมด ดังนั้นวิธีการที่? ทำไมคุณต้องคัดลอกคอนเทนเนอร์เช่นรายการ 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 ใช้การเรียกซ้ำเพื่อวัตถุที่ซ้ำกันและเพียงส่งคืนวัตถุที่ไม่เปลี่ยนรูปแบบเช่นเดียวกับเมื่อก่อนเนื่องจากวัตถุที่ไม่เปลี่ยนรูปไม่สามารถทำซ้ำได้ อย่างไรก็ตามมันลึกลงไปในเลเยอร์ภายในสุดของวัตถุที่เปลี่ยนแปลงไม่ได้จนกว่ามันจะไปถึงชั้นที่เปลี่ยนแปลงได้ของวัตถุ
มุมมองที่ใช้งานได้จริงเล็กน้อยในการมองเข้าไปในหน่วยความจำผ่าน 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)
จำไว้ว่าใน Python เมื่อคุณ:
list1 = ['apples','bananas','pineapples']
list2 = list1
List2 ไม่ได้จัดเก็บรายการจริง แต่เป็นการอ้างอิงถึง list1 ดังนั้นเมื่อคุณทำอะไรกับ list1 list2 ก็จะเปลี่ยนเช่นกัน ใช้โมดูลคัดลอก (ไม่ใช่ค่าเริ่มต้นดาวน์โหลดบน pip) เพื่อทำสำเนาต้นฉบับของรายการ ( copy.copy()
สำหรับรายการแบบง่ายสำหรับรายการcopy.deepcopy()
ซ้อน) สิ่งนี้ทำให้สำเนาที่ไม่เปลี่ยนแปลงกับรายการแรก
ตัวเลือก 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]]]
-----------------------------
นี่เป็นเพราะเส้น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)
newlist = [*mylist]
ก็เป็นไปได้ที่ Python 3newlist = list(mylist)
อาจจะชัดเจนกว่านี้