데이터분석 기록일지

파이썬

[Python] 참조(Reference) + 복사(Copy)

야하루 2024. 7. 5. 15:45
sizes = [[60, 50], [30, 70], [60, 30], [80, 40]]
for s in sizes:
      s.sort(reverse = True) 
      
print(sizes)
# 출력 : [[60, 50], [70, 30], [60, 30], [80, 40]]

이 글은 이 코드에서  최초로 시작되었으며.....

 

-> 이 코드에서 for문 내에서 지역 변수인 s를 정렬했는데, 전역 변수인 sizes도 정렬되는게 이해가 안가서 알아보다가 공부하게 된 개념이다.

 


참조

 

변수가 어떤 값을 가리키는 것을 참조(reference)라고 한다.

리스트, 딕셔너리, 문자열, 숫자 등의 객체를 변수에 할당하면, 해당 변수는 그 객체를 가리키게 된다.

즉, 변수는 실제 데이터가 저장된 메모리 공간이 아니라, 해당 객체의 메모리 위치를 참조하여 값을 나타내는 것이다.

따라서 파이썬에서 변수를 선언한다는 의미는 변수가 어떠한 객체를 참조한다는 의미이다.

 

여기서 가변(mutable) 객체불변(immutable) 객체에 따라서 또 나뉘게 되는데,

1. 문자열, 숫자, 튜플 등은 파이썬에서 불변 객체이다. 이 불변 객체들은 값이 변하지 않는다.
a = 10
print(a)
# 출력 10

a += 10
print(a)
# 출력 : 20

 

따라서 a = 10 에서  a+=10을 하여 a=20이 되었어도, 10 이라는 객체가 변한 것이 아니라 a가 10에서 20이라는 다른 객체를 참조하게 된 것이다.

a = 10
print(a)
# 출력: 10
print(id(a))
# 출력: 136137182200336

a += 10
print(a)
# 출력: 20
print(id(a))
# 출력: 136137182200656

 

(id함수는 변수가 가리키는 객체의 저장된 메모리 주소를 반환한다.)

따라서 a가 10일때와 10을 더해서 20일때, 서로 다른 메모리 위치에 존재하며 다른 객체를 가리킨다는 것을 알 수 있다.

 

 


 

2. 반면 리스트, 딕셔너리 등은 가변 객체이므로 내부 요소를 직접 수정할 수 있다.
따라서 가변 객체 내용의 변경은 해당 메모리 위치에 반영된다.
my_list = [10,20,30]
my_list[0] = 40
print(my_list)
# 출력: [40,20,30]

my_list = [10, 20, 30] 에서 my_list[0]은 10을 가리키고, my_list[1]은 20을 가리킨다.

이때 my_list[0] = 40 으로 값을 수정하면, 리스트는 가변 객체이기 때문에 실제로 10이 저장된 메모리 위치의 값을 변경하여 my_list의 첫 번째 요소도 변경된다.

 

my_list=[10,20,30]
print(id(my_list))
# 출력 : 136135898875840
my_list[0]=40
print(id(my_list))
# 출력 : 136135898875840

 

이 코드를 보면 my_list[0]의 값을 수정하여 my_list의 값이 변했어도, my_list는 같은 메모리에 존재하는 동일한 객체라는 것을 알 수 있다.

 


 

a = 10
b = 10
print(id(a))
# 출력: 136137182200336
print(id(b))
# 출력: 136137182200336

-----------------------------

my_list=[1,2,3,4]
your_list=[1,2,3,4]
print(id(my_list))
# 출력: 136136266260800
print(id(your_list))
# 출력: 136135898770560

 

신기한건 불변 객체인 a,b 는 같은 값을 할당하면 동일한 메모리를 가진다. 즉 a,b가 같은 값이면 같은 객체를 가리키고 있다는 것인데,

 

가변 객체는 동일한 리스트를 할당해도 메모리가 다르다. 초기에는 같은 값을 가지고 있더라도, 각각의 가변 객체는 요소의 추가, 삭제, 변경이  해당 메모리 위치에서 반영되기 때문에 서로 다른 메모리를 가지는 것 같다.

 

 


복사 copy()

 

만약 new1_list = my_list 라 하면, my_list의 요소를 수정하면 원치 않아도 new1_list의 값도 변경된다.

new1_list와 my_list는 같은 객체를 참조하기 때문이다.

my_list=[10,20,30]
print(id(my_list))
# 출력: 136135898951168
new1_list = my_list
print(id(new1_list))
# 출력: 136135898951168
# -> 같은 메모리에 위치 == 동일한 객체

my_list[0] = 40
print(my_list)
# 출력: [40, 20, 30]
print(new1_list)
# 출력: [40, 20, 30]

 

 

이럴때 복사를 사용하면 되는데, 복사는 새로운 메모리 위치에 동일한 내용을 복사한 새 객체를 생성하는 것이다.

복사를 이용하여 new2_list = my_list.copy() 라 하면, 두 리스트는 같은 값을 가지지만 서로 다른 메모리에 위치하여 my_list의 값을 수정해도 new2_list의 값은 변하지 않는다.

my_list=[10,20,30]
print(id(my_list))
# 출력: 136135898695552
new2_list = my_list.copy()
print(id(new2_list))
# 출력: 136135898709440
# -> 다른 메모리에 위치 == 다른 객체

my_list[0] = 40
print(my_list)
# 출력: [40, 20, 30]
print(new2_list)
# 출력: [10, 20, 30]

 

 


 

본래 이 궁금증이 생기게 된 원인으로 돌아오자면

sizes = [[60, 50], [30, 70], [60, 30], [80, 40]]
for s in sizes:
      s.sort(reverse = True) 
      
print(sizes)
# 출력 : [[60, 50], [70, 30], [60, 30], [80, 40]]

 

 

=> for s in sizes 여기에서 s는 sizes의 각 요소를 순회하며 참조한다. 즉 s = sizes[0], sizes[1], sizes[2], sizes[3]을 의미한다. 이때 sort(), remove(), append() 등의 함수는 리스트의 값을 직접 변경하기 때문에 sizes가 바뀌는 것이다.

(초기의 s는  sizes[0] 과 동일한 메모리를 가진다. 위 함수들은 s가 다른 메모리를 가리키게 하는 것이 아니고, 초기 s가 가리키는 메모리에 저장된 리스트 요소 자체를 직접 변경하므로, sizes[0] 값이 수정되고 sizes의 값이 변한다.)

 

만약  s=[1,1]  이런거나 sizes의 요소가 불변 객체였다면, s가 원래는 sizes의 요소를 참조했다가, 새로운 객체( [1,1] or 다른 불변 객체) 를 참조하는 것으로 바뀌게 된다. 따라서 s는 더이상 sizes의 객체를 가리키는 것이 아니기에 sizes는 변하지 않는다.

(s는 sizes[0] 과 동일한 메모리를 가지다가 s가 다른 메모리, 다른 객체를 가리키게 된다.)

 

 

 

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

[Pandas] 기본 함수 정리  (0) 2024.08.16
[Python] 이터러블(iterable) vs 이터레이터(iterator)  (0) 2024.07.21
map()함수  (0) 2024.06.09
lambda()함수  (0) 2024.06.09
리스트 컴프리헨션(list comprehension)  (0) 2024.06.09