リストを複製またはコピーする方法は?

2010-04-10 python list copy clone

Pythonでリストを複製またはコピーするオプションは何ですか?

new_list = my_listを使用している間、 new_list = my_list変更すると、毎回new_listが変更さmy_listます。 どうしてこれなの?

Answers

ことを使用してくださいthing[:]

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

これを行うためのPythonのイディオムはnewList = oldList[:]

new_list = my_list 、実際には2つのリストはありません。割り当ては、実際のリストではなくリストへの参照をコピーするだけなので、 new_listmy_listは両方とも、割り当て後に同じリストを参照します。

リストを実際にコピーするには、さまざまな可能性があります。

  • 組み込みのlist.copy()メソッドを使用できます(Python 3.3以降で使用可能)。

    new_list = old_list.copy()
    
  • あなたはそれをスライスすることができます:

    new_list = old_list[:]
    

    これに関するAlex Martelliの意見(少なくとも2007年には )は、これは奇妙な構文であり、これを使用しても意味がありません 。 ;)(彼の意見では、次のものはもっと読みやすいです)。

  • 組み込みのlist()関数を使用できます。

    new_list = list(old_list)
    
  • 一般的なcopy.copy()使用できます。

    import copy
    new_list = copy.copy(old_list)
    

    これは、最初にold_listのデータ型を見つける必要があるため、 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)]

Felixはすでに優れた答えを提供していますが、私はさまざまな方法の速度比較を行うと思いました。

  1. 10.59秒(105.9us / itn) copy.deepcopy(old_list)
  2. 10.16秒(101.6us / itn)-deepcopyでクラスをコピーする純粋なPython Copy()メソッド
  3. 1.488秒(14.88us / itn)-クラスをコピーしない純粋なpython Copy()メソッド(dicts / lists / 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] old_listの[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.deepcopy()やpythonバージョンとは異なり、 copy.copy()list[:]list(list) 、リスト内のリスト、辞書、クラスインスタンスをコピーしないので、オリジナルが変更された場合に注意してください。 、それらはコピーされたリストでも変更され、その逆も同様です。

(誰かが興味を持っている、または問題を提起したい場合のスクリプトは次のとおりです:)

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

Python 3.3以降では、 list.copy()メソッドが追加 されると言われいます 。これはスライスと同じくらい高速です。

newlist = old_list.copy()

Pythonでリストを複製またはコピーするオプションは何ですか?

Python 3では、浅いコピーを次のようにして作成できます。

a_copy = a_list.copy()

Python 2および3では、オリジナルの完全なスライスを含む浅いコピーを取得できます。

a_copy = a_list[:]

説明

リストをコピーするには、2つの意味論的な方法があります。浅いコピーは同じオブジェクトの新しいリストを作成し、深いコピーは新しい同等のオブジェクトを含む新しいリストを作成します。

浅いリストのコピー

浅いコピーでは、リスト内のオブジェクトへの参照のコンテナであるリスト自体のみがコピーされます。含まれているオブジェクトが変更可能で、1つが変更された場合、変更は両方のリストに反映されます。

Python 2と3でこれを行う方法はいくつかあります。Python2の方法は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を使用すると、my_listが変更されるたびにnew_listが変更されます。どうしてこれなの?

my_listは、メモリ内の実際のリストを指す名前です。 new_list = my_listと言うと、コピーを作成するのではなく、メモリ内の元のリストを指す別の名前を追加するだけです。リストのコピーを作成すると、同様の問題が発生する可能性があります。

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

リストは内容へのポインタの配列にすぎないため、浅いコピーはポインタをコピーするだけなので、2つの異なるリストがありますが、内容は同じです。コンテンツのコピーを作成するには、ディープコピーが必要です。

深いコピー

リストのディープコピーを作成するには、Python 2または3で、 copyモジュールでdeepcopyを使用します

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'], [], []]

そして、深くコピーされたリストは、元のリストとはまったく異なるリストであることがわかります。独自の関数をロールすることもできますが、ロールしないでください。標準ライブラリのディープコピー機能を使用することで、通常は発生しないバグを作成する可能性があります。

eval使用しない

これはディープコピーの方法として使用されているようですが、しないでください。

problematic_deep_copy = eval(repr(a_list))
  1. 特に信頼できないソースから何かを評価している場合は危険です。
  2. コピーしているサブ要素に、同等の要素を再現するために評価できる表現がない場合は、信頼できません。
  3. また、パフォーマンスも低下します。

64ビットPython 2.7の場合:

>>> 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ビットPython 3.5の場合:

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

適切なコピーを作成する方法を説明する回答はすでに多数ありますが、元の「コピー」が失敗した理由を説明する回答はありません。

Pythonは変数に値を格納しません。名前をオブジェクトにバインドします。元の割り当てでは、 my_listによって参照されるオブジェクトをmy_list 、それをnew_listにもバインドしました。どちらの名前を使用しても、リストは1つしかないため、 my_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)

コピーのコーナーケースの詳細については、ドキュメントを参照してください。

他のすべての貢献者が素晴らしい答えを出しましたが、これは単一の次元(レベル化された)リストがある場合に機能しますが、これまでに述べたメソッドの場合、 copy.deepcopy()のみが機能し、リストを複製/コピーして、多次元のネストされたリスト(リストのリスト)で作業しているときのネストされたlistオブジェクト。 Felix Klingは彼の回答でそれについて言及していますが、この問題にはもう少しあります。おそらく、 deepcopy代わるより速い代替手段となる可能性のあるビルトインを使用した回避策があります。

一方new_list = old_list[:]copy.copy(old_list)'とPy3kためold_list.copy()単一平坦化リストの仕事は、それらが指し示すに戻すlist内のネストされたオブジェクトold_listnew_listと、1つの変更listオブジェクトのもう1つは永続します。

編集:新しい情報が明らかに

Aaron HallPM 2Ringの両方がeval()を使用して指摘したのは悪い考えであるだけでなく、 copy.deepcopy()よりもはるかに遅いです。

つまり、多次元リストの場合の唯一のオプションはcopy.deepcopy()です。そうは言っても、適度なサイズの多次元配列で使用しようとするとパフォーマンスが大幅に低下するため、これは実際にはオプションではありません。バイオインフォマティクスアプリケーションでは前例のない42x42の配列を使用して時間をtimeitうとしましたが、応答を待つことをあきらめて、この投稿への編集の入力を開始しました。

その場合、唯一の実際のオプションは、複数のリストを初期化して、それらを個別に処理することです。多次元リストのコピーを処理する方法について他の提案がある場合は、いただければ幸いです。

他の人が述べたように、 多次元リストに copyモジュールとcopy.deepcopy 使用すると、パフォーマンスに重大な問題があります

Python 3.6タイミング

Python 3.6.8を使用したタイミング結果は次のとおりです。これらの時間は絶対的なものではなく、相対的なものです。

浅いコピーのみを行うことにこだわり、 list.copy() (Python3のスライスに相当するもの )や2つの形式のリストのアンパック*new_list, = listおよびnew_list = [*list] list.copy()など、Python2では不可能だったいくつかの新しいメソッドも追加しました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()をそれほどlist.copy()していません。

ダークホースは、アンパックおよび再パック方法( b = [*a] )です。これは、生のスライスより〜25%高速で、他のアンパック方法( *b, = a )の2倍以上高速です。

b = a * 1も驚くほどうまくいきます。

これらのメソッドは、リスト以外の入力に対して同等の結果を出力しないことに注意してください。これらはすべてスライス可能オブジェクトで機能し、一部は反復可能オブジェクトで機能しますが、より一般的なPythonオブジェクトではcopy.copy()のみが機能します。


これは関係者のためのテストコードです( ここからのテンプレート ):

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))
new_list = my_list[:]

new_list = my_list これを理解してみてください。 my_listがXのヒープメモリにあるとしましょう。つまり、my_listはXを指しています。ここで、 new_list = my_listを割り当てることにより、new_listがXを指しています。これは、浅いコピーと呼ばれます。

ここで、 new_list = my_list[:]を割り当てると、 new_list = my_list[:]各オブジェクトをnew_listにコピーするだけです。これはディープコピーと呼ばれます。

これを行うことができるその他の方法は次のとおりです。

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

Pythonバージョンに依存しない非常に単純なアプローチは、ほとんどの場合に使用できるすでに与えられた回答にはありませんでした(少なくとも私はそうします):

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

ご覧のとおり、ネストされたリストを使用していない場合でも、ソリューション#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

最初から始めて、この質問を探りましょう。

したがって、2つのリストがあるとします。

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

そして、両方のリストをコピーする必要があります。最初のリストから始めます。

したがって、まず変数のcopyを元のリストlist_1設定してみましょう。

copy=list_1

これで、list_1がコピーされたと考えている場合は、誤りです。 id関数は、2つの変数が同じオブジェクトを指すかどうかを示します。これを試してみましょう:

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

出力は次のとおりです。

4329485320
4329485320

どちらの変数もまったく同じ引数です。びっくりした?

したがって、Pythonは変数に何も格納しないことがわかっているため、変数はオブジェクトを参照しているだけで、オブジェクトは値を格納しています。ここではオブジェクトはlistが、2つの異なる変数名で同じオブジェクトへの2つの参照を作成しました。つまり、両方の変数が同じオブジェクトを指しており、名前が異なっているだけです。

copy=list_1を実行すると、実際には次のようになりcopy=list_1

ここに画像の説明を入力してください

ここの画像では、 list _1とコピーは2つの変数名ですが、オブジェクトはlistである両方の変数で同じです

したがって、コピーされたリストを変更しようとすると、リストはそこに1つしかないため、元のリストも変更されます。コピーされたリストまたは元のリストから何を行っても、そのリストは変更されます。

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

したがって、両方のリストが異なるIDを持っていることがわかり、両方の変数が異なるオブジェクトを指していることを意味します。ここで実際に起こっていることは:

ここに画像の説明を入力してください

次に、リストを変更して、前の問題がまだ発生しているかどうかを確認します。

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

両方の外側のリストは異なるIDを持っているので、内側のネストされたリストでこれを試してみましょう。

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

ご覧のとおり、元のネストされたリストは変更されず、コピーされたリストのみが変更されました。

これはまだ言及されていないので驚きます。完全を期すために...

"splat演算子": * 、リストのアンパックを実行できます。これにより、リストの要素もコピーされます。

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)

独自のカスタムクラスを定義していて、属性を保持したい場合は、Python 3などの代替手段ではなく、 copy.copy()またはcopy.deepcopy()を使用する必要がある場合があることに注意してください。

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

他の回答とは少し違うものを投稿したかったのです。これは最も理解しやすい、または最速のオプションではない可能性が高いですが、ディープコピーがどのように機能するかについての内面図を提供し、ディープコピーの別の代替オプションでもあります。これは、質問の回答のようにオブジェクトをコピーする方法を示すことですが、コアでディープコピーがどのように機能するかを説明するためのポイントとしても使用するためです。

深いコピー機能の中核は、浅いコピーを作成する方法です。どうやって?シンプル。ディープコピー機能は、不変オブジェクトのコンテナのみを複製します。ネストされたリストをディープコピーすると、リストの内部の変更可能なオブジェクトではなく、外部リストのみが複製されます。コンテナーを複製するだけです。クラスでも同じことが言えます。クラスをディープコピーすると、そのすべての変更可能な属性がディープコピーされます。では、どうやって?リスト、辞書、タプル、イター、クラス、クラスインスタンスなどのコンテナーをコピーする必要があるのはなぜですか?

それは簡単です。変更可能なオブジェクトは実際には複製できません。これは変更できないため、単一の値のみです。つまり、文字列、数値、ブール、またはそれらのいずれかを複製する必要はありません。しかし、どのようにコンテナを複製しますか?シンプル。すべての値で新しいコンテナを初期化するだけです。ディープコピーは再帰に依存しています。コンテナがなくなるまで、すべてのコンテナを複製します。コンテナは不変のオブジェクトです。

それがわかったら、参照なしでオブジェクトを完全に複製することは非常に簡単です。基本的なデータ型をディープコピーするための関数は次のとおりです(カスタムクラスでは機能しませんが、いつでも追加できます)

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自体の組み込みディープコピーは、その例に基づいています。唯一の違いは、他のタイプをサポートし、属性を新しい複製クラスに複製することでユーザークラスもサポートし、メモリストまたは辞書を使用してすでに見られているオブジェクトへの参照で無限再帰をブロックします。そしてそれは本当に深いコピーを作るためのそれです。基本的に、深いコピーを作成することは、浅いコピーを作成することです。この答えが質問に何か追加されることを願っています。

次のリストがあるとします: [1、2、3] 。不変の数値は複製できませんが、他の層は複製できます。リスト内包表記を使用して複製できます: [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]

これで再帰を使用して、str、bool、floast、int 、さらにはリストのリストを無限に多くのレイヤーにディープコピーできる関数ができました。ディープコピーがあります。

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も変更されます。 (リストの元のコピーを作成するには、copyモジュール(デフォルトではない、ピップのダウンロード)を使用しcopy.copy()単純なリスト、用copy.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]]]
-----------------------------

Related