[Python] Skill of coding - 동적 기본 인수를 지정 시 None과 docstring 사용

Hyeseong·2020년 12월 10일
0

python skill of coding

목록 보기
13/18

동적 기본 인수를 지정하려면 None과 docstring을 사용하기

키워드 인수의 기본값으로 비정적타입을 사용해야 할 때도 있다.
예. 이벤트 발생 시각까지 포함해 로깅 메시지를 출력한다고 하자. 기본적인 경우에는 함수를 호출한 시각을 메시지에 포함하려고 한다. 함수가 호출될 때 마다 기본 인수를 평가한다고 가정하고 다음과 같이 처리하려 할 것이다.

from time import sleep
from datetime import datetime

def log(message, when=datetime.now()):
    print('%s: %s' % (when, message))

log('Hi there!')
sleep(0.1)
log('Hi again!')
2020-12-10 09:27:33.260913: Hi there!
2020-12-10 09:27:33.260913: Hi again!

datetime.now는 함수를 정의할 떄 딱 한 번만 실행되므로 타임스탬프가 동일하게 출력된다.기본 인수의 값은 모듈이 로드될 때 한 번만 평가되며 보통 프로그랭이 시작할 때 일어난다 이 코드를 담고 있는 모듈이 로드된 후에는 기본 인수인 datetime.now를 다시 평가하지 않는다.

기대한 대로 나오게 하려면 기본값을 None으로 설정하고 docstring으로 실제 동작을 문서화 하는게 관례다. 코드에서 인수 값으로 None이 나타나면 알맞은 기본값을 할당하면 된다.

def log(message, when=None):
    """Log a message with a timestamp.

    Args:
        message: Message to print.
        when: datetime of when the message occurred.
            Defaults to the present time.
    """
    when = datetime.now() if when is None else when
    print('%s: %s' % (when, message))
2020-12-10 09:39:25.328102: Hi there!
2020-12-10 09:39:25.428109: Hi again!

이제는 타임스탬프가 다르게 나오는 것이 확인된다.

기본 인수 값으로 None을 사용하는 방법은 인수가 수정 가능(mutable)할 때 특히 중요함. 예. JSON 데이터로 인코드된 값을 로드한다고 하자.
데이터 디코딩이 실패하면 기본값으로 빈 딕셔너리를 반환하려고 한다.
다음과 같은 방법을 써볼 수 있다.

foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
Foo: {'stuff': 5, 'meep': 1}
Bar: {'stuff': 5, 'meep': 1}

위의 코드에는 datetime.now 예제와 같은 문제가 있다. 기본 인수 값은(모듈이 로드될 때) 딱 한 번만 평가되므로, 기본값으로 설정한 딕셔너리를 모든 decode 호출에서 공유한다. 보는 바와 같이 이 문제는 예상치 못한 동작을 야기했어요.

아마 각각 단일 키와 값을 담은 서로 다른 딕셔너리 두 개를 예상했을 것이다. 하지만 하나를 수정하면 다른 하나도 수정되는 것처럼 보인다. 이런 문제의 원인은 foo와 bar 둘 다 기본 파라미터와 같다는 점이다. 즉, 이 둘은 같은 딕셔너리 객체다.

assert foo is bar

키워드 인수의 기본값을 None으로 설정하고 함수의 docstring에 동작을 문서화해서 이 문제를 고친다.

    try:
        return json.loads(data)
    except ValueError:
        return default

이제 앞서 나온 테스트 코드를 실행하면 기대한 결과를 볼 수 있다.

foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
Foo: {'stuff': 5}
Bar: {'meep': 1}

핵심정리

  • 기본 인수는 모듈 로드 시점에 함수 정의 과정에서 딱 한번만 평가된다. 그래서 ({}나 []와 같은) 동적 값에는 이상하게 동작하는 원인이 되기도 한다.
  • 값이 동적인 키워드 인수에는 기본 값으로 None을 사용하자. 그러고 나서 함수의 docstring에 실제 기본 동작을 문서화 하자.
profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글