함수와 매개변수
본문두 함수를 만들었다. 하나는 숫자를 받아 그 매개변수에 5를 넣고, 다른 하나는 리스트를 받아 그 리스트에 원소 하나를 덧붙인다. 함수를 부른 뒤 밖에서 값을 확인하면, 숫자는 그대로인데 리스트에는 원소가 늘어 있다. 둘 다 함수 안에서 바꾸었는데 왜 결과가 다를까? 함수를 부르는 순간 컴퓨터 안에서는 무슨 일이 일어나고, 넘긴 값은 함수 안에서 어떻게 쓰일까?
1.1.1함수 호출과 스택 프레임
함수는 이름을 붙인 코드 묶음이다. 함수를 부르면 컴퓨터는 지금 실행하던 위치를 기억해 두고, 그 함수만을 위한 작업 공간을 새로 만든다. 이 작업 공간을 스택 프레임이라 한다. 프레임에는 넘겨받은 값을 담는 매개변수, 함수 안에서 만든 지역 변수, 끝나고 돌아갈 위치가 들어 있다. 함수 안에서 또 다른 함수를 부르면 그 위에 새 프레임이 쌓인다. 함수가 값을 돌려주며 끝나면 맨 위 프레임이 사라지고, 기억해 둔 위치로 돌아가 이어서 실행한다. 프레임들이 쌓인 모습을 호출 스택이라 한다.
함수를 부를 때 인수의 값이 매개변수라는 새 이름에 연결된다. 매개변수에 새 값을 대입하면 그 이름이 새 값을 가리킬 뿐, 밖의 변수는 바뀌지 않는다. 그런데 리스트처럼 바뀔 수 있는 객체를 넘기면 매개변수와 밖의 변수가 같은 객체를 가리키므로, 함수 안에서 객체 자체를 고치면(예를 들어 append) 밖에서도 바뀐 모습이 보인다.
그림 1에서 호출 깊이를 바꾸어 보자. main이 평균(점수)을 부르면 평균의 프레임이 쌓이고, 평균이 합계(점수)를 부르면 그 위에 또 쌓인다. 각 프레임의 지역 변수는 그 프레임 안에서만 쓰여, 이름이 같아도 서로 섞이지 않는다. 그래서 함수를 나누어 만들어도 변수 이름이 부딪치지 않는다. 그리고 함수가 끝나면 그 지역 변수도 프레임과 함께 사라진다. 처음 질문의 숫자는 함수 안의 매개변수만 새 값을 가리키다가 프레임과 함께 사라진 것이다.1
1.1.2좋은 함수의 조건
함수를 잘 나누면 같은 코드를 되풀이해 쓰지 않아도 되고, 고칠 곳이 한 군데로 모인다. 좋은 함수는 한 가지 일만 하고, 이름만 보고도 무엇을 하는지 알 수 있다. 필요한 값은 매개변수로 받고 결과는 return으로 돌려주어, 함수 밖의 변수에 몰래 기대지 않는다. 그러면 같은 입력에 늘 같은 출력이 나와 시험하기 쉽다. 함수 안에서 넘겨받은 리스트를 고쳐야 한다면, 그 사실을 함수 이름이나 설명에 밝혀 두어야 부르는 쪽이 헷갈리지 않는다. 기본값을 준 매개변수는 자주 쓰는 값을 생략하게 해 준다.
1.1.3예제 풀이
함수 def 덧붙이기(목록, 값=0): 목록.append(값); return len(목록)와 def 두배(x): x = x * 2; return x가 있다. a = [1, 2], b = 3일 때 n = 덧붙이기(a, 5), m = 두배(b), k = 덧붙이기(a)를 차례로 실행한 뒤 a, b, n, m, k의 값을 구하여라.
덧붙이기(a, 5)에서 매개변수 목록은 a와 같은 리스트를 가리키므로 append로 그 리스트에 5가 붙어 a는 [1, 2, 5]가 되고, n은 3이다. 두배(b)에서 x는 3에서 시작해 6을 새로 가리키고, 6을 돌려주므로 m은 6이지만 b는 그대로 3이다.
덧붙이기(a)는 값을 생략했으므로 기본값 0이 쓰여 a는 [1, 2, 5, 0], k는 4가 된다. 따라서 a = [1, 2, 5, 0], b = 3, n = 3, m = 6, k = 4이다. 즉 함수 밖의 값을 바꾸고 싶다면 결과를 return으로 돌려받아 다시 대입하는 것이 가장 분명하다. 그래서 b를 바꾸려면 b = 두배(b)처럼 쓴다.
아래 탐구 카드에서 매개변수와 지역 변수가 호출 스택에서 어떻게 생기고 사라지는지 따라가 보자.
- 파이썬은 호출 스택이 너무 깊어지면 기본으로 약 1000단계에서 RecursionError를 낸다. 끝나지 않는 재귀가 메모리를 다 쓰지 않게 막는 안전장치이다.
함수를 부르면 매개변수, 지역 변수, 돌아갈 위치를 담은 스택 프레임이 호출 스택에 쌓이고, 함수가 끝나면 그 프레임이 사라진다. 매개변수는 인수의 값에 연결된 새 이름이라, 새 값을 대입하면 밖에 영향이 없지만 리스트처럼 바뀔 수 있는 객체를 고치면 밖에서도 보인다. 그래서 처음 질문의 숫자는 함수 안의 이름만 새 값을 가리키다 사라졌고, 리스트는 밖과 같은 객체를 고쳐 바뀐 모습이 남았다.