파이썬 함수의 기본값은 함수를 정의할 때 한 번 평가되어 그 객체가 계속 재사용된다. 그래서 리스트·딕셔너리 같은 변경 가능한 객체를 기본값으로 쓰면, 호출할 때마다 같은 객체가 공유되어 값이 쌓인다.
def add_item(item, bucket=[]): # 이 리스트는 정의 시점에 딱 하나 만들어진다
bucket.append(item)
return bucket
add_item("a") # ['a']
add_item("b") # ['a', 'b'] ← 새 리스트를 기대했지만 이전 호출이 남아 있다고치는 법
None을 기본값으로 두고 함수 안에서 새로 만든다.
def add_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucketif not bucket:으로 쓰면 호출자가 일부러 넘긴 빈 리스트도 새 리스트로 바뀐다.is None으로 비교한다- 같은 원리로
datetime.now()를 기본값에 쓰면 정의 시각으로 고정된다. 호출 시각이 필요하면 역시None으로 받는다 - 데이터클래스는
field(default_factory=list)로 같은 문제를 피한다
왜 이렇게 동작하나
def 문이 실행될 때 함수 객체가 만들어지고, 기본값은 그 객체의 __defaults__에 저장된다. 함수 본문이 실행될 때마다 다시 계산되지 않는다. 클래스 본문에 리스트를 둔 클래스 속성이 모든 인스턴스에 공유되는 것도 같은 성격의 문제다(파이썬 클래스 기초).
린터(linter: pylint, ruff의 B006 등)가 이 패턴을 잡아 주므로 켜 두면 좋다. 참조를 공유한다는 점은 얕은 복사와 깊은 복사와, 공유된 상태가 예상 밖의 곳에서 바뀌는 문제는 최소 놀람의 원칙와 이어진다. 원인을 좁혀 가는 과정은 최소 재현을 본다.
출처: Python 튜토리얼: Default Argument Values · Python FAQ: Why are default values shared between objects?