파이썬 함수의 디폴트 매개변수는 호출 시점을 따르지 않는다.

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])

위 예제를 통해 아래를 유추할 수 있다.

  1. 함수의 __defaults__ 키워드로 내부에 저장된 기본 매개변수 값을 볼 수 있다.
  2. 파라미터 a에 값을 지정하지 않고 넘길때와 지정할 때 반환되는 주소값이 다르다.
  3. 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
    
  1. default 매개변수가 계속 누적되는 것을 볼 수 있다.
  2. 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)

이 글이 도움이 되었나요?
0 minutes ago
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.