코딩 테스트 연습을 하던 중 다음과 같은 상황을 맞이하여 엄청 당황했었습니다.

s = [1,2,3]
for i in range(len(s)):
    a = s
    print(a,s)
    a.pop()

 

위 코드를 동작 시켰을 때, 제가 기대한 결과는 a라는 list에만 변화가 생기는 것이었습니다. 하지만 다음과 같은 결과가 나온 것입니다.

a: [1, 2, 3] s: [1, 2, 3]
a: [1, 2] s: [1, 2]
a: [1] s: [1]

파이썬의 copy 개념이 제대로 잡혀있지 않아 발생한 문제였습니다.

 


copy가 동작하는 방식은 데이터가 immutable / mutable 인지 그리고 shallow copy / deep copy인지에 따라 달라집니다.

  • immutable한 자료형은 int형, str형 과 같은 자료형이며 mutable한 자료형은 list, set, dictionary와 같은 자료형입니다.
  • shallow copy에는 '=', [:] , copy.copy(), 객체.copy() 연산이 있습니다.

 

shallow copy일 경우, 다음과 같이 동작합니다.

서로 다른 변수가 같은 객체를 참조하고 있는 형태 입니다. 바로 문제가 발생했던 상황이죠.

변수가 immutable할 경우 이 상황에서 문제가 발생하지 않습니다. immutable한 자료형은 바뀌지 않기 때문에 데이터에 변화가 생길 경우 새로운 객체가 생성됩니다.

 

하지만 문제는 mutable한 자료형에서 발생합니다. mutable한 자료형은 말 그대로 바뀔 수 있기 때문에 객체 자체가 바뀌게 됩니다.

 

코드로 살펴보겠습니다.

 

<immutable한 자료형에서의 shallow copy>

a = 3
b = a

print('a,b:',a,b)
print('ID of a and b:',id(a),id(b))

# a,b: 3 3
# ID of a and b: 140423687498096 140423687498096

b = 5
print('a,b:',a,b)
print('ID of a and b:',id(a),id(b))

# a,b: 3 5
# ID of a and b: 140654810294640 140654810294704

처음에는 동일한 객체를 참조하고 있지만, 값이 변하게 되면서 b가 새로운 객체를 참조하고 있음을 알 수 있습니다.

 

<mutable한 자료형에서의 shallow copy>

listA = [1,2,3]
listB = listA

print('listA,listB:',listA,listB)
print('ID of listA and listB:',id(listA),id(listB))

# listA,listB: [1, 2, 3] [1, 2, 3]
# ID of listA and listB: 140613739648320 140613739648320

listB.append([1,2,3])
print('listA,listB:',listA,listB)
print('ID of listA and listB:',id(listA),id(listB))

# listA,listB: [1, 2, 3, [1, 2, 3]] [1, 2, 3, [1, 2, 3]]
# ID of listA and listB: 140613739648320 140613739648320

mutable한 자료형의 경우, 객체가 새로 생성되는 것이 아니라 객체 자체에서 변화가 생깁니다. 

저는 이러한 것을 고려하지 않고 '=' 을 통해 shallow copy를 하였기 때문에 문제가 발생했던 것이었습니다.

 

이러한 문제를 [:]을 통해 간단히 해결할 수 있습니다.(완벽한 방식은 아닙니다.)

listA = [1,2,3]
listB = listA[:]

print('listA,listB:',listA,listB)
print('ID of listA and listB:',id(listA),id(listB))

# listA,listB: [1, 2, 3] [1, 2, 3]
# ID of listA and listB: 140613739648320 140613739648320

listB[0] = 99
print('listA,listB:',listA,listB)
print('ID of listA and listB:',id(listA),id(listB))

# listA,listB: [1, 2, 3] [99, 2, 3]
# ID of listA and listB: 140457913193792 140457913193600

listB.append([1,2,3])
print('listA,listB:',listA,listB)
print('ID of listA and listB:',id(listA),id(listB))

# listA,listB: [1, 2, 3] [99, 2, 3, [1, 2, 3]]
# ID of listA and listB: 140457913193792 140457913193600

listA = listB[:]

listB[3].append(999)

print('listA,listB:',listA,listB)
# ID of listA and listB: 140524217406784 140524217406592
# listA,listB: [99, 2, 3, [1, 2, 3, 999]] [99, 2, 3, [1, 2, 3, 999]]

보시면 [:]를 활용할 경우, listA와 listB의 주소가 다르기 때문에 Deep copy로 보이기도 합니다.

하지만 마지막을 보시면 [1,2,3] 객체에 999를 append하였을 때 두 리스트 모두 반영되는 것을 알 수 있습니다.

따라서, 완벽한 방식은 아니지요. 하지만 때에 따라 활용할 수는 있을 것 같습니다.


해결 방안

 

  • 자료형을 고려하여 shallow copy와 deep copy를 적절히 사용하는 것이 가장 좋은 방법이라고 생각합니다.

 

Reference

https://blockdmask.tistory.com/576

'파이썬 > 이론' 카테고리의 다른 글

[Python] Generator  (0) 2023.03.02
[Python] Python의 메모리 관리(Garbage Collector)  (0) 2023.02.14

+ Recent posts