노트

가변 기본 인자 함정

Mutable Default Argument

CS#python#debugging · 연결된 개념 5개

쉽게 말하면

가변 기본 인자 함정은 손님마다 새 컵을 줄 줄 알았는데, 개업 날 만든 컵 하나를 계속 돌려 쓰는 카페 같아요. 앞 손님이 남긴 게 다음 손님 컵에 그대로 쌓여요.

비유가 깨지는 곳 파이썬 기본값은 def가 실행될 때 딱 한 번 만들어져 __defaults__에 남아요. 그래서 기본값은 None으로 두고, 함수 안에서 is None일 때 새로 만들어요.

파이썬 함수의 기본값은 함수를 정의할 때 한 번 평가되어 그 객체가 계속 재사용된다. 그래서 리스트·딕셔너리 같은 변경 가능한 객체를 기본값으로 쓰면, 호출할 때마다 같은 객체가 공유되어 값이 쌓인다.

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 bucket
  • if 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?

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 파이썬 기본 컬렉션

    파이썬의 기본 컬렉션은 리스트·튜플·딕셔너리·셋 네 가지다. 가장 중요한 구분은 변경 가능 여부(mutable·immutable)와 순서·중복을 어떻게 다루는가다.

  • 파이썬 스코프 (LEGB)

    파이썬은 이름을 찾을 때 Local → Enclosing → Global → Built-in 순서로 스코프를 뒤진다. 앞 글자를 따 LEGB(Local, Enclosing, Global, Built-in) 규칙이라 부른다. 함수 안에서 만든 변수는 그 함수 안에서만 살고, 바깥 함수의 변수는 안쪽 함수가 읽을 수 있다.

  • 매개변수 객체 만들기

    여러 함수에 늘 함께 넘겨지는 값 묶음(데이터 뭉치, Data Clumps)을 하나의 객체로 묶어 넘기는 리팩터링.

  • 값 객체

    식별자 없이 담고 있는 값 자체로 같음을 판단하는 불변 객체. 금액, 기간, 주소, 이메일처럼 "어떤 것"이 아니라 "얼마·무엇"을 나타내는 개념에 쓴다. 식별자로 같음을 판단하고 상태가 바뀌는 엔티티(Entity)와 대비된다(ddd).

  • 코드 냄새

    당장 버그는 아니지만 이해나 변경 비용을 높이는 구조적 신호. 리팩터링을 언제 시작하고 멈출지에 정확한 공식은 없어서, 냄새라는 어휘로 직관을 공유한다.

보기 옵션