warning을 사용하라

매일 공부(ML)·2022년 10월 24일
0

파이썬 코딩의 기술

목록 보기
26/27

리팩터링과 마이그레이션 방법을 알려주기 위해서 warning을 사용하라

코드베이스가 커지면서 API를 호출하는 지점 수가 많아지거나 여러 소스 코드 저장소에 호출 지점이 흩어지면, API변경과 호출 지점 변경을 함께 일관성 있게 수행하는 것이 어려울 수 있다.

그러므로, 자신의 코드를 리팩터링하고 여러분의 API를 사용하는 부분을 최신 API에 맞춰 변경하도록 협력을 요청할 수 있는 방법을 찾아야한다.

키워드 인자를 지정하여 함수 호출을 명확하게 만들라

#키에 해당하는 단위로 돼 있는 값을
#SI 단위계 단위로 바꿀 때 곱해야 하는 숫자를 저장하는 딕셔너리
CONVERSIONS = {
	'mph': 1.60934 / 3600 * 1000, # 마일/초 -> 미터/초
    '시간': 3600,  #시간 -> 초
    '마일': 1.60934 * 1000, # 마일 -> 미터
    '미터': 1,
    'm/s': 1,
    '초':1
}

def convert(value, units):
	rate = CONVERSIONS[units]
    return rate * value
    
def localize(value, units):
	rate = ONVERSIONS[units]
    return value / rate
    
def print_distance(speed, duration, *,
				   speed_units = 'mph',
                   time_units = '시간',
                   distance_units = '마일'):
                   
    norm_speed = convert(speed, speed_units)
    norm_duration = convert(duration, time_units)
    norm_distance = norm_speed * norm_duration
    distance = localize(norm_distance, distance_units)
    print(f{'distance} {distance_units}')

"""
단위를 마일로 변환을 해도 정확하게 표시하도록 만들 수 있다
"""
print_distance(1000, 3,
			   speed_units = '미터',
               time_units = '초')
               
#1.8641182099494205

단위를 지정하는 것은 좋은 방향이고, 오류 가능성이 낮아지긴 하지만 기존 API를 호출하는 모든 사용자가 항상 단위를 지정할 수는 없을 것이다.


warnings 모듈을 사용하라

  • 함수를 호출하는 쪽의 코드에 가능한 한 빨리 새로운 단위 인자를 포함시키도록 장려할 수 있다.

  • 자신이 의존하는 모드가 변경됐으므로 각자의 코드를 변경하라고 안내할 수 있다.

  • 컴퓨터가 자동으로 오류를 처리 시 주로 예외를 사용하지만 협업을 할 땐 의사를 전달할 때는 경고를 사용한다.

"""
warnings.warn 함수는 stacklevel 파라미터를 지원하고, 호출 스택에서 경고를 발생시킨 위치를 제대로 보고할 수 있다.
stacklevel을 활용하면 다른 코드를 대신해 경고를 표시하는 함수를 쉽게 작성할 수 있기에 준비 코드를 줄일 수 있다.
선택적인 인자가 제공되지 않은 경우에 경고를 표시하고 빠진 인자에 대한 디폴트 값을  제공해주는 도우미 함수를 정의한다.
"""

def require(name, value, default):
	if value is not None:
    	return value
    warnings.warm)
    	f'{name}이(가) 곧 필수가 됩니다. 코드를 변경해 주세요',
        DeprecationWarning,
        stacklevel=3)
    return default
    
def print_distance(speed, duration, *,
				   speed_units=None,
                   time_units = None, 
                   distance_units = None):
                   
    speed_units = require('speed_units', speed_units, 'mph')
    time_units = require('time_units', time_units,'시간')
    distance_units = require(
    	'distance_units', distance_units, '마일')
        
    norm_speed = convert(speed, speed_units)
    norm_duration = convert(duration, time_units)
    norm_distance = norm_speed * norm_duration
    distance = localize(norm_distance, distance_units)
    print(f'{distance} {distance_units}')

#프로그램을 실행한 결과를 검사하면 이 함수가 경고할 대상 위치를 제대로 전달하는지 확인할 수 있다.

import contextlib
import io

fake_stderr = io.StringIO()
with contextlib.redirect_stderr(fake_stderr):
	print_distance(1000,3,
    			   speed_units = '미터',
                   time_units = '초')

warnings.simplefilter('error')
try:
	warnings.warn('이 사용법은 향후 금지될 예정입니다',
    			   DeprecationWarning)
except DeprecationWarning:
	print('DeprecationWarning이 예외로 발생")
    pass #에외가 발생할 것으로 예상함
    
print(fake_stderr.getvalue())
#1.8641182099494205 마일
#DeprecationWarning이 예외로 발생```
코드를 입력하세요

<br>

>예외를 발생시키는 동작 방식은 상위 의존 관계에 있는 변경을 감지해서 적절히 실패하는 자동화된 테스트에 유용하고, 협업하는 사람들에게 코드를 변경해야 한다는 사실을 명확히 알려주는 좋은 방법이다.

"""
warnings.simplefilter('error)를 쓰지 않아도, -W error 명령줄 인자를 파이썬 인터프리터에게 넘기거나
PYTHONWARNINGS 환경 변수를 설정해서 이런 정책을 사용할 수 있다.
"""

#ex6.py
import warnings
try:
warnings.warn('이 사용법은 향후 금지될 예정입니다.',
DeprecationWarning)

except DeprecationWarning:
print("DeprecationWarning이 예외로 발생")

#실행

$ python -W error ex6.py
DeprecationWarning이 예외로 발생


로깅을 사용하여 경고를 잡아낼 경우, 프로그램에 오류 보고 시스템이 설정된 경우 프로덕션 환경에서도 중요한 경고를 통보받을 수 있다.

import logging

fake_stderr = io.StringIO()
handler = logging.StreamHandler(fake_stderr)
formatter = logging.Formatter(
	'%(asctime) -15s WARNING] %(message)s')
handler.setFormatter(formatter)

logging.captureWarnings(True)
logger = logging.getLogger('py.warnings')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

warnings.resetwarnings()
warnings.simplefilter('default')
warnings.warn('이 경고는 로그 출력에 표시됩니다.')

print(fake_stderr.getvalue())

#결과

2020-08-29 23:15:43,465 WARNING] ex8.py:17: UserWarning: 이 경고는 로그 출력에 표시됩니다.
  warnings.warn('이 경고는 로그 출력에 표시됩니다.')

정리본

API 라이브러리 관리자는 경고가 제대로 된 환경에서 명확하고 해결 방법을 제대로 알려주는 메시지와 함께 만들어지는지 검증하는 단위 테스트를 작성해야한다.

with warnings.catch_warnings(record=True) as found_warnings:
	found = require('my_arg', None, '가짜 단위')
    expected = '가짜 단위'
    assert found ==expected
    
"""
경고 메시지를 수집하고 나면 경고의 개수, 자세한 메시지, 분류가 예상과 맞아떨어지는지 확인
"""

assert len(found_warnings) == 1
single_warning = found_warnings[0]
assert str(single_warning.message) == (
	'my_arg이(가) 곧 필수가 됩니다. 코드를 변경해 주세요')
assert single_warning.category == DeprecationWarning

Summary

  • warnings 모듈을 사용하면 여러분의 API를 호출하는 사용자들에게 앞으로 사용 금지될 사용법에 대해 알려줄 수 있고, 경고 메시지는 API 사용자들이 자신의 코드가 깨지기 전에 코드를 변경하도록 권장한다.

  • -w error 명령줄 인자를 파이썬 인터프리터에게 넘기면 경고를 오류로 높일 수 있고, 의존 관계에서 잠재적인 회귀 오류가 있는지 잡아내고 싶은 자동화 테스트에서 기능이 특히 유용하다.

  • 프로덕션 환경에서는 경고를 logging 모듈로 복제해 실행 시점에 기존 오류 보고 시스템이 경고를 잡아내게 할 수 있다.

  • 다운스트림 의존 관계에서 알맞은 경고가 발동되도록 코드가 생성하는 경고에 대해 테스트를 작성하면 유용하다.

profile
성장을 도울 아카이빙 블로그

0개의 댓글