About/Python

[Python] 코드 검사 자동화(Mypy를 사용한 타입 힌팅, Pylint를 사용한 코드 검사)

김징어 2022. 3. 29. 23:03

Mypy를 사용한 타입 힌팅


Mypy

http://mypy-lang.org/

 

mypy - Optional Static Typing for Python

Why mypy? Compile-time type checking Static typing makes it easier to find bugs with less debugging. Easier maintenance Type declarations act as machine-checked documentation. Static typing makes your code easier to understand and easier to modify without

mypy-lang.org

Mypy는 파이썬에서 가장 일반적으로 사용하는 정적 타입 검사 도구입니다.

mypy를 설치하면 프로젝트의 모든 파일을 분석하여 타입 불일치를 검사해줍니다. 버그를 조기에 발견할 수 있기 때문에 유용하지만 가끔 잘못 탐지하는 경우도 있습니다.

pip를 사용해 설치할 수 있으며 프로젝트 셋업 파일에 종속성을 추가하는 것이 좋습니다.

1
$ pip install mypy
cs

 

가상환경에 mypy를 설치하고 mypy [파일명]을 입력하면 타입 검사 결과를 제공합니다. 여기서 보고된 대부분의 내용은 가능한 준수하는 것이 좋습니다. 왜냐하면 실제 상용화 시 문제가 재현될 수 있기 때문입니다. 잘못된 탐지를 하는 경우가 인쓴데, 문장 끝에 다음과 같이 주석을 추가하여 mypy가 무시하도록 할 수 있습니다.

Mypy 예제1 - Type Annotation error

Mypy에서 Type Annotation이 명시되어 있지 않은 경우 에러를 유발합니다.

다음은 파일명을 입력받아서 해당 파일에 있는 단어들을 카운트하는 파이썬 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# mypy_test.py
# Display the frequencies of words in a file.
 
import sys
import re
 
 
if not sys.argv[1:]:
    raise RuntimeError('Usage: wordfreq FILE')
 
= {}
 
with open(sys.argv[1]) as f:
    for s in f:
        for word in re.sub('\W'' ', s).split():
            d[word] = d.get(word, 0+ 1
 
# Use list comprehension
= [(freq, word) for word, freq in d.items()]
 
for freq, word in sorted(l):
    print('%-6d %s' % (freq, word))
cs

 

위의 파일을 mypy로 실행하면 다음과 같이 경고합니다.

11번 줄의 dict 변수 d 에 대한 type annotation이 필요하다고 합니다.

Mypy의 검사에 통과하기 위해서 11번 줄을 다음과 같이 변경합니다.

1
= {}  # type: Dict[strint]
cs

 

dict의 key는 str이며, value는 int 타입이라는 힌트를 준 것입니다.  따라서 변경된 소스코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# mypy_test.py
# Display the frequencies of words in a file.
 
import sys
import re
from typing import Dict
 
if not sys.argv[1:]:
    raise RuntimeError('Usage: wordfreq FILE')
 
= {}  # type: Dict[str, int]
 
with open(sys.argv[1]) as f:
    for s in f:
        for word in re.sub('\W'' ', s).split():
            d[word] = d.get(word, 0+ 1
 
# Use list comprehension
= [(freq, word) for word, freq in d.items()]
 
for freq, word in sorted(l):
    print('%-6d %s' % (freq, word))
cs

 

Mypy 예제2 - 오류 검사

mypy는 런타임 에러를 유발할 수 있는 오류도 검사합니다.

다음과 같이 두 정수를 입력 받아 두 정수의 곱을 하는 myMultiply() 함수가 있습니다.

1
2
3
4
def myMultiply(n1: int, n2: int-> int:
    return n1 * n2
 
print(myMultiply("1""2"))
cs

 

이 함수의 인자로 정수가 아닌 문자열을 전달하는 경우 문자열 끼리 곱셈은 불가능하기 때문에 런타임 오류가 발생합니다.

런타임 에러

이 파일을 Mypy로 실행하면 다음과 같이  잘못된 인자가 입력되었다는 것을 알려줍니다.

 

이처럼 Mypy를 사용하면 코드에 대한 정적 타입 체크를 해주고 인터프리터가 잡지 못하는 에러 또한 탐지할 수 있습니다. 

Pylint를 사용한 코드 검사


Pylint

https://pypi.org/project/pylint/

 

pylint

python code static checker

pypi.org

Pylint는 프로그래밍 오류를 찾고, 코딩 표준을 시행하고, 간단한 리팩토링 제안을 제공하는 Python 정적 코드 분석 도구입니다.

기본적으로 PEP-8을 준수했는지 여부를 검사하는 도구입니다.

pip를 이용하여 설치할 수 있습니다.

1
$ pip install pylint
cs

 

pylint [파일명]을 입력하면 검사를 수행합니다. .pylint 파일을 통해 설정 값을 바꿀 수 있으며 규칙을 활성화 또는 비활성화 할 수 있고 한 줄의 최대 글자 수 같은 값을 설정할 수 있습니다. 위에서 사용한 예시의 파일을 Pylint로 검사하면 다음과 같습니다.

1
2
3
4
def myMultiply(n1: int, n2: int-> int:
    return n1 * n2
 
print(myMultiply("1""2"))
cs

Pylint 검사 결과

snake 표기법을 사용하지 않았다는 점과 docstring을 작성하지 않았다는 점 등을 지적하여 주었습니다. code rate가 0점이네요...

Pylint에서 경고한 내용들을 반영하여 코드를 다음과 같이 변경하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# pylint_test.py
"""
pylint Test 파일
"""
 
def my_multiply(num1: int, num2: int-> int:
    """
    두 정수를 입력 받아 두 정수의 곱을 반환하는 함수
    :param num1: 정수1
    :param num2: 정수2
    :return: 정수1과 정수2의 곱
    """
    return num1 * num2
 
print(my_multiply("1""2"))
 
cs

 

다시 Pylint로 실행한 결과 모든 검사에 통과하였습니다.

Pylint 검사 결과

이처럼 Pylint는 코드 표준을 준수했는지를 검사해주는 유용한 도구입니다.

코드 자동 검사 설정

리눅스 개발 환경에서 빌드를 자동화 하는 가장 일반적인 방법은 Makefile을 사용하는 것입니다.  Makefile은 프로젝트를 컴파일하고 실행하기 위한 설정을 도와주는 파워풀한 도구입니다.

빌드외에도 포매팅 검사나 코딩 컨벤션 검사를 자동화하기 위해 사용할 수도 있습니다. 

Makefile을 작성하기 위해 좋은 방법은 테스트, 코드검사 등을 각각의 target으로 만들고, 이것들을 모두 실행하는 또 다른 target을 만드는 것입니다. 예를 들면 다음과 같이 작성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Makefile
typehint:
    mypy src/ tests/
 
test:
    pytest tests/
 
lint:
    pylint src/ tests/
 
checklist: lint typehint test
 
.PHONY: typehint test lint checklist
cs

 

위 파일을 작성한 후 다음과 같이 입력하면 테스트 및 코드 검사를 실행합니다.

1
$ make checklist
cs

 

위의 커맨드를 실행하면 다음 단계를 실행합니다.

1. 코딩 가이드라인 검사(ex. PEP-8)
2. 올바른 타입을 사용했는지 검사
3. 최종 테스트 실행

이 중 어떤 단계라도 실패하면 전체 프로세스가 실패한 것으로 간주하면 됩니다.

이 절차는 검사의 자동화로서 지속적인 통합 빌드(continuous integration build)의 하나의 절차가 될 수 있습니다. 이 검사에 실패하게되면 전체 빌드도 실패가 되어야 합니다.


이와 같이 코드 검사 도구를 사용하는 것은 코드 구조의 연속성을 확보할 수 있는 좋은 방법입니다.

Python 환경에서 Mypy를 이용하여 코드의 정적 타입 검사를 하였고, Pylint를 이용하여 코딩 가이드라인 검사를 수행하였습니다. 물론 이 도구 뿐만 아니라 다양한 도구들도 있기 때문에 팀의 색깔에 맞게 적절한 도구를 사용하는 것이 좋을 것 같습니다.

좋은 코드를 작성하는 것은 대단한 알고리즘을 작성하라는 것이 아니라고 생각합니다. 대단한 알고리즘이기 전에 읽기 쉽고 이해하기 쉬우며 어떠한 코드를 읽어도 일관된 형태의 코드가 좋은 코드라고 생각합니다.

특히 팀 단위의 프로젝트에서 개발자마다 코딩 스타일이 다르다면 유지보수가 힘들어질 수 있기 때문에 이러한 코드 검사 도구를 적절히 사용하는 것이 좋을 것 같습니다.