Python 함수의 기본 인자는 함수가 호출될 때마다 새로 계산되지 않습니다. 함수 정의가 실행될 때 기본값 객체가 만들어지고, 호출자가 해당 인자를 생략하면 그 객체가 사용됩니다. 기본값으로 빈 리스트를 두고 내용을 바꾸면 다음 호출에서도 앞서 바꾼 내용이 남습니다.
호출마다 새 리스트가 필요하면 None을 기본값으로 두고 함수 안에서 새 리스트를 만들 수 있습니다. Python 3.12 기준을 설명하며, 기본 동작과 호출자가 직접 리스트를 전달한 경우를 구별합니다.
기본값 객체는 함수 정의 때 평가됩니다
함수 정의를 실행하면 빈 리스트 기본값이 한 번 만들어집니다. 인자를 생략한 호출은 이 객체를 사용하고 append 같은 연산으로 내용을 바꿀 수 있습니다. 다음 생략 호출에서도 앞서 바뀐 내용이 이어집니다. 숫자나 문자열처럼 불변인 기본값은 같은 객체가 사용되어도 내부 상태를 바꾸는 연산이 없어 문제가 잘 드러나지 않습니다. 리스트와 딕셔너리는 변경할 수 있으므로 호출 경계를 넘어 값이 누적되는 차이를 추적해야 합니다.
두 번 호출하면 항목이 누적됩니다
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
print(add_tag(1))
print(add_tag(2))| 호출 | 추가 전 상태 | 호출 결과 |
|---|---|---|
| add_tag(1) | [] | [1] |
| add_tag(2) | [1] | [1, 2] |
두 번째 결과가 [2]가 아니라 [1, 2]인 까닭은 두 호출이 한 기본 리스트를 함께 사용하기 때문입니다. 이 동작은 Python이 호출 때마다 기본 객체를 복사한다는 가정을 반박합니다. 호출자가 직접 만든 리스트를 전달하면 함수는 전달된 객체를 수정합니다. 따라서 문제에서 인자를 생략했는지, 객체를 명시적으로 전달했는지도 함께 표시해야 합니다.
호출마다 독립된 목록이 필요하면 None을 사용합니다
def add_tag(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tags- 함수 호출에서 생략 가능한 인자를 찾습니다.
- 기본값이 리스트·딕셔너리 등 변경 가능한 객체인지 확인합니다.
- 인자가 생략되면 어느 객체를 사용하는지 추적합니다.
- 공유 누적이 의도된 동작이 아니라면 None을 기본값으로 두고 함수 안에서 새 객체를 만듭니다.
매개변수 기본값과 함수 안의 지역 변수는 다릅니다
None 기본값은 함수 안에서 필요할 때 목록을 만든다는 신호로 쓰입니다. 이 방법은 호출자가 이미 만든 목록을 전달했을 때 그 객체를 그대로 사용하고, 생략했을 때만 새 목록을 할당합니다. 공유 목록을 함수 정의 시 기본값으로 두는 것이 언제나 금지되는 것은 아니며 호출 간 누적이 의도된 경우도 있을 수 있습니다. 그러나 일반적인 함수 인자에서는 숨은 공유 상태가 테스트 순서에 따라 결과를 바꾸기 쉬우므로 기본값으로 변경 가능한 객체를 두지 않는 편이 안전합니다. 함수 정의가 모듈에서 다시 실행될 때 기본값이 다시 만들어질 수 있다는 설명도 호출마다 새 객체라는 뜻은 아닙니다.
같은 함수 객체의 기본 리스트를 초기화하지 않은 채 여러 테스트에서 재사용하면 앞선 테스트의 호출이 다음 결과에 영향을 줄 수 있습니다. 각 테스트에서 함수를 다시 정의하거나 기본 리스트를 실제로 초기화하면 테스트 간 공유 상태는 끊어지지만, 한 테스트 안에서 같은 함수의 인자 생략 호출을 반복하면 그 호출끼리는 기본 리스트를 공유합니다. 원인을 찾을 때는 입력 인자를 생략했는지와 호출 순서를 함께 기록합니다. None 방식으로 고친 뒤에는 인자를 생략한 호출 두 번이 각각 독립된 목록을 반환하는지 확인하고, 명시적으로 목록을 전달한 호출은 전달한 객체에 값을 추가하는지 별도로 시험하세요.
정답을 가리고 확인해 보세요
이 글을 위해 만든 학습 예시이며 공식 기출문제나 판매 카드의 인용이 아닙니다.
기본값이 []인 함수에서 첫 생략 호출은 1을 더하고 다음 생략 호출은 2를 더합니다. 두 번째 반환값은 무엇인가요?
정답과 해설 확인
[1, 2]입니다.Python 3.12에서 기본 인자는 함수 정의 때 평가되고 같은 기본 리스트가 호출 사이에 사용됩니다. None sentinel을 사용하면 생략 호출마다 새 목록을 생성할 수 있습니다.