[python] 목록을 복제하거나 복사하는 방법은 무엇입니까?


7 Answers

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

  1. 10.59 초 (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 초 (101.6us / itn) - deep python으로 클래스를 복사하는 pure python Copy() 메소드
  3. 1.488 초 (14.88us / itn) - 클래스를 복사하지 않는 순수 파이썬 Copy() 메소드 (dicts / lists / tuple 만)
  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] ( 목록 이해력 )
  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) new_list = []; new_list.extend(old_list)
  9. 0.039 초 (0.39us / itn) - old_list[:] 목록 old_list[:] ( 목록 조각 )

가장 빠른 방법은 목록 조각입니다. 그러나 copy.copy() 와는 달리 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

편집 : 새로운 스타일의 예전 스타일의 클래스와 dicts를 벤치 마크에 추가했으며, 파이썬 버전을 훨씬 빠르게 만들고 목록 표현식과 extend() 포함한 몇 가지 메소드를 추가했습니다.

Question

파이썬에서리스트를 복제하거나 복사하는 옵션은 무엇입니까?

new_list = my_list 를 사용하면 my_list 변경 될 때마다 new_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[:]

솔루션 # 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])

당신이 볼 수 있듯이 중첩 목록을 사용하지 않을 때 Solution # 1은 완벽하게 작동했습니다. 중첩 목록에 솔루션 # 1을 적용 할 때 어떤 일이 발생하는지 확인해 봅시다.

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   #Solution#1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       #Solution #2 - DeepCopy worked in nested list



파이썬에서리스트를 복제하거나 복사하는 옵션은 무엇입니까?

Python 3에서는 다음과 같이 얕은 복사본을 만들 수 있습니다.

a_copy = a_list.copy()

파이썬 2와 3에서는 원본의 전체 슬라이스로 얕은 복사본을 얻을 수 있습니다 :

a_copy = a_list[:]

설명

목록을 복사하는 의미 론적 방법은 두 가지가 있습니다. 얕은 복사본은 동일한 개체의 새 목록을 만들고 딥 복사본은 새로운 동일한 개체가 포함 된 새 목록을 만듭니다.

얕은 목록 복사

얕은 복사본은 목록의 개체에 대한 참조의 컨테이너 인 목록 자체 만 복사합니다. 포함 된 오브젝트가 변경 가능하고 하나가 변경된 경우, 변경 사항은 두리스트 모두에 반영됩니다.

파이썬 2와 파이썬 3에서 다른 방법이 있습니다. 파이썬 2 웨이는 파이썬 3에서도 작동합니다.

파이썬 2

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

목록은 내용에 대한 포인터의 배열이므로 얕은 사본은 포인터 만 복사하기 때문에 두 개의 다른 목록이 있지만 내용은 동일합니다. 내용을 복사하려면 깊은 사본이 필요합니다.

딥 카피

파이썬 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 사용하지 마십시오.

이것을 딥 카피하는 방법으로 사용하는 것을 볼 수는 있지만 그렇게하지는 마십시오 :

problematic_deep_copy = eval(repr(a_list))
  1. 위험합니다. 특히 신뢰하지 않는 출처에서 무언가를 평가하는 경우 더욱 그렇습니다.
  2. 복사하는 하위 요소에 동등한 요소를 재생산 할 수있는 표현이 없으면 신뢰할 수 없습니다.
  3. 그것은 또한 덜 performant입니다.

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



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

"splat operator": * 사용하여 목록 압축을 수행 할 수 있습니다. * 는 목록의 요소를 복사합니다.

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)



이것을하기위한 파이썬의 관용구는 newList = oldList[:]




파이썬 3.6.0 타이밍

다음은 파이썬 3.6.0을 사용한 타이밍 결과입니다. 이 시간들은 절대적이지 않고 서로에 상대적이라는 것을 명심하십시오.

필자는 얕은 복사본만을 사용하고, list.copy() (파이썬 3 슬라이스에 상응하는 )와리 스트 풀기 ( *new_list, = list )와 같이 list.copy() 에서는 불가능했던 몇 가지 새로운 메소드를 추가했다.

METHOD                  TIME TAKEN
b = a[:]                6.468942025996512   #Python2 winner
b = a.copy()            6.986593422974693   #Python3 "slice equivalent"
b = []; b.extend(a)     7.309216841997113
b = a[0:len(a)]         10.916740721993847
*b, = a                 11.046738261007704
b = list(a)             11.761539687984623
b = [i for i in a]      24.66165203397395
b = copy.copy(a)        30.853400873980718
b = []
for item in a:
  b.append(item)        48.19176080400939

파이썬 3의 list.copy() 접근법의 가독성을 고려할 때, 오래된 승자가 여전히 정상에 오르는 것을 볼 수는 있지만 엄청난 양은 아닙니다.

이러한 메서드는리스트 이외의 입력에 대해서는 동일한 결과를 출력하지 않습니다 . 그것들은 모두 슬라이스 가능한 객체에 대해 작동하고, 반복 가능한 객체에 대해서는 약간만 작동하지만, 어떤 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\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", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\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 = []\nfor 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))



new_list = list(old_list)




처음부터 시작해서 좀 더 깊이 탐구 해 봅시다.

그래서 두 목록이 있다고 가정 해보십시오.

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

그리고 두 목록을 모두 복사해야합니다. 이제 첫 번째 목록에서 시작합니다.

먼저 일반적인 복사 방법으로 시도해 보겠습니다.

copy=list_1

이제 list_1을 복사 한 것으로 생각하면 잘못 될 수 있습니다. 확인해 봅시다.

The id() function shows us that both variables point to the same list object, i.e. they share this object.
print(id(copy))
print(id(list_1))

산출:

4329485320
4329485320

놀랐지? 이제 살펴 보겠습니다.

파이썬은 변수에 아무것도 저장하지 않는다는 것을 알기 때문에, 변수는 단지 객체를 참조하고 객체는 값을 저장합니다. 여기에 객체가 있지만 두 개의 다른 변수 이름으로 동일한 객체에 대한 두 개의 참조를 만들었습니다. 따라서 두 변수 모두 동일한 객체를 가리키고 있습니다.

그래서 copy=list_1 할 때 실제로 무엇을하는지 :

여기 이미지 list_1과 copy는 두 개의 변수 이름이지만 객체는 두 변수 모두에 대해 동일 list

따라서 복사 된 목록을 수정하려고하면 목록이 하나뿐이기 때문에 원래 목록도 수정됩니다. 복사 한 목록이나 원본 목록에서 상관없이 목록을 수정하게됩니다.

copy[0]="modify"

print(copy)
print(list_1)

산출:

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

그래서 원래 목록을 수정했습니다 :

그러면 그 해결책은 무엇입니까?

해결책 :

이제 목록을 복사하는 두 번째 비유 방법으로 이동해 보겠습니다.

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

우리는 실제로 중첩 된 목록이 아닌 외부 목록 만 복사하므로 중첩 된 목록은 두 목록 모두에 대해 동일한 개체입니다. 확인해 봅시다.

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

산출:

4329485832
4329485832

그래서 실제로 copy_2=list_2[:] 할 때 이것은 일어납니다 :

리스트의 사본을 생성하지만 중첩 된리스트 복사가 아닌 중첩 된리스트 만 복사합니다. 중첩 된리스트는 두 변수 모두 동일합니다. 따라서 중첩 된리스트를 수정하려고하면 중첩 된리스트 객체가 둘 다 동일하기 때문에 원래의리스트도 수정합니다 중첩 목록.

그래서 해결책은 무엇입니까?

해결책은 deep copy

from copy import deepcopy
deep=deepcopy(list_2)

이제 확인해 보겠습니다.

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

산출:

4322146056 4322148040

두 ID가 다르기 때문에 이제 중첩 목록 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']]

따라서 원래의 중첩 목록을 수정하지 못했지만 복사 된 목록 만 수정했습니다.

만약 당신이 내 세부 답변을 좋아한다면, 당신이 의심의 여지 가이 답변을 realted있다, upvoting하여 알려 주시기 바랍니다 :)






Related