참고
Do it! 자료구조와 함께 배우는 알고리즘 입문 (파이썬 편)
뮤터블과 이뮤터블의 대입
정수를 나타내는 int형과 문자열을 나타내는 str형은 값을 변경할 수 없다. 이렇게 값을 변경할 수 없는 특성을 이뮤터블이라고 하며, 값을 변경할 수 있는 특성은 뮤터블이라고 한다.
뮤터블 자료형: list, set, dictionary
이뮤터블 자료형: 수, 문자열, 튜플
배열에는 객체가 저장되며, 배열에 저장된 객체 하나하나를 원소라고 한다.
각 원소는 인덱스를 부여받는다.
파이썬에서 배열 원소의 자료형은 어떤 것이라도 상관없다.
파이썬에서 변수는 객체와 연결된 이름에 불과하다고 설명했다. 따라서 리스트, 튜플의 원소 자료형을 미리 정할 필요가 없다. 다음 프로그램에서 확인해 보자.
# 자료형을 정하지 않은 리스트 원소 확인하기
x = [15, 64, 7, 3.14, [32, 55], 'ABC']
for i in range(len(x)):
print(f'x[{i}] = {x[i]}')
x[0] = 15
x[1] = 64
x[2] = 7
x[3] = 3.14
x[4] = [32, 55]
x[5] = ABC
그림은 배열 x의 형태를 나타낸 것이다.
각 원소 x[0], x[1], x[2], x[3], x[4], x[5]는 각각 int형, int형, int형, float형, list형, str형의 객체를 참조하는 이름이다.
파이썬에서는 리스트 내 각 원소의 자료형을 다르게 사용할 수 있다.
리스트를 복사할 때 사용하는 copy() 함수는 주의해서 사용해야 한다. 리스트를 복사한 후 원솟값을 변경하면 복사된 원솟값까지 변경될 수 있기 때문이다.
>>> a = [[1, 2], [3, 4]]
>>> b = a.copy()
>>> a is b
False
>>> a == b
True
>>> id(a)
2202101300032
>>> id(b)
2202101302336
>>> a[1].append(5)
>>> a
[[1, 2], [3, 4, 5]]
>>> b
[[1, 2], [3, 4, 5]]
>>> id(a)
2202101300032
>>> id(b)
2202101302336
>>> a[0][1] = 9
>>> a
[[1, 9], [3, 4, 5]]
>>> b
[[1, 9], [3, 4, 5]]
>>> x = [[1, 2, 3], [4, 5, 6]]
>>> y = x.copy() # x를 y로 얕은 복사
>>> x[0][1] = 9
>>> x
[[1, 9, 3], [4, 5, 6]]
>>> y
[[1, 9, 3], [4, 5, 6]]
copy를 사용하여 리스트 y를 복사한 뒤 x[0][1]의 값을 9로 업데이트하면 y[0][1]의 값까지 9로 업데이트된다. 그 이유는 리스트의 얕은 복사(shallow copy)를 수행하기 때문이다. 얕은 복사에서는 리스트 안의 모든 원소가 참조하는 곳까지 복사된다. 복사되는 모든 원소는 x[0]과 x[1] 2개이다. x[0]과 y[0]이 참조하는 곳이 같으므로 x[0][1]과 y[0][1]이 참조하는 곳도 같다. 즉, 리스트 x가 참조하는 곳이 다르면 y도 달라진다. 이러한 상황을 피하려면 구성 원소 수준으로 복사하는 방법이 필요하며, 이를 깊은 복사(deep copy)라고 한다. 깊은 복사는 copy 모듈 안의 deepcopy() 함수로 수행한다.
>>> import copy # deepcopy를 사용하기 위해 copy 모듈 임포트
>>> x = [[1, 2, 3], [4, 5, 6]]
>>> y = copy.deepcopy(x) # x를 y로 깊은 복사
>>> x[0][1] = 9
>>> x
[[1, 9, 3], [4, 5, 6]] # 대입된 9가 출력됨
>>> y
[[1, 2, 3], [4, 5, 6]] # y 배열은 영향을 받지 않음
리스트의 원소뿐만 아니라 구성 원소(원소의 원소)도 복사된다. 따라서 x[0][1]에 9를 대입하면 x[0][1]이 참조하는 곳을 2에서 9로 업데이트한다. 그 결과 y[0][1]이 참조하는 곳은 업데이트되지 않는다.
✓ 얕은 복사와 깊은 복사
객체가 갖는 멤버의 값을 새로운 객체로 복사할 때 객체가 참조 자료형의 멤버를 포함할 경우 얕은 복사라고 하며, 이는 참조값만 복사하는 방식이다. 깊은 복사는 참조값 뿐만 아니라 참조하는 객체 자체를 복사한다. 즉, 객체가 갖는 모든 멤버(값과 참조 형식 모두)를 복사하므로 전체 복사라고도 한다.
✓ 얕은 복사(shallow copy)
리스트 안에 리스트(mutable 객체 안에 mutable 객체)인 경우에 문제가 된다.
재할당하는 경우는 문제가 없다. 메모리 주소도 변경된다.
>>> x = [[1, 2, 3], [4, 5, 6]]
>>> y = x.copy()
>>> id(x)
2202101387712
>>> id(y)
2202101300096
>>> id(x[0])
2202101464576
>>> id(y[0])
2202101464576
>>> x[0] = [7, 8, 9] # 재할당
>>> x
[[7, 8, 9], [4, 5, 6]]
>>> y
[[1, 2, 3], [4, 5, 6]]
>>> id(x[0])
2202101464320
>>> id(y[0])
2202101464576
>>> x[0][1] = 4
>>> x
[[7, 4, 9], [4, 5, 6]]
>>> y
[[1, 2, 3], [4, 5, 6]]