Logo

파이썬 타입 검사기 Mypy 사용법

파이썬 버전 3.5에 추가된 타입 어노테이션(type annotation) 덕분에 정적 타입 검사(static type checking)를 통해 좀 더 견고한 프로그램 개발이 가능해졌습니다.

이번 포스팅에서는 파이썬에서 타입 검사기(type checker)로 가장 많이 사용되는 도구인 Mypy에 대해서 간단한 실습을 통해서 알아보도록 하겠습니다.

타입 어노테이션 / 타입 체크

파이썬과 같이 동적(dynamic) 프로그래밍 언어에서 정적(static) 타입 체크가 왠 말이냐고 생각하시는 분들이 있으실 것 같은데요. 물론, 파이썬의 동적 타입 처리의 유연함은 일회성 스크립트나 소규모의 애플리케이션을 빠르게 개발할 때는 큰 장점으로 작용합니다. 하지만, 프로젝트 규모가 커지게 되면 이러한 파이썬의 다이나믹함이 치명적인 버그로 이어질 확률이 높아지게 되며 애플리케이션 안정성에 위험 요소가 되기도 하죠.

타입 어노테이션(type annotation)은 파이썬 코드에 타입을 명시하는 것이 가능하도록 파이썬 3.5에 추가되었는데요. 이 표준에 따라 변수나 함수, 클래스에 타입을 명시해준 파이썬 코드는 마치 Java나 C#처럼 정적 타입 검사가 가능해집니다. 이를 통해서 코드가 실행하면서 발생할 수 있는 오류를 미리 발견해서 고칠 수 있죠.

하지만 아쉽게도 타입 어노테이션을 추가한다고 해서 바로 프로그래밍 언어 차원에서 타입 검사가 강제되지는 않습니다. 파이썬에서 타입 검사는 어디까지나 선택 사항이기 때문에 우리는 Mypy와 같은 타입 검사 도구를 사용해야지 진정한 혜택을 누릴 수 있게 됩니다.

파이썬의 타입 어노테이션에 대한 자세한 설명은 관련 포스팅를 참고바라겠습니다.

Mypy 설치 및 실행

Mypy는 파이썬에서 가장 많이 사용되고 있는 정적 타입 검사 도구입니다. 타입 어노테이션이 추가된 파이썬 코드를 상대로 Mypy를 돌리면 타입 에러를 찾아내줍니다.

Mypy는 파이썬의 패키지 매니저인 pip으로 손쉽게 설치할 수 있습니다.

$ pip install mypy

파이썬의 패키지 매니저인 pip(Package Installer for Python)에 대해서는 별도 포스팅에서 다루고 있으니 참고 바랍니다.

다음과 같이 Mypy의 버전이 확인되면 잘 설치가 된 것입니다.

$ mypy --version
mypy 1.8.0 (compiled: yes)

Mypy를 터미널에서 실행할 때는 파일이나 디렉토리명을 인자로 넘기면 됩니다.

$ mypy our_file.py our_directory

타입 검사

실습을 위해 test1.py 파일을 생성하고 다음과 같이 매우 간단한 파이썬 코드를 작성해보겠습니다. no 변수의 타입을 int로 명시하기 위해서 타입 어노테이션을 추가하였습니다.

test1.py
no: int = "1"
print(no)

이 코드를 파이썬 인터프리터로 실행해보면 다음과 같이 문제없이 잘 돌아갑니다. 😰

python test1.py
1

왜냐하면 타입 어노테이션은 언어 레벨에서는 코드 실행에 아무 영향이나 제약을 주지않기 때문입니다. 이렇게 타입 어노테이션을 사용하는 것을 소외 타입 힌팅(type hinting)이라고 하는데 주로 코드를 읽기 쉽게 하거나, 코드 편집기(IDE)나 린터(linter)에서 활용됩니다.

이 코드를 다시 Mypy로 돌려보면 변수의 타입과 변수에 저장된 값의 타입이 다르다고 타입 에러가 발생합니다.

$ mypy test1.py
test1.py:1: error: Incompatible types in assignment (expression has type "str", variable has type "int")
Found 1 error in 1 file (checked 1 source file)

이렇게 Mypy를 사용하면 파이썬 인터프리터가 잡지 못하는 타입 버그를 쉽게 찾아낼 수 있습니다. 🐛

오류 예방

Mypy를 사용하면 위와 같은 단순한 타입 버그 뿐만 아니라 실제로 런타임에 발생할 수 있는 오류를 미리 알아낼 수도 있습니다.

예를 들어, test2.py 파일에 다음과 같이 파이썬 함수를 작성해보겠습니다. 함수의 인자 타입과 반환 타입을 명시하기 위해서 타입 어노테이션으로 해주었습니다.

test2.py
from typing import List


def repeat(message: str, times: int = 2) -> List[str]:
    return [message] * times


repeat("Hi", "3")

파이썬 코드에 타입 어노테이션을 추가할 때 사용되는 typing 내장 모듈에 대한 자세한 설명은 관련 포스팅를 참고 바랍니다.

이 코드를 파이썬 인터프리터로 실행해보면 repeat() 함수의 두 번째 인자로 숫자가 아닌 문자 타입의 인자가 넘어가기 때문에 오류가 발생하게 됩니다. 😞

$ python test2.py
Traceback (most recent call last):
  File "test2.py", line 8, in <module>
    repeat("Hi", "3")
  File "test2.py", line 5, in repeat
    return [message] * times
TypeError: can't multiply sequence by non-int of type 'str'

이 코드를 Mypy로 돌려보면 이미 이 코드에서 이러한 오류가 발생할 것이라는 것을 알려줍니다.

$ mypy test2.py
test2.py:7: error: Argument 2 to "repeat" has incompatible type "str"; expected "int"
Found 1 error in 1 file (checked 1 source file)

이렇게 Mypy를 사용하면 많은 오류를 애플리케이션을 실행 전에 찾아내고 효과적으로 예방할 수 있습니다. 👍

검사 예외

파이썬으로 코드를 작성하다보면 잠시 타입 오류를 무시해야할 때도 생기는데요. 이럴 때는 줄 맨 뒤에 # type: ignore 주석을 붙이면 Mypy가 해당 줄을 타임 검사에서 제외시키는 방법이 있습니다.

test1.py
no: int = "1"  # type: ignore
print(no)
$ mypy test1.py
Success: no issues found in 1 source file

만약에 파일 전체에서 발생하는 타입 오류를 무시하고 싶다면, 파일 맨 위에 # mypy: ignore-errors 주석을 넣어주시면 됩니다.

test2.py
# mypy: ignore-errors
from typing import List


def repeat(message: str, times: int = 2) -> List[str]:
    return [message] * times


print(repeat("Hi", "3"))
$ mypy test2.py
Success: no issues found in 1 source file

타입 검사를 피하기 위해서 이러한 주석을 절대 좋은 관행은 아니지만 간혹 요긴하게 쓰일 때가 있으니 참고 바랍니다.

Mypy 설정

Mypy 설정은 실행 시 커맨드에 옵션을 넘길 수도 있고, 설정 파일을 이용할 수도 있습니다. Mypy를 프로젝트에 통합해서 사용하는 경우, 매번 커맨드에 옵션을 넘기는 것 보다는 설정 파일을 이용하는 편이 편리할 것입니다.

설정 파일은 프로젝트 최상위 디렉토리의 mypy.ini 또는 .mypy.ini, setup.cfg를 사용하도록 되어 있습니다.

예를 들어, 다음과 같이 Mypy가 발견한 타입 오류를 어떻게 출력할지를 상세하게 설정할 수 있습니다.

mypy.ini
[mypy]
show_error_context = True
show_column_numbers = True
show_error_codes = True
pretty = True

다시 터미널에서 Mypy를 실행해보면 타입 오류에 대해서 좀 더 상세한 정보가 보기 좋게 나오는 것이 확인됩니다.

$ mypy .
test2.py:7:20: error: Argument 2 to "repeat" has incompatible type "str"; expected "int"  [arg-type]
    print(repeat("Hi", "3"))
                       ^~~
test1.py:1:11: error: Incompatible types in assignment (expression has type "str", variable has type "int")  [assignment]
    no: int = "1"
              ^~~
Found 2 errors in 2 files (checked 2 source files)

뿐만 아니라 Mypy가 특정 디렉토리에 있는 파일만 검사하거나, 특정 모듈은 타입 검사에서 예외되도록 설정해줄 수도 있습니다.

mypy.ini
[mypy]
files = app,tests

[mypy-app.*.migrations.*]
ignore_errors = True

이와 같이 모듈 레벨에서도 Mypy를 설정할 수 있기 때문에 기존에 타입 에러가 있는 프로젝트에서 Mypy를 사용하는데 큰 지장이 없을 것입니다.

마치면서

지금까지 파이썬 코드에 대한 정적 타입 체크를 해주고 런타임 오류를 미리 찾아내주는 유용한 도구인 Mypy에 대해서 살펴보았습니다. 실제 협업 프로젝트에서는 타입 오류를 방지하기 위해서 개발자가 저장소에 코드를 올릴 때마다 Mypy가 아예 자동으로 실행되도록 CI(지속 통합, continuous integration) 설정을 하는 경우가 많습니다. 이 부분에 대해서는 추후 기회가 되면 다른 포스팅을 통해서 다루도록 하겠습니다.

참고로 Mypy는 타입 어노테이션을 사용하지 않는 프로젝트에서도 점진적으로 사용할 수 있도록 타입 어노테이션이 추가되지 않은 코드는 무시하도록 되어 있습니다. 따라서 신규 프로젝트 뿐만 아니라 기존 프로젝트에서도 특히 프로젝트 규모가 크다면 코드 품질과 애플리케이션 안정성을 위해서 Mypy 사용을 추천드리고 싶습니다.