우선 이글을 읽기전 참조값에 대해 다시 한번 개념 확인 후 읽기를 바란다.
참조값은 메모리에서 리스트 객체의 주소값을 의미한다.
변수 값이 저장되는 순서 : 값(Value) -> 참조값(Reference) -> 메모리(Memory)
얕은 복사(Shallow copy)
Python의 list 변수의 가장 큰 특징
리스트 개체를 직접 저장하지 않는 다는 점이다. 다른 곳에 저장되고 리스트의 참조값(reference)만 변수에 저장된다.
복사에 대한 일반적인 정의를 먼저 알아보자. Ctrl+C , Ctrl+V를 우리는 통상 복사, 붙여넣기라 한다. A라는 파일을 복사한 후 붙여넣기하면 A와 동일하지만 이름만 다른 파일인 A_1이 생겨난다. 이때, 생김새만 동일하고, A를 수정한다고 해서 A_1이 수정되지 않는다. 즉, A와 A_1은 독립이다.
리스트를 복사하면 어떤일이 벌어질까? 아래 예시를 보자.
value = [1,2,3,4,5]
value_copy = value
과연 value_copy에 value값이 복사 붙여넣기가 되었을까?
value값을 수정해보겠다. 우리가 예상하는결과는 value_copy의 결과의 원소는 1,2,3,4,5로 바뀌지 않는 것이다.
value[2] = 0
value_copy
## value_copy 출력 결과 : [1, 2, 0, 4, 5]
하지만, value_copy를 출력해보면 value의 3번째 자리를 0으로 바꾸었을 때, value_copy도 동일하게 바뀌어 있는 것을 확인할 수 있다. 이러한 이유는 무엇일까?
list 변수의 특징을 좀 더 디테일하게 적어보겠다. 다른 곳에 저장되고 리스트의 참조값(reference)만 변수인 별칭에 저장된다. 즉, value는 리스트 [1,2,3,4,5]의 참조값을 별칭에 저장한 것 뿐이고, 이러한 참조값을 복사하여 다른 별칭인 value_copy에다가 저장한 것 뿐이다. 따라서, value의 리스트 객체를 바꾼다면 value_copy는 그대로 value의 주소를 가져오기 때문에 수정된 value값을 가져온 것이다.
자 그렇다면, 리스트를 복사+붙여넣기를 제대로 하려면 어떻게 해야 될까?
깊은 복사(Deep copy)
복사+붙여넣기를 하는 것을, 깊게 복사한다 하여 깊은 복사라 부른다. 방법은 총 3가지가 있다.
방법1) deepcopy() 또는 copy()메서드를 이용한다. 이 두 함수는 내장 함수로, 리스트를 받아서 복사본을 생성하여 반환한다. 깊은 복사를 하게되면 다른 주소값을 가지게 되어 원본 리스트에는 영향을 끼치지 않는다.
value = [1,2,3,4,5]
value_deep_copy = value.copy()
value[2] = 0
value_deep_copy
## value_deep_copy 결과 : [1, 2, 3, 4, 5]
방법2) [:] 인덱스를 이용한다.
value = [1,2,3,4,5]
value_deep_copy2 = value[:]
value[2] = 0
value_deep_copy2
### value_deep_copy2 결과 : [1, 2, 3, 4, 5]
얕은, 깊은 복사를 변수의 값과 함수 매개변수의 관계로 알아보자.
얕은, 깊은 복사는 함수 내에서도 자주 이루어지는 작업이다.
함수에서 인수(매개변수)를 호출하여 불러오는 과정 :
1) 호출하여 다른 변수로 별도 저장하기(Call-by-Value) : b가 수정된다해서 a의 변화가 일어나지 않음(a ≠ b)
- 호출 변수(a) -> 참조값(Reference) -> 메모리(Memory) -> 값(value) -> 다른 변수(b)에 저장
2) 호출하여 다른 변수에 참조로 저장하기(Call-by-Reference) : b가 수정되면 a의 변화가 일어남(a = b)
- 호출 변수(a) -> 참조값(Reference) -> 다른 변수(b)에 저장
1)의 과정은 깊은 복사, 2)의 과정은 얕은 복사에 해당된다.
함수 매개변수가 깊은 복사가 되는 경우
- 호출 변수(a) -> 참조값(Reference) -> 메모리(Memory) -> 값(value) -> 다른 변수(b)에 저장
예시를 통해 함수에서의 깊은 복사 과정을 살펴보겠다.
def func1(x) :
print("함수내 y값",x,"id=",id(x))
x=0 # 새로운 객체 생성
print("함수내 y값",x,"id=",id(x))
return(x)
위와 같은 함수가 있다고 하자. 매개변수 x를 받고 print를 한 뒤, x에 새로운 값을 대입하고 또 다시 print를 하는 함수이다.
y = 11
print("본래 y 값",y,"id=",id(y))
after_y = func1(y)
print("함수 이후 y값",y,"id=",id(y))
print("after_y",after_y,"id=",id(after_y))
하나하나 뜯어보자.
y=11
y의 변수에 11이라는 값을 대입하자. 대입하는 순간 y의 변수는 별칭, 참조값이 생기며 이 참조값 위치의 메모리에 값이 저장된다. id()함수를 통해 참조값 확인이 가능하다.
print("본래 y 값",y,"id=",id(y))
### 결과 : 본래 y 값 11 id= 140735601615568
y의 참조값은 위와 같음을 알 수 있다.
아래의 함수를 실행해 보자. 그러면 x에 y가 대입되며 아래와 같은 변화가 일어난다.
func1(y)
-
print("함수내 y값",y,"id=",id(y)) ### 결과값 : 함수내 x값 11 id= 140735601615568
-
y=0 # 새로운 객체 생성 print("함수내 y값",y,"id=",id(y)) ### 결과값 : 함수내 y값 0 id= 140735601615216 return(y) ### return된 after_y에 저장되는 값 : 0 id= 140735601615216
print("함수내 y값",y,"id=",id(y))
### 결과값 : 함수 이후 y값 11 id= 140735601615568
위 결과값들을 살펴보면, 함수의 매개변수 개체 y가 x값에 전달되었고, 바뀐 y값이 함수 중간 과정에서 0으로 값이 바뀌였으나 여전히 y의 결과 값은 11을 유지하는 것을 볼 수 있다. 이는 정수나 문자열이 변경 불가능한 객체이기 때문이다.
y=0 과정에서 새로운 객체가 생성되었고 이 객체의 참조값이 after_y변수에 저장되었기에, y와 완전히 구분되었다.
함수 매개변수가 얕은 복사가 되는 경우
- 호출 변수(a) -> 참조값(Reference) -> 다른 변수(b)에 저장
아래 예시를 바로 결과를 살펴보겠다.
def func2(list) :
list[0] = 999
return(list)
values = [1,2,3,4,5]
print(values) ### 결과값 : [1, 2, 3, 4, 5]
func2(values)
print(values) ### 결과값 : [999, 2, 3, 4, 5]
함수의 매개변수인 values 리스트의 참조값만이 list값에 전달되었고, 참조값의 첫번째자리가 999로 값이 바뀌었다. 그 결과, func2 함수가 실행 된 후 결과를 보면 [999,2,3,4,5] 결과를 반환한다는 것을 알 수 있다. 이는 리스트는 참조값으로 전달되었기 때문에 함수 안에서 리스트의 요소들의 변경이 가능하다.
'Python > Coding Base' 카테고리의 다른 글
15. 리스트(List) 4 - 다양한 리스트 알고리즘 (0) | 2022.01.30 |
---|---|
14. 리스트(List) 3 - 리스트 함축(list comprehensions) (0) | 2022.01.30 |
12. 리스트(List) 1 - 리스트와 시퀀스(sequence) (0) | 2022.01.23 |
11. 함수(function) 4 - 람다식(무명 함수), 모듈 (0) | 2022.01.23 |
10. 함수(function) 3 - 참조 값, 지역 변수, 전역 변수 (0) | 2022.01.23 |