mutable한 변수를 함수의 디폴트 매개변수로 사용 시 의도치않은 동작이 일어날 수 있다.
>>> def func(a=1, b=[]):
>>> b.append(10)
>>> print(b)
>>> return id(a), id(b)
>>> func.__defaults__
(1, [])
>>> func()
[10]
(140713432621456, 1728853850056)
>>> func()
[10, 10]
(140713432621456, 1728853850056)
>>> func(a=20, b=[30])
[30, 10]
(140713432622064, 1728853898696)
>>> func.__defaults__
(1, [10, 10])
위 예제를 통해 아래를 유추할 수 있다.
- 함수의
__defaults__
키워드로 내부에 저장된 기본 매개변수 값을 볼 수 있다. - 파라미터 a에 값을 지정하지 않고 넘길때와 지정할 때 반환되는 주소값이 다르다.
func
함수는 mutable한 기본 매개변수를 갖기 때문에 b에 값을 주지 않은 채로 호출하면 값이 누적된다.
A. 예제
a. 디폴트 매개변수에 mutable 객체를 선언하여 문제 발생
>>> import datetime
>>> import time
>>> # stopwatch 함수 선언
>>> def stopwatch(date_info = []):
>>> date_info.append(datetime.datetime.now())
>>> return date_info
>>> print("##### Phase 1 #########")
>>> print(f'stopwatch 함수 선언 후 바로 실행\n\t{stopwatch()}')
>>> print(f'stopwatch 함수의 defaults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
>>> time.sleep(10)
>>> print("##### Phase 2 #########")
>>> print(f'stopwatch 실행\n\t{stopwatch()}')
>>> print(f'stopwatch 함수의 defaults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
>>> time.sleep(10)
>>> print("##### Phase 3 #########")
>>> print(f'stopwatch 새로 실행\n\t{stopwatch([])}')
>>> print(f'stopwatch 함수의 defaults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
>>> time.sleep(10)
>>> print("##### Phase 4 #########")
>>> print(f'stopwatch 실행\n\t{stopwatch()}')
>>> print(f'stopwatch 함수의 defaults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
------
##### Phase 1 #########
stopwatch 함수 선언 후 바로 실행
[datetime.datetime(2022, 4, 3, 9, 48, 9, 400298)]
stopwatch 함수의 defaults 매개변수
([datetime.datetime(2022, 4, 3, 9, 48, 9, 400298)],)
현재 시간
2022-04-03 09:48:09.400298
##### Phase 2 #########
stopwatch 실행
[datetime.datetime(2022, 4, 3, 9, 48, 9, 400298), datetime.datetime(2022, 4, 3, 9, 48, 19, 411539)]
stopwatch 함수의 defaults 매개변수
([datetime.datetime(2022, 4, 3, 9, 48, 9, 400298), datetime.datetime(2022, 4, 3, 9, 48, 19, 411539)],)
현재 시간
2022-04-03 09:48:19.411539
##### Phase 3 #########
stopwatch 새로 실행
[datetime.datetime(2022, 4, 3, 9, 48, 29, 411675)]
stopwatch 함수의 defaults 매개변수
([datetime.datetime(2022, 4, 3, 9, 48, 9, 400298), datetime.datetime(2022, 4, 3, 9, 48, 19, 411539)],)
현재 시간
2022-04-03 09:48:29.411675
##### Phase 4 #########
stopwatch 실행
[datetime.datetime(2022, 4, 3, 9, 48, 9, 400298), datetime.datetime(2022, 4, 3, 9, 48, 19, 411539), datetime.datetime(2022, 4, 3, 9, 48, 39, 423540)]
stopwatch 함수의 defaults 매개변수
([datetime.datetime(2022, 4, 3, 9, 48, 9, 400298), datetime.datetime(2022, 4, 3, 9, 48, 19, 411539), datetime.datetime(2022, 4, 3, 9, 48, 39, 423540)],)
현재 시간
2022-04-03 09:48:39.423540
- default 매개변수가 계속 누적되는 것을 볼 수 있다.
- stopwatch를 재실행해도 Phase 4에서는 이전에 실행했던 stopwatch 기록으로 기록이 진행된다.
b. 해결 방법
>>> import datetime
>>> import time
>>> # stopwatch 함수 선언
>>> def stopwatch(date_info = None):
>>> if date_info is None:
>>> date_info = []
>>> date_info.append(datetime.datetime.now())
>>> return date_info
>>> record = None
>>> print("##### Phase 1 #########")
>>> print(f'stopwatch 함수 선언 후 바로 실행\n\t{stopwatch(record)}')
>>> print(f'stopwatch 함수의 defqults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
>>> time.sleep(10)
>>> print("##### Phase 2 #########")
>>> print(f'stopwatch 실행\n\t{stopwatch(record)}')
>>> print(f'stopwatch 함수의 defqults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
>>> time.sleep(10)
>>> record = []
>>> print("##### Phase 3 #########")
>>> print(f'stopwatch 새로 실행\n\t{stopwatch(record)}')
>>> print(f'stopwatch 함수의 defqults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
>>> time.sleep(10)
>>> print("##### Phase 4 #########")
>>> print(f'stopwatch 실행\n\t{stopwatch(record)}')
>>> print(f'stopwatch 함수의 defqults 매개변수\n\t{stopwatch.__defaults__}')
>>> print(f'현재 시간\n\t{datetime.datetime.now()}')
>>> print()
-------
##### Phase 1 #########
stopwatch 함수 선언 후 바로 실행
[datetime.datetime(2022, 4, 3, 10, 6, 25, 272702)]
stopwatch 함수의 defqults 매개변수
(None,)
현재 시간
2022-04-03 10:06:25.272702
##### Phase 2 #########
stopwatch 실행
[datetime.datetime(2022, 4, 3, 10, 6, 35, 274798)]
stopwatch 함수의 defqults 매개변수
(None,)
현재 시간
2022-04-03 10:06:35.274798
##### Phase 3 #########
stopwatch 새로 실행
[datetime.datetime(2022, 4, 3, 10, 6, 45, 280475)]
stopwatch 함수의 defqults 매개변수
(None,)
현재 시간
2022-04-03 10:06:45.280475
##### Phase 4 #########
stopwatch 실행
[datetime.datetime(2022, 4, 3, 10, 6, 45, 280475), datetime.datetime(2022, 4, 3, 10, 6, 55, 280664)]
stopwatch 함수의 defqults 매개변수
(None,)
현재 시간
2022-04-03 10:06:55.280664
B. 결론
python의 default 매개변수는 함수를 생성하는 그 시점에 초기화되며, list와 같은 mutable 객체는 그 값이 변경되면서 예상하지 못한 결과를 출력할 수 있다.
참고문헌
[1] ggg. https://velog.io/@ggg/Python-default-parameter. Velog. (accessed Apr 3. 2022)
[2] ash84. https://blog.weirdx.io/post/44132. Blog. (accessed Apr 3. 2022)
[3] https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument?page=1&tab=scoredesc#tab-top. Stack Overflow(accessed Apr 3. 2022)
Ghost