Python Mutable Default Argument

김우진·2026년 9월 8일

크래프톤 정글

목록 보기
15/26
post-thumbnail

파이썬에서 함수를 작성하다 보면 다음과 같은 코드를 작성할 수 있다.

def add_item(item, items=[]):
    items.append(item)
    return items

처음 보면 add_item()을 호출할 때마다 items=[]가 실행되어 새로운 빈 리스트가 만들어질 것처럼 보인다.

하지만 실제로는 그렇지 않다.

print(add_item(1))
print(add_item(2))
print(add_item(3))

결과는 다음과 같다.

[1]
[1, 2]
[1, 2, 3]

왜 이런 일이 발생할까?


1. Mutable이란?

mutable은 값을 변경할 수 있는 객체라는 뜻이다.

대표적으로 다음과 같은 자료형들이 mutable이다.

list
dict
set

예를 들어 리스트는 생성된 이후에도 내용을 변경할 수 있다.

a = [1, 2]

a.append(3)

print(a)

결과:

[1, 2, 3]

반대로 다음과 같은 자료형은 대표적인 immutable 객체다.

int
float
str
tuple

즉, Mutable Default Argument를 직역하면

변경 가능한 객체를 함수의 기본 인자로 사용하는 것

이라고 볼 수 있다.


2. Default Argument란?

함수에서 인자를 전달하지 않았을 때 자동으로 사용되는 값을 기본 인자(Default Argument)라고 한다.

예를 들어:

def hello(name="Kim"):
    print(name)

다음과 같이 호출하면:

hello()

name을 직접 전달하지 않았기 때문에 기본값인 "Kim"이 사용된다.

Kim

물론 직접 전달하면 해당 값이 사용된다.

hello("Woojin")
Woojin

3. 문제는 기본 인자가 만들어지는 시점이다

다음 코드를 다시 보자.

def add_item(item, items=[]):
    items.append(item)
    return items

처음에는 보통 이렇게 생각하기 쉽다.

add_item(1)
→ items = []
→ [1]

add_item(2)
→ items = []
→ [2]

하지만 파이썬은 이렇게 동작하지 않는다.

파이썬에서 기본 인자는 함수가 호출될 때가 아니라 함수가 정의될 때 평가된다.

즉,

def add_item(item, items=[]):

이 코드가 실행되는 순간 []가 만들어진다.

개념적으로 보면 다음과 비슷하다.

default_items = []

def add_item(item, items=default_items):
    items.append(item)
    return items

따라서 함수 호출 과정을 보면:

함수 정의
    ↓
[] 생성
    ↓
함수의 기본값으로 저장

그 이후:

add_item(1)
    ↓
저장되어 있던 리스트 사용
    ↓
[1]

다시 호출하면:

add_item(2)
    ↓
아까 사용했던 같은 리스트 사용
    ↓
[1, 2]

또 호출하면:

add_item(3)
    ↓
같은 리스트 사용
    ↓
[1, 2, 3]

즉 호출할 때마다 새로운 리스트가 만들어지는 것이 아니다.


4. 왜 파이썬은 이렇게 동작할까?

이것은 리스트만의 특별한 규칙이 아니다.

파이썬의 기본 인자 자체가 함수 정의 시점에 한 번 평가되도록 설계되어 있기 때문이다.

예를 들어:

def calculate(x=10 + 20):
    return x

함수가 정의될 때:

10 + 20

이 계산이 이루어지고 30이 기본값으로 저장된다.

조금 더 확실하게 확인해보자.

def get_value():
    print("get_value 실행")
    return 10


def test(x=get_value()):
    return x

이 코드를 실행하면 test()를 호출하지 않아도

get_value 실행

이 출력된다.

왜냐하면

def test(x=get_value()):

를 정의하는 순간 get_value()가 실행되기 때문이다.

전체 흐름은 다음과 같다.

함수 정의
    ↓
기본 인자 표현식 평가
    ↓
결과를 함수 객체에 저장
    ↓
나중에 함수 호출
    ↓
인자가 없으면 저장된 기본값 사용

5. 호출 시점에는 무슨 일이 일어날까?

호출 시점에는 새로운 기본값을 만드는 것이 아니다.

파이썬은 대략 다음과 같이 판단한다고 생각하면 된다.

함수를 호출했을 때

인자를 직접 전달했는가?
       ↓
   ┌───┴───┐
  YES      NO
   ↓        ↓
전달한 값   저장된 기본값
사용        사용

예를 들어:

def add_item(item, items=[]):
    items.append(item)
    return items

다음과 같이 호출하면:

print(add_item(1))
print(add_item(2, [100]))
print(add_item(3))

결과:

[1]
[100, 2]
[1, 3]

두 번째 호출에서는 [100]을 직접 전달했으므로 기본 리스트를 사용하지 않는다.

따라서 기본 리스트는 여전히 [1] 상태다.

세 번째 호출에서 다시 기본값을 사용하기 때문에:

[1, 3]

이 된다.


6. 그러면 어떻게 작성해야 할까?

다음과 같이 작성하는 것은 피하는 것이 좋다.

def add_item(item, items=[]):
    items.append(item)
    return items

보통 None을 기본값으로 사용한다.

def add_item(item, items=None):
    if items is None:
        items = []

    items.append(item)
    return items

이제:

print(add_item(1))
print(add_item(2))
print(add_item(3))

결과는:

[1]
[2]
[3]

가 된다.

왜냐하면:

items = []

가 함수 본문 안에 있기 때문이다.

함수 본문은 함수가 호출될 때마다 실행된다.

따라서:

add_item(1)
→ 새로운 [] 생성

add_item(2)
→ 새로운 [] 생성

add_item(3)
→ 새로운 [] 생성

이 된다.


7. 정의 시점 vs 호출 시점

이 차이가 가장 중요하다.

기본 인자

def func(items=[]):
    pass

[] 생성 시점:

함수 정의 시 한 번

함수 내부

def func(items=None):
    if items is None:
        items = []

[] 생성 시점:

함수를 호출할 때마다

따라서 다음 두 코드는 생김새는 비슷하지만 완전히 다르게 동작한다.

# 같은 리스트를 계속 사용할 수 있음
def func(a=[]):
    ...
# 호출할 때마다 새로운 리스트 생성
def func(a=None):
    if a is None:
        a = []

8. int나 str은 왜 괜찮을까?

다음 코드는 일반적으로 문제가 없다.

def func(x=10):
    return x

또는:

def hello(name="hello"):
    return name

10이나 "hello" 역시 함수 정의 시점에 기본값으로 저장된다.

그런데 int, str은 immutable이다.

즉 기존 객체의 내부 값을 변경할 수 없다.

반면 리스트는:

items.append(1)

처럼 같은 객체의 내용을 직접 변경할 수 있다.

따라서 Mutable Default Argument 문제가 발생한다.

핵심은 다음 두 가지 규칙의 조합이다.

  1. 파이썬의 기본 인자는 함수 정의 시 한 번 평가된다.

  2. 리스트와 딕셔너리 같은 mutable 객체는 내용을 변경할 수 있다.

이 두 가지가 합쳐지면서 예상하지 못한 결과가 발생하는 것이다.


9. 주의해야 하는 기본 인자

다음과 같은 코드는 주의해야 한다.

List

def func(a=[]):
    ...

Dictionary

def func(a={}):
    ...

Set

def func(a=set()):
    ...

보통 다음과 같이 작성하는 것이 안전하다.

def func(a=None):
    if a is None:
        a = []

Dictionary라면:

def func(a=None):
    if a is None:
        a = {}

Set이라면:

def func(a=None):
    if a is None:
        a = set()

10. OX 퀴즈

문제 1

다음 함수에서 []는 func()를 호출할 때마다 새롭게 생성된다.

def func(a=[]):
    return a

정답: X

[]는 함수가 호출될 때가 아니라 함수가 정의될 때 한 번 생성된다.


문제 2

다음 코드의 결과는 [1], [2]이다.

def func(a=[]):
    a.append(1)
    return a

print(func())
print(func())

정답: X

결과:

[1]
[1, 1]

두 호출 모두 같은 기본 리스트를 사용한다.


문제 3

리스트를 기본 인자로 사용하면 무조건 문법 오류가 발생한다.

def func(a=[]):
    pass

정답: X

문법적으로는 완전히 정상이다.

다만 기본 리스트를 수정하면 예상하지 못한 문제가 발생할 수 있기 때문에 일반적으로 피하는 것이다.


문제 4

다음 방식은 Mutable Default Argument 문제를 피하기 위해 흔히 사용하는 방법이다.

def func(a=None):
    if a is None:
        a = []

정답: O

a = []가 함수 본문에서 실행되므로 함수를 호출할 때마다 새로운 리스트가 생성된다.


문제 5

다음 코드에서 두 번째 func()는 첫 번째 호출에서 수정된 리스트를 다시 사용한다.

def func(a=[]):
    a.append(10)
    return a

func()
func()

정답: O

기본 리스트는 함수 정의 시 한 번 만들어지고 계속 재사용된다.


문제 6

int, str 같은 값을 기본 인자로 사용하는 것도 리스트와 똑같은 문제가 발생한다.

def func(x=10):
    ...

정답: X

int, str은 immutable 객체이기 때문에 리스트의 append()처럼 기존 객체 자체의 내용을 수정할 수 없다.

따라서 일반적인 기본값으로 안전하게 사용할 수 있다.


문제 7

다음 코드에서 [100]은 함수의 기본 리스트에 영향을 준다.

def func(x, arr=[]):
    arr.append(x)
    return arr

func(1)
func(2, [100])
func(3)

정답: X

두 번째 호출에서는 [100]이라는 별도의 리스트를 직접 전달했다.

따라서 기본 리스트와 별개의 객체다.

결과는:

func(1)          # [1]
func(2, [100])   # [100, 2]
func(3)          # [1, 3]

이다.


문제 8

파이썬의 기본 인자에 들어가는 표현식은 함수 정의 시점에 평가된다.

정답: O

예를 들어:

def get_value():
    print("실행")
    return 10


def func(x=get_value()):
    pass

func()를 호출하지 않아도 def func(...)가 실행되는 시점에 get_value()가 호출된다.


핵심 정리

Mutable Default Argument를 이해하기 위해서는 다음 한 문장을 기억하면 된다.

파이썬의 기본 인자는 호출할 때마다 만들어지는 것이 아니라 함수 정의 시점에 한 번 평가되어 저장된다.

따라서:

def func(a=[]):

에서 []는 호출할 때마다 초기화되지 않는다.

같은 리스트가 계속 사용된다.

반면:

def func(a=None):
    if a is None:
        a = []

에서는 a = []가 함수 본문에 있기 때문에 호출할 때마다 실행된다.

최종적으로 다음처럼 기억하면 된다.

기본 인자에 [] 사용
→ 정의할 때 한 번 생성
→ 같은 객체 재사용

함수 내부에서 [] 생성
→ 호출할 때 실행
→ 매번 새로운 객체 생성

한 줄 요약

def func(a=[])의 []는 호출 시점이 아니라 정의 시점에 만들어진다.

0개의 댓글