[Python] Call by Assignment 이해하기

2022. 4. 3. 23:48·◼ IT Etc./Python
반응형

 

Python은 함수에 인수를 전달할 때 Call by Assignment 방식으로 전달된다.

이 말은 무슨 의미일까?

 

 

mutable VS immutable

우선 mutable과 immutable의 개념부터 짚고 넘어가면, python에선 모든 것이 객체이며 총 2가지 종류가 있다.

  • immutable 객체 : 값이 변경 불가능한 객체
    • int, float, str, tuple 등
  • mutable 객체 : 값이 변경 가능한 객체 (단, 자신의 id값은 일정하게 유지)
    • list, dictonary, set 등

 

 

Python에서의 Call By Value VS Call By Reference

  • immutable 객체가 함수의 인자로 전달되면 마치 값이 복사되어 전달되는 Call By Value처럼 동작한다. (원본값 영향 X)
def swap(a, b):
    a, b = b, a
    print(a, b)

n, m = 10, 20
swap(n, m)
print(n, m)
20 10
10 20

 

  • 반면, mutable한 객체가 함수의 인자로 전달되면 마치 값이 주소가 전달되는 Call By Reference처럼 동작한다. (원본값 영향 O)
def swap(lst):
    lst[0], lst[1] = lst[1], lst[0]
    print(lst[0], lst[1])

lst = [10, 20]
swap(lst)
print(lst[0], lst[1])
20 10
20 10

 

  • 즉 python에선, C++과 달리, Call By Value와 Call By Reference를 명시적으로 할 수 없는 것이다. (인수의 자료형에 의해 결정)

 

 

Call By Assignment (Call By Object Reference)

  • 사실 python에선 함수에 인자를 넘겨줄 때 Call By Value나 Call By Reference와는 조금 다르게 동작한다.
  • 다시 한 번 말하자면, python에선 모든 것이 객체이다.
  • ✨ 즉, 변수에 어떤 값을 할당할 때, 실제로 값들은 변수 내에 저장되는 것이 아니라, 1, 2와 같은 객체가 생성되고 변수가 그 객체를 가리키는 것이다. (변수 이름을 '이름표'에 비유)

 

a = 1
c = 1
  • 위 코드에서 a와 c 두 개의 변수는 1이라는 하나의 객체를 같이 가리키고 있다고 이해하면 된다.

 

 

🗨 그럼 immutable 객체와 mutable 객체가 각각 함수 인자로 넘어갈 때 Python에서 코드는 어떻게 동작할까?

  • 우선 immutable 객체를 함수의 인자로 넘기면, 이 객체는 '불변'이기 때문에 함수 안에서 새로운 값을 생성한다. 이는 마치 Call By Value 처럼 보인다.
  • 아래 코드를 살펴보자.
def func(c):
    c = 2

a = 1
func(a)
  • a는 1이라는 immutable한 객체를 가리키고 있다.
  • func 함수에 변수 a를 인자로 전달하면, c라는 지역 변수도 a가 가리키는 객체 1을 같이 가리키게 된다.
  • 여기서 함수 내 지역 변수 c의 값을 2로 바꾸면, 정수는 immutable하므로 실제 c의 값이 1에서 2로 바뀌는 것이 아니라, 2라는 객체가 새로 생성되고 1을 가리키던 지역 변수 c가 2를 가리키게 된다.

 

  • 참고로 어떤 두 변수 x, y가 같은 값을 가리키면, x와 y의 id 또한 동일해진다.
def func(c):
    print(id(c))
    c = 2;  print(id(c))

a = 1;  print(id(a))
b = 2;  print(id(b))
func(a)
1570728274224
1570728274256
1570728274224
1570728274256

 

  • 그러나 mutable 객체를 함수의 인자로 넘기면, 새 객체 생성 없이, 다른 변수를 통해서도 기존 객체에 접근하여 값을 수정할 수 있다. 이는 마치 Call By Reference 처럼 보인다.
  • 아래 코드를 살펴보자.
def func(arr):
    arr.append(5)

a = [1, 2, 3, 4]
func(a)
  • a가 mutable한 객체 [1, 2, 3, 4]를 가리키고 있다.
  • func 함수가 실행되면, 지역 변수 arr도 a와 같은 객체 [1, 2, 3, 4]를 가리킨다.
  • 여기서 arr.append(5)를 하게 되면, 리스트는 mutable하기 때문에 5가 추가된 새로운 객체가 생성되는 것이 아니라, 기존의 동일한 id를 가지는 리스트에 5가 추가된다.
  • 따라서, 함수가 종료되고 지역 변수인 arr가 사라지고 난 후에도 a는 여전히 [1, 2, 3, 4, 5]를 가리키게 된다.

 

  • 그럼 아래 코드에선 어떤 일이 일어날까?
def func(arr):
    arr = [5, 6]

a = [1, 2, 3, 4]
func(a)
  • 함수가 실행되면 arr도 a와 동일하게 [1, 2, 3, 4]를 가리킨다.
  • arr = [5, 6]이 실행되면, [5, 6]이라는 새로운 리스트 객체가 생성되고, arr는 이를 가리키게 된다.
  • 따라서, 함수가 종료된 이후에도 a는 여전히 [1, 2, 3, 4]를 가리키고 있는 것이다.
  • 이렇게 아무리 immutable한 객체라 하더라도 기존과 다른 새 값을 할당하면, 원본에는 영향을 미치지 않는다.

 

 

immutable 객체가 들어있는 변수에 새로운 immutable 객체를 할당해도 괜찮은 이유

(지금부터 편의상 immutable한 값이 할당된 변수를 'immutable 변수'라 부른다.)

  • immutable 변수는 레퍼런스가 가리키는 데이터의 값을 변경할 수 없다.
string = "Hello"

string[5] = 'a'

print(string)
TypeError: 'str' object does not support item assignment

 

  • 따라서 위처럼 immutable 변수인 문자열을 수정하려 하면 에러가 발생한다.
  • 그러나 immutable 변수의 값을 아래처럼 통째로 바꾸는 것은 아무런 에러 없이 잘 동작한다.
string = "Hello"
string = "World"

a = 10
a = 20

print(string)
print(a)
World
20
  • 이는 값이 변경되선 안되는 immutable 객체의 값이 변경된 것처럼 보인다.
  • 하지만 실제론 immutable 객체의 값은 변경되지 않았다. 왜 그런걸가? 그 원리를 아래에서 살펴보자.

 

  • 우선, 파이썬은 id(객체) 문법으로 객체의 id를 가져올 수 있다. (참고로 id는 메모리 상에서가 아닌 VM상에서의 위치를 의미한다.)

이 때 id는 하나의 값에 대응되므로

  • 어떤 변수를 값을 바꾸고 다시 원래대로 돌려놓으면, 그 변수는 처음과 동일한 id를 가진다.
  • 어떤 두 변수가 같은 값을 가지면 두 변수(객체)의 id는 동일해진다.
a = 200; print(id(a))
a = 300; print(id(a))

a = 200; print(id(a))
a = 300; print(id(a))

b = 200; print(id(b))
b = 300; print(id(b))
2623010923152
2623039916720
2623010923152
2623039916720
2623010923152
2623039916720

 

  • 즉, (파이썬에서) immutable한 값이 들어있는 어떤 변수 a에 다른 immutable한 값을 할당하면, C언어처럼 기존 값이 지워지고, 새로 할당 받은 값이 a에 저장되는 것이 아니라
    • ✨ 새로운 메모리 공간을 할당 받고 그 곳에 새 값이 저장된 후 변수 a가 이를 가리키게 되는 것이다. 그리고 기존값은 지워지지 않고 그대로 메모리 상에 잔류한다. 만일 여기서 a에 처음 값을 재할당하면, 변수 a는 다시 처음 변수를 가리키고, 처음의 id값을 가지게 된다.

 

  • 즉, 겉으로 봤을 땐 immutable 객체는 그 값이 변경된 것처럼 보여도, 실은 변수가 가리키는 부분만 달라진 것이고, 변수가 가리키는 부분의 값은 그대로이다.

 

 


< 참고 자료 >
Python은 Call by reference일까? Call by value일까? (개인 블로그),
Python - Call by Object Reference (개인 블로그),
Python 은 call-by-value 일까 call-by-reference 일까 (개인 블로그),
How do I pass a variable by reference? (StackOverflow)

 

반응형

'◼ IT Etc. > Python' 카테고리의 다른 글

[Python] 변수 Scope (전역 변수와 지역 변수) 에 대한 이해  (2) 2023.02.02
[Python] 조건문  (0) 2023.01.21
[Python] 사칙연산을 위한 연산자  (0) 2023.01.21
[Python] 파이썬 입력 함수 input 사용법 정리  (0) 2022.03.17
[Python] 파이썬 출력 함수 print 사용법 정리  (1) 2022.03.16
'◼ IT Etc./Python' 카테고리의 다른 글
  • [Python] 조건문
  • [Python] 사칙연산을 위한 연산자
  • [Python] 파이썬 입력 함수 input 사용법 정리
  • [Python] 파이썬 출력 함수 print 사용법 정리
SangYoonLee (SYL)
SangYoonLee (SYL)
Slow, But Steady Wins The Race 😎
    반응형
  • SangYoonLee (SYL)
    ◆ Slow, But Steady ◆
    SangYoonLee (SYL)
  • 전체
    오늘
    어제
    • ◻ 전체 글 수 : (131) N
      • ✪ 취미, 경험 회고 및 일상 (26) N
        • [취미] Room Escape (2)
        • [회고] IT 관련 경험 회고 (18) N
        • [일상] 일상 생각 (4)
        • [일상] 독후감 (1)
      • ◼ FrontEnd (29) N
        • Web & HTML, CSS (8)
        • JavaScript (4) N
        • TypeScript (1)
        • ReactJS (16)
      • ◼ CS (3)
        • 자료구조 & 알고리즘 (1)
        • 컴퓨터 구조 (1)
        • 운영체제 (1)
      • ◼ PS Note (40)
        • 백준 (38)
        • 프로그래머스 (2)
      • ◼ IT Etc. (33)
        • (Until 2021) (21)
        • Python (6)
        • C | C# | C++ (1)
        • Git (1)
        • Unity (4)
        • Game Dev. (0)
  • 블로그 메뉴

    • 홈
    • 💻 GitHub
    • 🟢 Velog
    • 🧩 온라인 방탈출 출시 작품 모음
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    프로젝트
    Python
    코드숨
    unity
    C++
    CodeSoom
    유니티
    Cpp
    리엑트
    프로그래머스
    후기
    회고
    개인 프로젝트
    백준
    위코드
    JavaScript
    pygame
    미궁 게임
    wecode
    알고리즘
    관심사의 분리
    주간 회고
    더라비린스
    소수 구하기
    1929
    Component
    코딩 일기
    React
    파이썬
    방탈출고사
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
SangYoonLee (SYL)
[Python] Call by Assignment 이해하기
상단으로

티스토리툴바