[리팩토링] mypy를 이용한 타입힌트, 서브 클래스 시그니처 불일치, 타입 불일치 리팩토링 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 타입을 str로 설정하게 된 배경
- .env에 저장된 값을 함수 호출부에서 이용해야 한다.
- 이때 os.getenv를 통해 가져온 Option[str]을 함수의 str 인자 자리에 넣어야 한다.
- 여기서 타입 힌팅과 관련해서 타입이 맞지 않는다는 문제 발생. (기대하는 타입: str, 들어온 타입: Option[str])
- 이럴 막기 위해 assert로 None이 아님을 보장함으로써 타입이 str인것을 명확히 해 타입 힌팅 에러를 없애려고 했으나 아래와 같은 이유로 인해 되지 않았다.
assert
의 한계-
.env
에서 값을 불러온 후 다음처럼 검사를 할 수 있다:assert BUCKET_NAME is not None, "버킷 이름이 필요합니다"
-
하지만 이는 런타임에만 작동하며, 정적 타입 검사기(
mypy
)는 여전히 해당 변수를Optional[str]
로 인식한다. -
즉,
assert
는 타입 체커가 변수의 타입을 좁히는 데 도움을 주지 못한다.
-
2. 해결 전략
assert
대신 ifraise
로 변경
a. if BUCKET_NAME is None:
raise ValueError("BUCKET_NAME은 .env에 설정되어야 합니다.")
mypy
는 위처럼 조건문을 통한 흐름 제어를 인식하여 타입을str
로 좁힌다.- 따라서 정적 분석 + 런타임 모두 안전하게 보장된다.
b. 로깅 데코레이터의 타입 힌팅을 비활성화한 배경
def log_exception(func: Callable[P, R]) -> Callable[P, R]:
"""예외 발생 시 자동으로 로깅하는 데코레이터"""
@wraps(func)
def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
try:
return func(*args, **kwargs)
except Exception as e:
logger.opt(depth=1).exception(
f"{func.__name__} 함수 예외 발생: {e}"
)
raise
@wraps(func)
async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
try:
return await func(*args, **kwargs) # type: ignore
except Exception as e:
logger.opt(depth=1).exception(
f"{func.__name__} 함수 예외 발생: {e}"
)
raise
return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper # type: ignore
- 로깅 데코레이터 정의하는 부분에서도 타입 힌팅이 필요하다.
- 이때, 비동기 함수에 이용할 데코레이터 부분은 Callable[P, R]로 커버되지 않는다.
- 데코레이터 정의 부분에서 인자와 반환형이 각각 동기, 비동기 wrapper인지에 따라 분기된다.
sync_wrapper
,async_wrapper
는 각기 다른 타입 (Callable[P, R]
,Callable[P, Awaitable[R]]
)이므로, 분기 반환 시 타입 오류가 발생
- 그런데 정적 타입 체커로는 둘 중 어느 타입인지 추론할 수 없기 때문에 에러가 발생한다.
- 정적 분석에만 문제가 있고, 로깅 데코레이터를 런타임에 사용하는 것은 정상 동작하는 상황.
- 이를 막기 위한 방법으로는 2가지가 있다:
- type: ignore ← 타입 체크를 하지 않는 방식
- @overload를 이용해 여러 타입 분기해서 나타내기
- 선택한 방법:
# type: ignore
를 데코레이터 반환부에 붙여서 타입 체커 에러를 무시- 이유:
-
현재 동작 자체에는 문제가 없고 정적 타입 체킹에서 로깅 데코레이터 부분 경고가 뜨지 않게 하는 것이 목적
-
타입 체킹을 안 뜨게 하기 위해서 overload를 하면 코드가 지저분해진다.
-
overload를 해서 타입 체킹을 유지할만큼 로깅 데코레이터의 타입 체킹이 필요하지 않다.
-
런타임 동작에서는 타입 분기가 적절히 이루어지지만 단순히 정적 타입 분석기가 이를 인식하지 못하는 상황인 만큼 정적 타입 분석기를 이 부분에 대해 비활성화함으로써 간단히 문제를 해결해도 된다고 판단했다.
-