[1주차] 의도하지 않았던 얕은 복사

김동영·2021년 12월 12일
0

의문이 떠오른 계기

프로그래머스 인공지능 데브코스 3기를 시작한지 1주일이 지났다.

첫 글을 어떻게 시작할지 고민을 많이 했는데, 1주일간 컴퓨터적 사고력을 기르기 위해 여러 알고리즘 강의를 듣는 중에 떠오른 의문을 해결해보고자 한다.

내가 하고자 했던 일은 다음과 같다.

  • 하나의 리스트를 만드려고 한다.
  • 리스트의 각 원소는 서로 다른 집합이다.
  • 리스트의 각 집합을 각 인덱스 값으로 초기화한다.(list[i]의 집합은 초기값으로 i를 가지고 있다.)

나는 이를 수행하기 위해 다음과 같은 방식으로 코딩하였다.

a = [set()] * 5

강의 자료에서는 위와 같은 일을 다음과 같은 방식으로 코딩하였다.

b = [set() for x in rang(5)]

여기서 나는 내가 작성한 방식이 틀리지 않았는지 list가 어떻게 이루어져있는지 확인해보았고 그 결과는 다음과 같다.

print(a)
[set(), set(), set(), set(), set()]

print(b)
[set(), set(), set(), set(), set()]

겉보기에는 문제가 없었고, 다른 살펴볼 내부가 있다고 판단되지 않아서 다음 단계로 진행했는데 문제가 생겼다.

리스트의 각 집합을 초기화했더니, 의도하지 않은 방식으로 작동한 것이다.

for i in range(10):
    a[i].add(i)

for i in range(10):
    b[i].add(i)

위와 같은 코드로 리스트의 각 집합에 인덱스 값을 add한 결과는 다음과 같다.

print(a)
[{0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}]

print(b)
[{0}, {1}, {2}, {3}, {4}]

원했던 결과는 b인데, 그럼 a와 b의 차이는 왜 일어난걸까?

a = [set()] * 5
for i in range(5):
    a[i].add(i)
        
b = [set() for x in range(5)]
for i in range(5):
    b[i].add(i)

분명 a의 각 인덱스에 접근하여 add를 수행했는데, a의 모든 집합이 같은 원소를 갖는다는 점에서 얕은 복사와 깊은 복사 개념에 의한 문제라는 생각이 들었다.

파이썬을 접한지 오래 되지 않아서, 파이썬으로 얕은 복사와 깊은 복사 문제를 처음 접했다.

b의 경우에는 각기 다른 set()을 생성한 것이기 때문에, 당연히 문제가 없다.

a의 경우에는 [set()]을 * 연산을 수행했는데, 나는 이를 별개의 set을 복사(깊은 복사)했다고 생각했는데, 실제로는 얕은 복사를 수행한 것으로 보였다.

얕은 복사가 일어났다

얕은 복사라는 것은 간단히 말해서 메모리 참조 주소만 복사한 것이다.

만약에 얕은 복사가 이루어졌다면 파이썬의 id(객체) 함수를 수행했을 때의 결과가 같은 값이 나올 것이다.

for i in range(5):
    print(id(a[i]))
    
2945906123936
2945906123936
2945906123936
2945906123936
2945906123936

for i in range(5):
    print(id(b[i]))

2945908912416
2945908914208
2945908913760
2945908913312
2945908913088

이를 확인해본 결과는 위와 같다.

b의 각 집합의 id 값은 서로 다름에 반해, a의 각 집합의 id 값이 같으므로, 각 a[i] 마다 값을 add했지만 사실 하나의 집합에 add한 것과 같은 결과인 것이다.

이는 list에 * 연산을 수행하면, 해당 list 요소를 반복하기 때문에, 같은 요소를 반복함으로써 id가 같은 반복이 일어났기에 얕은 복사가 일어난 것이다.

mutable과 immutable

그런데, 또 하나의 의문이 생겼다.

나는 평소에도 리스트 생성시에

a = [0] * 5
print(a)
[0, 0, 0, 0, 0]

와 같은 방법을 자주 사용했었다.

그런데 이 때는 다음과 같이 값을 조작하여도 문제가 없었다.

for i in range(len(a)):
    a[i]+=i
print(a)
[0, 1, 2, 3, 4]

앞선 사례의 경우와 같으려면 0 + 1 + 2 + 3 + 4 = 10, 즉 [10, 10, 10, 10, 10]이 되어야 할텐데 말이다.

이는 mutable 객체와 immutable 객체의 특성으로, 간단히 말해서 immutable 객체는 얕은 복사의 영향을 받지 않는다.

mutable 객체는 list, set, dictionary 가 있으며, mutable 객체만 얕은 복사를 주의하면 된다.

immutable 객체는 int, float, str, tuple 등이 있다.

따라서 평소에 사용했던 [0] * n 과 같은 방식에 문제가 없었던 것이었다.

profile
오래 공부하는 사람

0개의 댓글