割り当て後にリストが予期せず変更されます。これを防ぐために、クローンまたはコピーするにはどうすればよいですか?
を使用している間new_list = my_list
、new_list
変更はmy_list
毎回変更されます。これはなぜですか。リストを複製またはコピーして、それを防ぐにはどうすればよいですか。
回答
を使用するとnew_list = my_list
、実際には2つのリストはありません。割り当ては、実際のリストではなくリストへの参照をコピーするだけなので、両方とも割り当て後に同じリストnew_list
をmy_list
参照します。
リストを実際にコピーするには、さまざまな可能性があります。
組み込みのlist.copy()メソッド(Python 3.3以降で使用可能)を使用できます。
new_list = old_list.copy()
あなたはそれをスライスすることができます:
new_list = old_list[:]
これに関するAlexMartelliの意見(少なくとも2007年に遡る)は、これは奇妙な構文であり、これまで使用しても意味がないというものです。;)(彼の意見では、次のものはより読みやすいです)。
組み込みlist()関数を使用できます。
new_list = list(old_list)
ジェネリックを使用できますcopy.copy():
import copy new_list = copy.copy(old_list)
これは、最初
list()
のデータ型を見つける必要があるためよりも少し遅くなりますold_list
。リストにオブジェクトが含まれていて、それらもコピーする場合は、generic 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)]
フェリックスはすでに優れた答えを提供しましたが、私はさまざまな方法の速度比較を行うと思いました:
- 10.59秒(105.9us / itn)- copy.deepcopy(old_list)
- 10.16秒(101.6us / itn)-deepcopyを
Copy()
使用してクラスをコピーする純粋なPythonメソッド - 1.488秒(14.88us / itn)-
Copy()
クラスをコピーしない純粋なPythonメソッド(dict /リスト/タプルのみ) - 0.325秒(3.25us / itn)-
for item in old_list: new_list.append(item)
- 0.217秒(2.17us / itn)-
[i for i in old_list]
(リスト内包表記) - 0.186秒(1.86us / itn)- copy.copy(old_list)
- 0.075秒(0.75us / itn)-
list(old_list)
- 0.053秒(0.53us / itn)-
new_list = []; new_list.extend(old_list)
- 0.039秒(0.39us / itn)-
old_list[:]
(リストスライス)
したがって、最速はリストスライスです。ただしcopy.copy()
、list[:]
とlist(list)
は異なりcopy.deepcopy()
、Pythonバージョンではリスト、辞書、クラスインスタンスがリストにコピーされないため、元のリストが変更されると、コピーされたリストでも変更されることに注意してください。その逆も同様です。
(誰かが興味を持っているか、問題を提起したい場合のスクリプトは次のとおりです:)
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の方法は、Python3でも機能します。
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つの異なるリストがありますが、コンテンツは同じです。コンテンツのコピーを作成するには、ディープコピーが必要です。
ディープコピー
リストのディープコピーdeepcopycopyを作成するには、Python 2または3で、次のモジュールを使用します。
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))
- 特に、信頼できないソースから何かを評価している場合は危険です。
- コピーしているサブ要素に、同等の要素を再現するために評価できる表現がない場合、それは信頼できません。
- また、パフォーマンスも低下します。
64ビットPython2.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ビットPython3.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
それをバインドしnew_list
ました。どの名前を使用しても、リストは1つしかないため、それを参照するときに行われた変更は、を参照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)
コピーのコーナーケースの詳細については、ドキュメントを参照してください。
最初から始めて、この質問を調べてみましょう。
したがって、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
、それは実際にやっています:
この画像では、list_1とcopyに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']]
ご覧のとおり、元のネストされたリストは変更されず、コピーされたリストのみが変更されました。
使用する thing[:]
>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>>
Python3.6のタイミング
Python3.6.8を使用したタイミングの結果は次のとおりです。これらの時間は絶対的なものではなく、相互に関連していることに注意してください。
私は浅いコピーだけを行うことに固執し、Python2では不可能だったいくつかの新しいメソッドlist.copy()
((Python3スライスと同等)や2つの形式のリストアンパック(*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の勝者は依然としてうまく機能していることがわかりますがlist.copy()
、特に後者の優れた読みやすさを考えると、Python3をそれほど上回っていません。
ダークホースは、開梱と再梱包の方法(b = [*a]
)であり、生のスライスよりも約25%速く、他の開梱方法(*b, = a
)の2倍以上高速です。
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))
これを行うためのPythonのイディオムは newList = oldList[:]
他のすべての寄稿者は素晴らしい答えを出しました。これは単一のディメンション(平準化された)リストがある場合に機能しますが、これまでに説明した方法の中でcopy.deepcopy()
、リストの複製/コピーにのみ機能し、ネストされたlist
オブジェクトを指すようにはなりません。多次元のネストされたリスト(リストのリスト)の操作。一方でフェリックスクリングが彼の答えでそれを指し、もう少し問題と可能性へのより高速な代替を証明するかもしれないビルトインを使用して回避策がありますdeepcopy
。
一方でnew_list = old_list[:]
、copy.copy(old_list)'
とPy3kのためのold_list.copy()
単一の平準リストの仕事、彼らが指し示すに戻りlist
内にネストされたオブジェクトold_list
とnew_list
、との1への変更list
オブジェクトは、他に生まれついています。
編集:新しい情報が明らかになりました
アーロンホールとPM2Ringの 両方が指摘したように、使用
eval()
することは悪い考えであるだけでなく、よりもはるかに遅いですcopy.deepcopy()
。これは、多次元リストの場合、唯一のオプションが
copy.deepcopy()
。であることを意味します。そうは言っても、適度なサイズの多次元配列で使用しようとするとパフォーマンスが大幅に低下するため、これは実際にはオプションではありません。私はtimeit
42x42の配列を使用しようとしましたが、バイオインフォマティクスアプリケーションでは前代未聞ではなく、それほど大きくもありませんでした。応答を待つのをあきらめて、この投稿に編集内容を入力し始めました。その場合、唯一の現実的な選択肢は、複数のリストを初期化し、それらを個別に処理することであるように思われます。多次元リストのコピーを処理する方法について、他に何か提案があれば、よろしくお願いします。
他の人が述べているように、モジュールと多次元リストの使用には重大なパフォーマンスの問題があります。copy
copy.deepcopy
これがまだ言及されていないことに驚いたので、完全を期すために...
「スプラット演算子」を使用してリストの解凍を実行でき*
ます。これにより、リストの要素もコピーされます。
old_list = [1, 2, 3]
new_list = [*old_list]
new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]
この方法の明らかな欠点は、Python3.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のバージョンに依存しない非常に単純なアプローチは、ほとんどの場合に使用できる(少なくとも私はそうする)すでに与えられた回答にはありませんでした:
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[:]
ソリューション#1とソリューション#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
独自のカスタムクラスを定義し、属性を保持したい場合は、たとえば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
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[:]
、my_listの各オブジェクトをnew_listにコピーするだけです。これはディープコピーとして知られています。
これを行うことができる他の方法は次のとおりです。
new_list = list(old_list)
import copy new_list = copy.deepcopy(old_list)
他のいくつかの回答とは少し違うものを投稿したかったのです。これはおそらく最も理解しやすい、または最速のオプションではありませんが、ディープコピーがどのように機能するかについての内部ビューを少し提供し、ディープコピーの別の代替オプションでもあります。私の関数にバグがあるかどうかは問題ではありません。これは、質問の回答のようなオブジェクトをコピーする方法を示すだけでなく、ディープコピーがコアでどのように機能するかを説明するためのポイントとして使用するためです。
ディープコピー機能の中核となるのは、シャローコピーを作成する方法です。どうやって?シンプル。ディープコピー関数は、不変オブジェクトのコンテナのみを複製します。ネストされたリストをディープコピーすると、外側のリストのみが複製され、リスト内の可変オブジェクトは複製されません。コンテナを複製しているだけです。同じことがクラスでも機能します。クラスをディープコピーすると、その可変属性がすべてディープコピーされます。それで、どうやって?リスト、dict、タプル、iter、クラス、クラスインスタンスなどのコンテナをコピーするだけでよいのはなぜですか?
それは簡単です。可変オブジェクトは実際には複製できません。変更することはできないため、単一の値にすぎません。つまり、文字列、数字、bool、またはそれらのいずれかを複製する必要はありません。しかし、どのようにコンテナを複製しますか?シンプル。すべての値で新しいコンテナを初期化するだけです。ディープコピーは再帰に依存しています。コンテナがなくなるまで、コンテナが内部にあるものも含めて、すべてのコンテナを複製します。コンテナは不変のオブジェクトです。
それがわかれば、参照なしでオブジェクトを完全に複製するのは非常に簡単です。これは、基本的なデータ型をディープコピーするための関数です(カスタムクラスでは機能しませんが、いつでも追加できます)
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も変更されます。コピーモジュール(デフォルトではなく、pipでダウンロード)を使用して、リストの元のコピーを作成します(copy.copy()
単純なリストの場合copy.deepcopy()
、ネストされたリストの場合)。これにより、最初のリストで変更されないコピーが作成されます。
これは、この行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)
ディープコピーオプションは私のために働く唯一の方法です:
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]]]
-----------------------------