mutable한 변수를 함수의 디폴트 매개변수로 사용 시 의도치않은 동작이 일어날 수 있다.
mutable 하다는 것은 변수에 할당된 값을 변경할 수 있다는 의미이다.
다음 예제를 보자.
>>> def func(a=[]):
... a.append(5)
... return id(a)
>>>
>>> func.__defaults__
([],)
>>> func()
4516110408
>>> func()
4516110408
>>> func([3])
4516185480
>>> func.__defaults__
([5, 5],)
>>> id(func.__defaults__[0]) == func()
True
예제를 통해 알 수 있는 건,
__defaults__
키워드로 내부에 저장된 기본 매개변수 값을 볼 수 있다.func
함수는 mutable 한 기본 매개변수를 갖기때문에 a에 값을 주지 않은 채로 호출 하면 값이 누적된다. 실제로 서비스 중 문제를 겪었던 코드는 대략 다음과 같았다.
from datetime import date
def get_semseter_with_year(date_info=date.today()):
return date_info.year, get_semseter(date_info)
파라미터를 넣지않으면 datetime 에서 현재시각을 얻어와서 연도/학기 정보를 주는 함수다.
실제 시간 기준으로 2019년 12월에 해당할 때는 문제를 인지하지 못했고,
해가 바뀌어 새 학기가 열리는 시점에 문제가 터졌다.
호출부에서 파라미터를 넘기지않아 현재 날짜 기준으로 함수가 동작해야하는데,
잘못된 값을 리턴해서 버그가 생긴 것이다.
이유는 함수가 정의되는 시점에 함수의 결과가 저장된다.
print(get_semester_with_year.__defaults__)
함수의 __defaults__
키워드를 출력해보면
함수 정의 시점의 datetime 객체가 들어있음이 확인 가능하다.
def get_semester_with_year(date_info=None):
if date_info is None:
date_info = date.today()
return date_info.year, get_semester(date_info)
의도대로 동작하도록 이렇게 수정하여 해결할 수 있다.
결론은 다음과 같다.
기본매개변수에 함수 호출부를 지정한 경우, 실행 결과가 저장될 뿐이다.
>>> def bar(a=input('함수 호출없이 이 메시지가 보일겁니다. 값을 입력해보세요 : ')):
... return a
...
함수 호출없이 이 메시지가 보일겁니다. 값을 입력해보세요 : hello world!!
>>> bar(123)
123
>>> bar()
'hello world!!'
>>>
위 처럼 함수만 선언해도 input 메시지를 볼수 있다.
a에 값 전달 없이 bar함수를 호출하면 bar.__defaults__
에 저장된 방금 입력한 값이 계속 리턴됨을 확인할 수 있다.
Django Model Field의 디폴트값으로 함수를 설정하면 매번 똑같은 값이 나오는 것도 위와 같은 이유다.