Bir liste nasıl kopyalanır veya kopyalanır?


2547

Python'da bir listeyi klonlama veya kopyalama seçenekleri nelerdir?

Kullanırken new_list = my_list, her değişiklik new_listdeğişiklik my_list. Bu neden?

Yanıtlar:


3328

İle new_list = my_list, gerçekte iki listeyi yok. Ödev, referansı gerçek listeye değil, listeye kopyalar, bu nedenle her ikisi de new_listve my_listödevden sonra aynı listeye bakın.

Listeyi gerçekten kopyalamak için çeşitli olasılıklarınız vardır:

  • Yerleşik list.copy()yöntemi kullanabilirsiniz (Python 3.3'ten beri kullanılabilir):

    new_list = old_list.copy()
  • Dilim yapabilirsiniz:

    new_list = old_list[:]

    Alex Martelli'nin (en azından 2007'de ) bu konudaki düşüncesi , garip bir sözdizimi olması ve bunu hiç kullanmanın mantıklı olmamasıdır . ;) (Ona göre, bir sonraki daha okunabilir).

  • Yerleşik işlevi kullanabilirsiniz list():

    new_list = list(old_list)
  • Jenerik kullanabilirsiniz copy.copy():

    import copy
    new_list = copy.copy(old_list)

    Bu, biraz önce daha yavaş list()çünkü old_listilkinin veri tipini bulmak zorunda .

  • Liste nesneler içeriyorsa ve bunları da kopyalamak istiyorsanız genel copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)

    Açıkçası en yavaş ve en çok belleğe ihtiyaç duyan yöntem, ama bazen kaçınılmaz.

Misal:

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

Sonuç:

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
Yanılmıyorsam: newlist = [*mylist]Python 3'te newlist = list(mylist)de bir ihtimal var .
Stéphane

9
başka bir olasılık new_list = old_list * 1
aris

4
Bu yöntemlerden hangisi sığ ve hangileri derin kopyadır?
Eswar

4
@Eswar: sonuncusu hariç hepsi sığ bir kopya yapıyor
Felix Kling

3
@Eswar sığ bir kopya.
juanpa.arrivillaga

602

Felix zaten mükemmel bir cevap verdi, ancak çeşitli yöntemlerin hız karşılaştırmasını yapacağımı düşündüm:

  1. 10,59 saniye (105,9us / itn) - copy.deepcopy(old_list)
  2. 10.16 sn. (101.6us / itn) - Copy()sınıfları deepcopy ile kopyalayan saf python yöntemi
  3. 1.488 sn. (14.88us / itn) - Copy()sınıfları kopyalamamak için saf python yöntemi (yalnızca dikte / listeler / tuples)
  4. 0.325 sn (3.25us / itn) - for item in old_list: new_list.append(item)
  5. 0,217 sn (2,17us / itn) - [i for i in old_list](bir liste anlama )
  6. 0.186 saniye (1.86us / itn) - copy.copy(old_list)
  7. 0,075 sn (0,75us / itn) - list(old_list)
  8. 0,053 sn (0,53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 sn (0,39us / itn) - old_list[:]( liste dilimleme )

En hızlı liste dilimleme. Ama unutmayın copy.copy(), list[:]ve list(list)aksine copy.deepcopy()listesindeki herhangi listeleri, sözlükler ve sınıf örneklerini kopyalamazsanız ve piton versiyonu orijinalleri değiştirirseniz, bu nedenle, onlar da tam tersi kopyalanan listede değişecek.

(İşte ilgilenen veya herhangi bir sorun ortaya çıkarmak isteyen komut dosyası :)

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
Kıyaslama yaptığınızdan, bir referans noktası eklemek yararlı olabilir. Bu rakamlar 2017'de Python 3.6'yı tam derlenmiş kodla kullanmaya devam ediyor mu? Aşağıdaki cevaba dikkat çekiyorum ( stackoverflow.com/a/17810305/26219 ) bu cevabı zaten sorguluyor.
Mark Edington

4
timeitmodülü kullanın . ayrıca, bunun gibi gelişigüzel mikro ölçütlerden çok fazla sonuç çıkaramazsınız.
Corey Goldberg

3
3.5+ için yeni bir seçenek eklemek istiyorsanız, [*old_list]kabaca eşdeğer olmalıdır list(old_list), ancak genel işlev çağrısı yolları değil sözdizimi olduğundan, çalışma zamanında biraz tasarruf sağlar (ve aksine old_list[:]dönüştürme yazmaz, [*old_list]herhangi bir yinelenebilir üzerinde çalışır ve a list) üretir .
ShadowRanger

3
@CoreyGoldberg biraz daha az keyfi bir mikro kıyaslama için ( timeit100k yerine 50m çalışır) bkz. Stackoverflow.com/a/43220129/3745896
Nehir

1
@ShadowRanger [*old_list]aslında hemen hemen tüm diğer yöntemlerden daha iyi performans gösteriyor. (cevabımı önceki yorumlarda bulabilirsiniz)
Nehri


125

Python'da bir listeyi klonlama veya kopyalama seçenekleri nelerdir?

Python 3'te aşağıdakilerle sığ bir kopya yapılabilir:

a_copy = a_list.copy()

Python 2 ve 3'te, orijinalin tam bir dilimi ile sığ bir kopya elde edebilirsiniz:

a_copy = a_list[:]

açıklama

Bir listeyi kopyalamanın iki anlamsal yolu vardır. Sığ bir kopya aynı nesnelerin yeni bir listesini, derin bir kopya ise yeni eşdeğer nesneleri içeren yeni bir liste oluşturur.

Sığ liste kopyası

Sığ bir kopya, yalnızca listedeki nesnelere başvuruların bulunduğu bir kap olan listenin kendisini kopyalar. İçerisindeki nesneler değiştirilebilir ve biri değiştirilirse, değişiklik her iki listeye de yansıtılır.

Bunu Python 2 ve 3'te yapmanın farklı yolları vardır. Python 2 yolları Python 3'te de çalışır.

Python 2

Python 2'de, bir listenin sığ bir kopyasını oluşturmanın deyimsel yolu, orijinalin tam bir dilimi ile:

a_copy = a_list[:]

Aynı şeyi listeyi liste yapıcısından geçirerek de yapabilirsiniz,

a_copy = list(a_list)

ancak yapıcıyı kullanmak daha az verimlidir:

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

Python 3

Python 3'te listeler list.copyyöntemi alır:

a_copy = a_list.copy()

Python 3.5'te:

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

Başka bir işaretçi yapma gelmez değil bir kopyasını

New_list = my_list kullanıldığında listem her değiştiğinde new_list değiştirilir. Bu neden?

my_listyalnızca bellekteki gerçek listeye işaret eden bir addır. Bir new_list = my_listkopya yapmadığınızı söylediğinizde , yalnızca bellekteki orijinal listeye işaret eden başka bir ad eklersiniz. Listelerin kopyalarını çıkarırken benzer sorunlar yaşayabiliriz.

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

Liste, içeriğe yalnızca bir işaretçi dizisidir, bu nedenle sığ bir kopya yalnızca işaretçileri kopyalar ve böylece iki farklı listeniz vardır, ancak aynı içeriğe sahiptirler. İçeriğin kopyalarını oluşturmak için derin bir kopyaya ihtiyacınız vardır.

Derin kopyalar

Bir yapmak için Python 2 veya 3'te bir listenin derin kopyasını kullanmak deepcopyiçinde copymodül :

import copy
a_deep_copy = copy.deepcopy(a_list)

Bunun yeni alt listeler yapmamızı nasıl sağladığını göstermek için:

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

Ve böylece derin kopyalanan listenin orijinalinden tamamen farklı bir liste olduğunu görüyoruz. Kendi işlevini yerine getirebilirsin - ama yapma. Standart kitaplığın derin kopya işlevini kullanarak, aksi takdirde sahip olmayacağınız hatalar oluşturabilirsiniz.

Kullanma eval

Bunun derin kopyalamanın bir yolu olarak kullanıldığını görebilirsiniz, ancak yapmayın:

problematic_deep_copy = eval(repr(a_list))
  1. Bu, özellikle güvenmediğiniz bir kaynaktan gelen bir şeyi değerlendiriyorsanız tehlikelidir.
  2. Kopyaladığınız bir alt öğenin eşdeğer bir öğeyi yeniden oluşturabilecek bir temsili yoksa güvenilir değildir.
  3. Aynı zamanda daha az performans.

64 bit Python 2.7'de:

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

64 bit Python 3.5 üzerinde:

>>> 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
Liste 2B ise bir derin kopyaya ihtiyacınız yoktur. Listelerin bir listesiyse ve bu listelerin içinde listeler yoksa, for döngüsü kullanabilirsiniz. Şu anda kullanıyorum list_copy=[] for item in list: list_copy.append(copy(item))ve çok daha hızlı.
John Locke

53

Size uygun bir kopyayı nasıl oluşturacağınızı söyleyen birçok cevap var, ancak hiçbiri orijinal 'kopyanızın' neden başarısız olduğunu söylemiyor.

Python değerleri değişkenlerde saklamaz; isimleri nesnelere bağlar. Orijinal ödeviniz, atıfta bulunulan nesneyi aldı my_listve ona da bağladı new_list. Hangi adı kullanırsanız kullanın, hala yalnızca bir liste vardır, bu nedenle söz konusu şekilde adlandırılırken yapılan değişiklikler, söz konusu my_listifadeye atıfta bulunulurken devam edecektir new_list. Bu soruya verilen diğer yanıtların her biri, bağlanacak yeni bir nesne oluşturmanın farklı yollarını sunar new_list.

Listenin her öğesi bir ad gibi davranır, çünkü her öğenin yalnızca bir nesneye bağlanmaz. Sığ bir kopya, öğeleri daha önce olduğu gibi aynı nesnelere bağlanan yeni bir liste oluşturur.

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]

Listenizi bir adım daha ileri götürmek için, listenizin referans aldığı her nesneyi kopyalayın ve bu öğe kopyalarını yeni bir listeye bağlayın.

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

Bu henüz derin bir kopya değildir, çünkü listenin her öğesi tıpkı liste öğelerine bağlı olduğu gibi diğer nesnelere başvurabilir. Listedeki her öğeyi özyineli olarak ve sonra her öğenin başvurduğu diğer nesneleri kopyalamak ve bu şekilde devam etmek için: derin bir kopya gerçekleştirin.

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

Kopyalamadaki köşe kutuları hakkında daha fazla bilgi için belgelere bakın .


37

kullanım thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 

34

En baştan başlayalım ve bu soruyu inceleyelim.

Diyelim ki iki listeniz var:

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

Ve şimdi ilk listeden başlayarak her iki listeyi de kopyalamamız gerekiyor:

İlk önce değişkeni copyorijinal listemize ayarlayarak deneyelim list_1:

copy=list_1

Şimdi kopyaların list_1 kopyalandığını düşünüyorsanız, yanılıyorsunuz. idİki değişken aynı nesneye işaret eğer fonksiyon bize gösterebilir. Hadi bunu deneyelim:

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

Çıktı:

4329485320
4329485320

Her iki değişken de aynı argümandır. Şaşırdın mı?

Python'un bir değişkende hiçbir şey saklamadığını bildiğimizden, Değişkenler sadece nesneye başvuruyor ve nesne değeri saklıyor. Burada nesne bir listama aynı nesneye iki farklı değişken adıyla iki referans oluşturduk. Bu, her iki değişkenin de yalnızca farklı adlarla aynı nesneyi işaret ettiği anlamına gelir.

Bunu yaptığınızda copy=list_1, aslında:

resim açıklamasını buraya girin

Burada resim listesi_1 ve kopyada iki değişken adı vardır, ancak nesne her iki değişken için de aynıdır. list

Dolayısıyla, kopyalanan listeyi değiştirmeye çalışırsanız, liste yalnızca bir liste olduğu için orijinal listeyi de değiştirir, kopyalanan listeden veya orijinal listeden ne yaparsanız yapın bu listeyi değiştireceksiniz:

copy[0]="modify"

print(copy)
print(list_1)

çıktı:

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

Böylece orijinal listeyi değiştirdi:

Şimdi listeleri kopyalamak için bir pythonic yöntemine geçelim.

copy_1=list_1[:]

Bu yöntem, sahip olduğumuz ilk sorunu giderir:

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

4338792136
4338791432

Yani her iki listemizin de farklı kimliğe sahip olduğunu görebiliyoruz ve her iki değişkenin de farklı nesnelere işaret ettiği anlamına geliyor. Aslında burada olan şey:

resim açıklamasını buraya girin

Şimdi listeyi değiştirmeye çalışalım ve hala bir önceki sorunla karşı karşıya olup olmadığımızı görelim:

copy_1[0]="modify"

print(list_1)
print(copy_1)

Çıktı:

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

Gördüğünüz gibi, yalnızca kopyalanan listeyi değiştirdi. Bu işe yaradığı anlamına gelir.

Sence bitti mi? Hayır. Yuvalanmış listemizi kopyalamaya çalışalım.

copy_2=list_2[:]

list_2kopyası olan başka bir nesneye başvurmalıdır list_2. Hadi kontrol edelim:

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

Çıktıyı elde ederiz:

4330403592 4330403528

Şimdi her iki listenin farklı bir nesneyi işaret ettiğini varsayabiliriz, şimdi değiştirmeye çalışalım ve görelim ki istediğimizi veriyoruz:

copy_2[0][1]="modify"

print(list_2,copy_2)

Bu bize çıktı verir:

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

Bu biraz kafa karıştırıcı görünebilir, çünkü daha önce kullandığımız aynı yöntem işe yaradı. Bunu anlamaya çalışalım.

Yaptığınızda:

copy_2=list_2[:]

İç listeyi değil, yalnızca dış listeyi kopyalıyorsunuz. Bunu idkontrol etmek için işlevi bir kez daha kullanabiliriz.

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

Çıktı:

4329485832
4329485832

Yaptığımızda copy_2=list_2[:], bu olur:

resim açıklamasını buraya girin

Listenin kopyasını oluşturur, ancak yalnızca dış liste kopyasını oluşturur, iç içe liste kopyası değil, iç içe liste her iki değişken için de aynıdır, bu nedenle iç içe listeyi değiştirmeye çalışırsanız, iç içe liste nesnesi aynı olduğundan orijinal listeyi de değiştirir her iki liste için.

Çözüm nedir? Çözüm deepcopyişlevdir.

from copy import deepcopy
deep=deepcopy(list_2)

Şunu kontrol edelim:

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

4322146056 4322148040

Her iki dış listenin farklı kimlikleri vardır, bunu iç yuvalanmış listelerde deneyelim.

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

Çıktı:

4322145992
4322145800

Gördüğünüz gibi her iki kimlik de farklıdır, yani her iki iç içe listenin şimdi farklı bir nesneyi gösterdiğini varsayabiliriz.

Bu, deep=deepcopy(list_2)gerçekte ne olduğunu yaptığınızda anlamına gelir :

resim açıklamasını buraya girin

Her iki iç içe liste farklı nesneyi işaret ediyor ve şimdi iç içe listenin ayrı bir kopyası var.

Şimdi iç içe listeyi değiştirmeye çalışalım ve önceki sorunu çözüp çözmediğini görelim:

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

Çıktıları:

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

Gördüğünüz gibi, orijinal iç içe listeyi değiştirmedi, sadece kopyalanan listeyi değiştirdi.


33

Python'un bunu yapma deyimi newList = oldList[:]


33

Python 3.6 Zamanlamaları

İşte Python 3.6.8 kullanarak zamanlama sonuçları. Bu zamanların mutlak değil, birbiriyle göreceli olduğunu unutmayın.

Sadece sığ kopyalar yapmayı bıraktım ve Python2'de mümkün olmayan bazı yeni yöntemler ekledim, örneğin list.copy()(Python3 dilim eşdeğeri ) ve iki liste açma ( *new_list, = listve new_list = [*list]) biçimi :

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 kazananının hala iyi işlediğini görebiliriz, ancak list.copy()özellikle ikincisinin üstün okunabilirliği göz önüne alındığında Python3'ü çok fazla etkilemez .

Karanlık at, b = [*a]ham dilimlemeden ~% 25 daha hızlı ve diğer açma yönteminden ( *b, = a) iki kat daha hızlı olan açma ve yeniden paketleme yöntemidir ( ).

b = a * 1 ayrıca şaşırtıcı derecede iyi.

Bu yöntemlerin listeler dışında herhangi bir girdi için eşdeğer sonuçlar vermediğini unutmayın . Hepsi dilimlenebilir nesneler için çalışır, birkaç yinelenebilir herhangi bir iş için copy.copy()çalışır , ancak sadece daha genel Python nesneleri için çalışır.


İlgili taraflar için test kodu ( Buradan şablon ):

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'de hala benzer bir hikayeyi doğrulayabilir b=[*a]- bunu yapmanın en belirgin yolu;).
SuperShoot

19

Diğer katılımcılar Tüm verdi harika ancak yöntemlerden sadece ana kadar bahsedilen, siz (tesviye) liste tek boyuta sahip iş cevapları, copy.deepcopy()bir listeyi kopyalamak ve iç içe işaret yok / klon için çalışır listsiz hazır olduğunuzda nesneler çok boyutlu, iç içe listelerle (liste listesi) çalışma. İken Felix Kling onun cevabını bu kavrama başvurur biraz daha konuya ve muhtemelen daha hızlı alternatif kanıtlamak olabilir yerleşik ins kullanarak geçici bir çözüm için vardır deepcopy.

İken new_list = old_list[:], copy.copy(old_list)'ve Py3k için old_list.copy()tek tesviye listeler için işten onlar işaret dönmek listiçinde yuvalanmış nesneler old_listve new_listve birine değişiklikler listnesneler diğer da sürüyor.

Edit: Yeni bilgiler ışığa çıkarıldı

Her iki tarafından işaret edildiği gibi Aaron Hall ve PM 2Ring kullanarak eval()çok daha yavaş da sadece kötü bir fikir değildir copy.deepcopy().

Bu, çok boyutlu listeler için tek seçeneğin olduğu anlamına gelir copy.deepcopy(). Bununla birlikte, orta boyutlu çok boyutlu bir dizide kullanmaya çalıştığınızda performans güneye doğru gittiği için gerçekten bir seçenek değil. timeitBiyoinformatik uygulamaları için duyulmamış hatta büyük bir 42x42 dizisi kullanmaya çalıştım ve bir yanıt beklemekten vazgeçtim ve bu yazıda düzenlememi yazmaya başladım.

O zaman tek gerçek seçenek birden fazla listeyi başlatmak ve bunlar üzerinde bağımsız olarak çalışmaktır. Birinin başka önerileri varsa, çok boyutlu liste kopyalamanın nasıl ele alınacağı konusunda takdir edilecektir.

Diğerlerinin de belirttiği gibi, modülü kullanarak ve çok boyutlu listeler için önemli performans sorunları vardır .copycopy.deepcopy


5
Bu her zaman işe yaramaz, çünkü döndürülen dizenin repr()nesneyi yeniden oluşturmak için yeterli olduğuna dair bir garanti yoktur . Ayrıca, eval()son çare bir araçtır; bkz Eval gerçekten tehlikeli detaylar için SO emektar Ned Batchelder tarafından. Eğer kullanımını savunan Yani eval()sen gerçekten tehlikeli olabilir bahsetmeliyiz.
PM 2Ring

1
Doğru tespit. Her ne kadar Batchelder'ın amacı eval()genel olarak Python'da işleve sahip olmanın bir risk olduğudur. Koddaki işlevi kullanıp kullanmamanız o kadar da değil, Python'da kendi başına bir güvenlik deliği. Benim örnek girdi alır bir işlevle onu kullanmıyor input(), sys.agrvhatta bir metin dosyası. Daha çok, boş bir çok boyutlu listeyi bir kez başlatma ve daha sonra döngünün her yinelemesinde yeniden başlatma yerine bir döngüde kopyalama yoluna sahip olma çizgisi boyunca ilerler.
AMR

1
@AaronHall'ın işaret ettiği gibi, muhtemelen kullanım için önemli bir performans sorunu var new_list = eval(repr(old_list)), bu yüzden kötü bir fikir olmasının yanı sıra, muhtemelen çalışmak için çok yavaş.
AMR

12

Bu konuya henüz değinilmediğim beni şaşırtıyor, bu yüzden bütünlük uğruna ...

Listenin *öğelerini de kopyalayacak olan "uyarıcı işleci": ile liste paketini açabilirsiniz .

old_list = [1, 2, 3]

new_list = [*old_list]

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

Bu yöntemin bariz dezavantajı, sadece Python 3.5+ 'da mevcut olmasıdır.

Zamanlama akıllıca olsa da, bu diğer yaygın yöntemlerden daha iyi performans gösterir.

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
Kopyaları değiştirirken bu yöntem nasıl davranır?
not2qubit

2
@ not2qubit, yeni listenin öğelerini eklemek veya düzenlemek anlamına mı geliyor? Örnekte old_listve new_listiki farklı listede, bir tanesini düzenlemek diğerini değiştirmeyecektir (öğeleri kendileri doğrudan değiştirmediğiniz sürece (liste listesi gibi), bu yöntemlerin hiçbiri derin kopya değildir).
SCB

7

Python sürümünden bağımsız çok basit bir yaklaşım, çoğu zaman kullanabileceğiniz verilen cevaplarda eksikti (en azından ben):

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

Ancak, listem başka kapsayıcılar içeriyorsa (örneğin, iç içe listeler için) yukarıdaki kopyalarda yukarıda verilen yanıtlarda önerildiği gibi derin kopya kullanmalısınız. Örneğin:

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

. Bonus : Öğeleri kopyalamak istemiyorsanız kullanın (sığ kopya olarak da bilinir):

new_list = my_list[:]

Çözüm # 1 ve Çözüm # 2 arasındaki farkı anlayalım

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

Gördüğünüz gibi, # 1 numaralı çözüm yuvalanmış listeleri kullanmadığımızda mükemmel bir şekilde çalıştı. Yuvalanmış listelere 1 numaralı çözümü uyguladığımızda ne olacağını kontrol edelim.

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

Kendi özel sınıfınızı tanımladıysanız ve öznitelikleri korumak istiyorsanız , örneğin Python 3'te alternatifler yerine copy.copy()veya copy.deepcopy()yerine kullanmanız gereken bazı durumlar olduğunu unutmayın :

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

Çıktılar:

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 Bunu anlamaya çalışın. Diyelim ki benim_listem X yerindeki yığın bellekte, yani benim_listim X'i gösteriyor. Şimdi atayarak new_list = my_listyeni_listenin X'i işaret etmesine izin verelim. Bu sığ Kopya olarak bilinir.

Şimdi new_list = my_list[:]atadığımda, sadece my_list öğesinin her nesnesini new_list'e kopyalıyorsunuz demektir. Bu Derin kopya olarak bilinir.

Bunu yapmanın diğer yolu:

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

2

Diğer cevaplardan biraz farklı bir şey yayınlamak istedim. Bu muhtemelen en anlaşılır veya en hızlı seçenek olmasa da, derin kopyanın nasıl çalıştığına dair bir iç görünüm ve aynı zamanda derin kopyalama için başka bir alternatif seçenek sunar. İşlevimde hatalar olması gerçekten önemli değil, çünkü bunun amacı soru cevapları gibi nesneleri kopyalamanın bir yolunu göstermek, aynı zamanda bunu, derin kopyanın özünde nasıl çalıştığını açıklamak için bir nokta olarak kullanmaktır.

Herhangi bir derin kopya fonksiyonunun özünde sığ bir kopya yapmanın yolu vardır. Nasıl? Basit. Herhangi bir derin kopyalama işlevi yalnızca değiştirilemeyen nesnelerin kaplarını çoğaltır. Yuvalanmış bir listeyi derin kopyaladığınızda, listelerin içindeki değiştirilebilir nesneleri değil, yalnızca dış listeleri çoğaltırsınız. Yalnızca kapları çoğaltırsınız. Aynı şey sınıflar için de geçerlidir. Bir sınıfı derin kopyaladığınızda, değişebilir niteliklerinin tümünü kopyalarsınız. Nasıl? Neden sadece listeleri, dikte, tuples, iters, sınıflar ve sınıf örnekleri gibi kapsayıcıları kopyalamanız gerekiyor?

Basit. Değişken bir nesne gerçekten çoğaltılamaz. Asla değiştirilemez, bu yüzden sadece tek bir değerdir. Bu, dizeleri, sayıları, boolleri veya bunların herhangi birini asla kopyalamanız gerekmediği anlamına gelir. Ancak kapları nasıl çoğaltırsınız? Basit. Tüm değerleri içeren yeni bir kapsayıcı başlatmanız yeterli. Deepcopy özyinelemeye dayanır. Hiçbir kap kalmayıncaya kadar tüm kapları, içinde kapları olanları bile çoğaltır. Bir kap değişmez bir nesnedir.

Bunu öğrendikten sonra, herhangi bir referans olmadan bir nesneyi tamamen çoğaltmak oldukça kolaydır. Temel veri türlerini derinlemesine kopyalamak için bir işlev (özel sınıflar için işe yaramaz, ancak bunu her zaman ekleyebilirsiniz)

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

Python'un kendi yerleşik derin kopyası bu örneğe dayanmaktadır. Tek fark, diğer türleri desteklemesi ve öznitelikleri yeni bir yinelenen sınıfa çoğaltarak kullanıcı sınıflarını desteklemesi ve ayrıca bir not listesi veya sözlük kullanarak zaten görüldüğü bir nesneye referansla sonsuz özyinelemeyi engellemesidir. Ve derin kopyalar elde etmek için gerçekten bu kadar. Özünde derin bir kopya yapmak sadece sığ kopyalar yapmaktır. Umarım bu cevap soruya bir şey katar.

ÖRNEKLER

Bu listeye sahip olduğunuzu varsayalım: [1, 2, 3] . Değişmez sayılar çoğaltılamaz, ancak diğer katman olabilir. Bir liste kavrayışı kullanarak çoğaltabilirsiniz: [1, 2, 3] içindeki x için x

Şimdi, bu listeye sahip olduğunuzu düşünün: [[1, 2], [3, 4], [5, 6]] . Bu kez, listenin tüm katmanlarını derin kopyalamak için özyineleme kullanan bir işlev yapmak istiyorsunuz. Önceki liste kavrayışı yerine:

[x for x in _list]

Listeler için yeni bir tane kullanır:

[deepcopy_list(x) for x in _list]

Ve deepcopy_list şöyle görünür:

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

Sonra artık strs, bools, floast, ints ve hatta listelerin herhangi bir listesini özyineleme kullanarak sonsuz sayıda katmana derinleştirebilen bir işleve sahipsiniz . Ve işte burada, derin kopyalar.

TLDR : Deepcopy nesneleri çoğaltmak için özyineleme kullanır ve yalnızca değiştirilemeyen nesneler çoğaltılamadığından, aynı değişmez nesneleri döndürür. Bununla birlikte, bir nesnenin en dıştaki mutable katmanına ulaşıncaya kadar, değişken nesnelerin en iç katmanlarını derinleştirir.


2

İd ve ​​gc aracılığıyla belleğe bakmak için pratik bir bakış açısı.

>>> 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'da şunları yaptığınızı unutmayın:

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

Liste2 gerçek listeyi saklamaz, ancak liste1'e bir başvuru yapar. List1'e bir şey yaptığınızda list2 de değişir. listenin orijinal bir kopyasını oluşturmak için kopyalama modülünü kullanın (varsayılan değil, pip'e indirin) ( copy.copy()basit listeler copy.deepcopy()için, iç içe olanlar için). Bu, ilk listeyle değişmeyen bir kopya oluşturur.


0

Deepcopy seçeneği benim için çalışan tek yöntemdir:

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('-----------------------------')

çıktısına yol açar:

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

Çizgi Bunun nedeni, new_list = my_listdeğişken için yeni bir başvuru atar my_listolan new_list bu benzer Caşağıdaki kod,

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

Tarafından yeni bir liste oluşturmak için kopyalama modülünü kullanmalısınız.

import copy
new_list = copy.deepcopy(my_list)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.