단순 할당 (b = a)
객체 a를 다른 객체 b에 할당할 때 데이터의 복사는 일어나지 않는다.
참조가 이루어지고, 메모리 사본을 만들지 않는다.
* id(x) 함수 : id(x) 함수는 인자를 입력 받고, 입력 받은 그 객체가 메모리 안에 위치해 있는 주소를 반환하는 함수
* 객체의 수명 동안 유일하고 바뀌지 않는 특징을 가지고 있다. (메모리에 올라간 이후에는 주소가 바뀌지 않는다.)
a = [1, 2, 3]
b = a
print(b) # [1, 2, 3]
b.append(4)
print(a) # [1, 2, 3, 4]
print(b) # [1, 2, 3, 4]
print(id(a)) # 5231404992
print(id(b)) # 5231404992
print(id(a) == id(b)) # True
# 즉 단순 할당(b = a)은 객체 a에 b라는 새로운 이름을 추가로 부여한 것과 같다.

얕은 복사 (b = a[ : ])
얕은 복사는 새 복합 객체를 만들고 원본 객체 내에 있는 오브젝트에 대한 참조를 삽입한다.
a = [1, 2, 3]
b = a[:]
b.append(4)
print(a) # [1, 2, 3]
print(b) # [1, 2, 3, 4]
print(id(a)) # 5204060480
print(id(b)) # 5202076090
print(id(a)==id(b)) # False

깊은 복사 (b = copy.deepcopy(a))
얕은 복사와 깊은 복사의 차이점은 리스트나 클래스와 같은 복합 객체에 대해서만 적용됨
얕은 복사는 새 복합 객체를 만들고 원본 객체 내에 있는 오브젝트에 대한 참조를 삽입하는 반면, 깊은 복사는 새 복합 객체를 만들고 원본 객체 내에 있는 객체에 대한 복사를 재귀적으로 삽입한다.
깊은 복사의 경우 재귀 루프에 빠지거나, 공유하려고 한 데이터에 대해서도 너무 많은 복사를 할 수 있다.
import copy
a = [[1, 2],[3, 4]]
shallow_copied = a[:]
deep_copied = copy.deepcopy(a)
a[0][0] = 10
print(a) # [[10, 2], [3, 4]]
print(shallow_copied) # [[10, 2], [3, 4]]
print(deep_copied) # [[1, 2], [3, 4]]

리스트나 클래스처럼 다층적으로 구성되어서 재귀적으로 복사가 필요한 경우에는
얕은 복사만으로 완전히 복사되지 않을 수 있으므로 깊은 복사(재귀)가 필요하다