etc./StackOverFlow

할당 후 예기치 않은 변경 사항을 나열합니다. 이것은 왜 발생하며 어떻게 방지할 수 있습니까?

청렴결백한 만능 재주꾼 2021. 10. 27. 23:23
반응형

질문자 :aF.


사용하는 동안 new_list = my_list 를 개조 new_list 변경 my_list 마다. 그 이유는 무엇이며 목록을 복제하거나 복사하여 방지하려면 어떻게 해야 합니까?



new_list = my_list 를 사용하면 실제로 두 개의 목록이 없습니다. 할당은 실제 목록이 아닌 목록에 대한 참조를 복사하므로 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 Kling

Felix는 이미 훌륭한 답변을 제공했지만 다양한 방법의 속도 비교를 할 것이라고 생각했습니다.

  1. 10.59초(105.9µs/itn) - copy.deepcopy(old_list)
  2. 10.16초(101.6 µs/itn) - deepcopy를 사용하여 클래스를 복사하는 Copy()
  3. 1.488초(14.88µs/itn) - Copy() 메서드(dicts/lists/tuple만 해당)
  4. 0.325초(3.25µs/itn) - for item in old_list: new_list.append(item)
  5. 0.217초(2.17μs/itn) - [i for i in old_list] ( 목록 이해 )
  6. 0.186초(1.86µs/itn) - copy.copy(old_list)
  7. 0.075초(0.75µs/itn) - list(old_list)
  8. 0.053초(0.53μs/itn) - new_list = []; new_list.extend(old_list)
  9. 0.039초(0.39µs/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

cryo

나는 Python 3.3+에서 슬라이싱만큼 빨라야 하는 list.copy() 메서드를 추가한다고 들었습니다.

 newlist = old_list.copy()

anatoly techtonik

Python에서 목록을 복제하거나 복사하는 옵션은 무엇입니까?

Python 3에서는 다음을 사용하여 얕은 복사를 만들 수 있습니다.

 a_copy = a_list.copy()

Python 2 및 3에서는 원본의 전체 조각으로 얕은 복사본을 얻을 수 있습니다.

 a_copy = a_list[:]

설명

목록을 복사하는 두 가지 의미론적 방법이 있습니다. 얕은 복사는 동일한 개체의 새 목록을 만들고 깊은 복사는 새 등가 개체를 포함하는 새 목록을 만듭니다.

얕은 목록 복사

얕은 복사는 목록의 개체에 대한 참조의 컨테이너인 목록 자체만 복사합니다. 포함된 개체 자체가 변경 가능하고 하나가 변경되면 변경 사항이 두 목록에 모두 반영됩니다.

Python 2와 3에서는 이를 수행하는 다양한 방법이 있습니다. Python 2 방법은 Python 3에서도 작동합니다.

파이썬 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

파이썬 3

Python 3에서 목록은 list.copy 메서드를 가져옵니다.

 a_copy = a_list.copy()

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

목록은 내용에 대한 포인터의 배열일 뿐이므로 얕은 복사는 포인터를 복사하므로 두 개의 다른 목록이 있지만 내용은 같습니다. 내용의 복사본을 만들려면 전체 복사본이 필요합니다.

딥 카피

목록의 전체 복사본 을 만들려면 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'], [], []]

그래서 우리는 깊은 복사된 목록이 원본과 완전히 다른 목록임을 알 수 있습니다. 당신은 당신 자신의 기능을 굴릴 수 있지만 하지 마십시오. 표준 라이브러리의 deepcopy 기능을 사용하여 다른 방법으로는 발생하지 않았을 버그를 생성할 가능성이 높습니다.

eval 사용하지 마십시오

이것이 deepcopy의 방법으로 사용되는 것을 볼 수 있지만 하지 마십시오.

 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

Aaron Hall

적절한 사본을 만드는 방법을 알려주는 많은 답변이 이미 있지만 원본 '복사본'이 실패한 이유에 대한 답변은 없습니다.

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)

복사의 모서리 케이스에 대한 자세한 내용은 설명서 를 참조하십시오.


jack

처음부터 시작하여 이 질문을 살펴보겠습니다.

두 개의 목록이 있다고 가정해 보겠습니다.

 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

두 변수는 완전히 동일한 인수입니다. 너 놀랐 니?

아시다시피 Python은 변수에 아무 것도 저장하지 않습니다. 변수는 객체를 참조하고 객체는 값을 저장합니다. 여기서 객체는 list 이지만 두 개의 다른 변수 이름으로 동일한 객체에 대한 두 개의 참조를 생성했습니다. 이것은 두 변수가 이름만 다른 동일한 객체를 가리키고 있음을 의미합니다.

copy = list_1 하면 실제로 다음을 수행합니다.

여기에 이미지 설명 입력

여기 이미지에서 list_1copy 는 두 개의 변수 이름이지만 객체는 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

따라서 두 목록이 서로 다른 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']]

보시다시피 원래 중첩 목록을 수정하지 않고 복사된 목록만 수정했습니다.


Aaditya Ura

thing[:]

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

Paul Tarjan

파이썬 3.6 타이밍

다음은 Python 3.6.8을 사용한 타이밍 결과입니다. 이 시간은 절대적인 것이 아니라 상대적인 시간임을 명심하십시오.

나는 얕은 복사만 하는 것에 list.copy() (파이썬 3 슬라이스에 해당 )와 두 가지 형태의 목록 풀기 ( *new_list, = listnew_list = [*list] ):

 METHOD TIME TAKEN b = [*a] 2.75180600000021 b = a * 1 3.50215399999990 b = a[:] 3.78278899999986 # Python 2 winner (see above) b = a.copy() 4.20556500000020 # Python 3 "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

우리는 Python 2 우승자가 여전히 잘하는 것을 볼 수 있지만, 특히 후자의 우수한 가독성을 고려할 때 list.copy()

다크 호스는 포장 풀기 및 재포장 방법( b = [*a] )으로, 원시 슬라이싱보다 ~25% 빠르며 다른 포장 풀기 방법( *b, = a )보다 2배 이상 빠릅니다.

b = a * 1 도 놀랍게도 잘 작동합니다.

이러한 메서드는 목록 이외의 입력에 대해 동일한 결과를 출력 하지 않습니다. 그것들은 모두 슬라이스 가능한 객체에 대해 작동하고 일부는 모든 copy.copy() 대해 작동하지만 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))

River

이를 수행하는 Python의 관용구는 newList = oldList[:]


erisco

다른 모든 기여자들은 단일 차원(평준화된) 목록이 있을 때 작동하는 훌륭한 copy.deepcopy() 만 목록을 복제/복사하고 목록을 가리키지 않습니다. 다차원 중첩 목록(목록 목록)으로 작업할 때 중첩 list Felix Kling 이 그의 답변에서 이를 언급하는 동안 deepcopy 대한 더 빠른 대안을 증명할 수 있는 내장 기능을 사용하는 해결 방법이 있을 수 있습니다.

new_list = old_list[:] , copy.copy(old_list)' 및 Py3k의 경우 old_list.copy() 는 단일 수준 목록에서 작동 old_listnew_list 내에 중첩된 list 객체를 가리키는 것으로 돌아가고 하나로 변경됩니다. list 개체의 다른 개체에서 영속화됩니다.

편집: 밝혀진 새로운 정보

Aaron HallPM 2Ring이 모두 지적했듯이 eval() 사용하는 것은 나쁜 생각일 뿐만 아니라 copy.deepcopy() 보다 훨씬 느립니다.

즉, 다차원 목록의 경우 유일한 옵션은 copy.deepcopy() 입니다. 즉, 적당한 크기의 다차원 배열에서 사용하려고 할 때 성능이 저하되기 때문에 실제로 옵션이 아닙니다. 나는 생물 정보학 응용 프로그램에 대해 들어본 적이 없거나 그리 크지 않은 42x42 어레이를 사용하여 timeit 을 측정하려고 시도했으며 응답을 기다리는 것을 포기하고 이 게시물에 편집 내용을 입력하기 시작했습니다.

유일한 실제 옵션은 여러 목록을 초기화하고 독립적으로 작업하는 것입니다. 누구든지 다차원 목록 복사를 처리하는 방법에 대해 다른 제안이 있으면 감사하겠습니다.

다른 사람들이 말했듯이 다차원 목록의 copy 모듈과 copy.deepcopy 를 사용하는 경우 상당한 성능 문제가 있습니다.


AMR

이것이 아직 언급되지 않았다는 것이 놀랍습니다. 그래서 완전성을 위해...

* 사용하여 목록 압축 해제를 수행할 수 있으며, 이는 목록의 요소도 복사합니다.

 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)

SCB

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

jainashish

사용자 정의 클래스를 정의했고 속성을 유지하려면 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

Chris_Rands

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 = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)

Ravi Shankar

다른 답변과 조금 다른 것을 게시하고 싶었습니다. 이것이 가장 이해하기 어렵거나 가장 빠른 옵션이 아닐지라도 딥 복사가 작동하는 방식에 대한 약간의 내부 보기를 제공할 뿐만 아니라 딥 복사를 위한 또 다른 대안이 됩니다. 내 기능에 버그가 있는지 여부는 그다지 중요하지 않습니다. 이 질문의 요점은 질문 답변과 같은 객체를 복사하는 방법을 보여주는 것뿐만 아니라 이것을 핵심에서 딥카피가 작동하는 방식을 설명하는 요점으로 사용하는 것입니다.

깊은 복사 기능의 핵심은 얕은 복사를 만드는 방법입니다. 어떻게? 단순한. 모든 깊은 복사 기능은 변경할 수 없는 개체의 컨테이너만 복제합니다. 중첩 목록을 딥카피하면 목록 내부의 변경 가능한 객체가 아니라 외부 목록만 복제합니다. 컨테이너만 복제하고 있습니다. 수업에서도 마찬가지입니다. 클래스를 딥카피할 때 변경 가능한 모든 속성을 딥카피합니다. 그래서 방법? 목록, dicts, 튜플, iters, 클래스 및 클래스 인스턴스와 같은 컨테이너만 복사해야 하는 이유는 무엇입니까?

간단 해. 가변 객체는 실제로 복제될 수 없습니다. 절대 변경할 수 없으므로 단일 값일 뿐입니다. 즉, 문자열, 숫자, bool 또는 그 중 어떤 것도 복제할 필요가 없습니다. 그러나 컨테이너를 어떻게 복제합니까? 단순한. 모든 값으로 새 컨테이너를 초기화하기만 하면 됩니다. 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))

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]

그런 다음 재귀를 사용하여 strs, bools, floast, int 및 심지어 목록의 목록 을 무한히 많은 레이어에 딥카피할 수 있는 함수를 갖게 되었습니다. 그리고 거기에 딥카피가 있습니다.

TLDR : Deepcopy는 객체를 복제하기 위해 재귀를 사용하고 불변 객체는 복제할 수 없기 때문에 이전과 동일한 불변 객체만 반환합니다. 그러나 객체의 가장 바깥쪽 변경 가능한 레이어에 도달할 때까지 변경 가능한 객체의 가장 안쪽 레이어를 딥카피합니다.


Corman

다음을 수행할 때 Python에서 기억하십시오.

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

List2는 실제 목록을 저장하지 않고 list1에 대한 참조를 저장합니다. 따라서 list1에 무엇이든 하면 list2도 변경됩니다. 복사 모듈(기본값 아님, pip에서 다운로드)을 사용하여 목록의 원본 복사본을 copy.copy() 간단한 목록의 경우 copy.deepcopy() , 중첩된 목록의 경우 copy.deepcopy()). 이렇게 하면 첫 번째 목록에서 변경되지 않는 복사본이 만들어집니다.


Dr. Hippo

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)

B.Mr.W.

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

shahar_m

이 때문에, 라인 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)

Roshin Raphel

사용 방법은 복사 중인 목록의 내용에 따라 다릅니다. 목록에 중첩된 dicts 이 포함되어 있으면 deepcopy가 작동하는 유일한 방법입니다. 그렇지 않으면 답변에 나열된 대부분의 방법(슬라이스, 루프 [for], 복사, 확장, 결합 또는 압축 풀기)이 작동하고 비슷한 시간에 실행됩니다(예외 for 루프 및 deepcopy, 최악의 결과를 초래함).

스크립트

 from random import randint from time import time import copy item_count = 100000 def copy_type(l1: list, l2: list): if l1 == l2: return 'shallow' return 'deep' def run_time(start, end): run = end - start return int(run * 1000000) def list_combine(data): l1 = [data for i in range(item_count)] start = time() l2 = [] + l1 end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'combine', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} def list_extend(data): l1 = [data for i in range(item_count)] start = time() l2 = [] l2.extend(l1) end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'extend', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} def list_unpack(data): l1 = [data for i in range(item_count)] start = time() l2 = [*l1] end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'unpack', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} def list_deepcopy(data): l1 = [data for i in range(item_count)] start = time() l2 = copy.deepcopy(l1) end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'deepcopy', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} def list_copy(data): l1 = [data for i in range(item_count)] start = time() l2 = list.copy(l1) end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'copy', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} def list_slice(data): l1 = [data for i in range(item_count)] start = time() l2 = l1[:] end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'slice', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} def list_loop(data): l1 = [data for i in range(item_count)] start = time() l2 = [] for i in range(len(l1)): l2.append(l1[i]) end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'loop', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} def list_list(data): l1 = [data for i in range(item_count)] start = time() l2 = list(l1) end = time() if type(data) == dict: l2[0]['test'].append(1) elif type(data) == list: l2.append(1) return {'method': 'list()', 'copy_type': copy_type(l1, l2), 'time_µs': run_time(start, end)} if __name__ == '__main__': list_type = [{'list[dict]': {'test': [1, 1]}}, {'list[list]': [1, 1]}] store = [] for data in list_type: key = list(data.keys())[0] store.append({key: [list_unpack(data[key]), list_extend(data[key]), list_combine(data[key]), list_deepcopy(data[key]), list_copy(data[key]), list_slice(data[key]), list_loop(data[key])]}) print(store)

결과

 [{"list[dict]": [ {"method": "unpack", "copy_type": "shallow", "time_µs": 56149}, {"method": "extend", "copy_type": "shallow", "time_µs": 52991}, {"method": "combine", "copy_type": "shallow", "time_µs": 53726}, {"method": "deepcopy", "copy_type": "deep", "time_µs": 2702616}, {"method": "copy", "copy_type": "shallow", "time_µs": 52204}, {"method": "slice", "copy_type": "shallow", "time_µs": 52223}, {"method": "loop", "copy_type": "shallow", "time_µs": 836928}]}, {"list[list]": [ {"method": "unpack", "copy_type": "deep", "time_µs": 52313}, {"method": "extend", "copy_type": "deep", "time_µs": 52550}, {"method": "combine", "copy_type": "deep", "time_µs": 53203}, {"method": "deepcopy", "copy_type": "deep", "time_µs": 2608560}, {"method": "copy", "copy_type": "deep", "time_µs": 53210}, {"method": "slice", "copy_type": "deep", "time_µs": 52937}, {"method": "loop", "copy_type": "deep", "time_µs": 834774} ]}]

fjemi

이를 처리하는 간단한 기술이 있습니다.

암호:

 number=[1,2,3,4,5,6] #Original list another=[] #another empty list for a in number: #here I am declaring variable (a) as an item in the list (number) another.append(a) #here we are adding the items of list (number) to list (another) print(another)

산출:

 >>> [1,2,3,4,5,6]

귀하의 문의에 도움이 되었길 바랍니다.


Code Carbonate

지금까지 나열되지 않은 목록을 복사하는 또 다른 방법이 있습니다. 빈 목록을 추가하는 것입니다. l2 = l + [] .

Python 3.8로 테스트했습니다.

 l = [1,2,3] l2 = l + [] print(l,l2) l[0] = 'a' print(l,l2)

최선의 대답은 아니지만 작동합니다.


Laurent Lyaudet

출처 : http:www.stackoverflow.com/questions/2612802/list-changes-unexpectedly-after-assignment-why-is-this-and-how-can-i-prevent-it

반응형