Python 공부

할당 vs 얕은 복사 vs 깊은 복사 / In-place 제자리 수정 vs Out-of-place 외부 생성

myun0506 2026. 1. 9. 17:01

 

- 할당 vs 얕은 복사 vs 깊은 복사

  • 얕은 복사
    • 객체의 최상위 레벨만 복사하고, 그 안에 포함된 하위 객체들은 원본 객체와 참조를 공유함
  • 깊은 복사
    • 객체와 그 안에 포함된 모든 하위 객체까지 재귀적으로 복사하여, 원본 객체와 완전히 독립된 새로운 객체를 생성함
    • pandas에서는 copy 자체가 깊은복사임
  • a = [1, [2, 3]] 가정
구분 1. 단순 할당 (b=a) 2. 얕은 복사 (c=a.copy()) 3. 깊은 복사 (d =copy.deepcopy(a))
개념 리모콘 공유 껍데기만 새것 전부 다 새것
새 주소 a와 주소가 같음 a와 주소가 다름 a와 주소가 다름
내부 주소 내부 리스트 주소 같음 내부 리스트 주소 같음 내부 리스트 주소 다름
영향력 - 숫자 b를 바꾸면 a가 바뀜 c를 바꾸어도 a는 안바뀜 (immutable) d를 어떻게 바꿔도 a는 안전함
영향력 - 리스트 b를 바꾸면 a가 바뀜 c를 바꾸면 a가 바뀜 (mutable) d를 어떻게 바꿔도 a는 안전함
  • a 안의 값을 수정한다고 할 때
    • a 값 안의 숫자값은 immutable이기 때문에
      • 얕은 복사나 깊은 복사를 한 경우엔 서로 다른 주소값을 가진 채로 a와 달리 변동 없음
      • 예를들어, a의 숫자값이 1에서 7로 변동된다고 한다면
        • 1을 가리키던 id(a[0])이 7을 가리키는 주소값으로 완전히 '변경'(갈아끼워짐)되기 때문!
        • but c[0]은 여전히 1을 가리키는 주소값 그대로
    • a 값 안의 리스트는 mutable이기 때문에
      • 얕은 복사를 한 경우엔 a와 리스트의 주소값은 같은 것을 가리키고 있기 때문에
      • a 안의 리스트가 수정된다면 c 안의 리스트 또한 변동됨
      • 예를들어, a의 리스트가 [2,3]이었는데 a[1][0]=200으로 수정한다고 한다면
        • c안의 리스트도 같은 주소값의 리스트를 가리키고 있었기 때문에 [200,3]으로 변동됨!
        • 즉, id(a[1]) == id(c[1]) 라는 것!
import copy
a = [1,2,[3,4]]
b = a # 할당
c = a.copy() # 얕은 복사
d = copy.deepcopy(a) # 깊은 복사

print(id(a)) #12345
print(id(b)) #12345
print(id(c)) #23456
print(id(d)) #34567

a[0] = 100
print(a,id(a),id(a[0])) #100,2,3,4 #12345 #00000
print(b,id(b),id(b[0])) #100,2,3,4 #12345 #00000
print(c,id(c),id(c[0])) #1,2,3,4 #23456 #10000
print(d,id(d),id(d[0])) #1,2,3,4 #34567 #10000

a[2][0] = 300
print(a,id(a),id(a[2][0])) #100,2,300,4 #12345 #20000
print(b,id(b),id(b[2][0])) #100,2,300,4 #12345 #20000
print(c,id(c),id(c[2][0])) #1,2,300,4 #23456 #20000
print(d,id(d),id(d[2][0])) #1,2,3,4 #34567 #30000

 

- In-place vs Out-of-place

  • In-place (제자리 수정)
    • 새로운 객체를 만들지 않고, 기존 객체가 저장된 메모리 주소(그 exact 자리)로 찾아가서 내부의 데이터만 슬쩍 바꾸는 방식
    • 메모리를 아낄 수 있지만, 원본 데이터가 파괴됨
    • ex) list.append(), list.sort(), list[0] = 1
  • Out-of-place (외부 생성 / 사본 생성)
    • 기존 객체는 건드리지 않고 메모리의 다른 공간(Out)에 새로운 객체를 만들어 그 결과를 담는 방식
    • 원본이 안전하게 보존되지만, 새로운 메모리 공간이 필요함
    • ex) sorted(list), a = a+1, a = a+[4]
# In-place
def 채널_바꾸기(동생_리모컨):
    # 동생이 받은 리모컨으로 리스트의 0번 칸을 고칩니다.
    동생_리모컨[0] = "무한도전" 

우리집_TV = ["뉴스", "드라마"]
채널_바꾸기(우리집_TV) # 내 리모컨을 복사해서 동생에게 줍니다.

print(우리집_TV) 
# 결과: ["무한도전", "드라마"] (원본이 바뀌어버렸어요!)


# Out-of-place
def 새_TV_사주기(동생_리모컨):
    # 동생이 "난 이 TV 안 볼래!" 하고 리모컨을 새 리스트에 연결합니다.
    동생_리모컨 = ["만화", "스포츠"] 
    print("동생 방 TV:", 동생_리모컨)

우리집_TV = ["뉴스", "드라마"]
새_TV_사주기(우리집_TV)

print("거실 TV:", 우리집_TV) 
# 결과: ["뉴스", "드라마"] (거실 TV는 그대로예요!)

 

 

- q=p[:]의 얕은 복사 방식

  • 외곽 주소: p와 q는 서로 다른 새 리스트임. 서로 다른 주솟값을 가짐
  • 내부 요소:
    • p[0]과 q[0]은 숫자 1의 주소를 같이 가리킴
    • p[2]와 q[2]는 내부 리스트 [3,4]가 저장된 동일한 주소를 가리킴
x = [1, 2, 3]
y = x[:] 
print(id(x),id(y)) # 서로 다른 주소값
x[0] = 99 # x는 주소값 그대로
print(x, y) # [99,2,3],[1,2,3]
print(id(x),id(y)) # 서로 다른 채로 유지

p = [1, 2, [3, 4]]
q = p[:] 
print(id(p),id(q)) # 주소값 서로 다름
print(id(p[2]),id(p[2][0])) # but 안의 내용의 주소는 같음
print(id(q[2]),id(q[2][0]))
p[2][0] = 999
print(p, q) # [1,2,[999,4]],[1,2,[999,4]]
print(id(p),id(q)) # 주소값 서로 다른 채로 유지