Python linting, formatting, testing, 생산성을 위한 몸부림


Tool을 사용하는 데에 익숙하고 생산성이나 자동화라는 키워드에 목이 메어 있는 천성 개발자들이라면, 누구나 소프트웨어 개발 과정을 더 일정한 규칙에 통일시키고, 기대했던 결과를 그대로 눈으로 확인하고, 그래서 전체 개발 프로세스를 조금 더 편하게 만드는 데에 관심있을 것이다. Python 사용자들은 그 프로그래밍 언어의 신념 (Simple is better than complex)과도 일맥상통하게 이러한 개발자들의 요구에 답하는 도구들을 많이 제공하고 있다. 그 중 내가 사용하는 도구들을 몇 가지 소개한다.

Linting 하기 - pylint

pylint는 python으로 작성된 모듈, 패키지 등을 손쉽게 PEP8 스타일 가이드 및 구문에러 등을 통해 분석해 채점하고 꾸짖어주는 도구이다. 요즘에는 좋은 IDE (Visual Studio Code, PyCharm)를 쓰면 으레 자동으로 따라다니곤 하지만 그렇지 않다면 꼭 찾아서 활성화 해주도록하자. IDE에서는 편집중인 파일이나 폴더에 대한 구문의 이상을 실시간으로, 전반적으로 편리하게 분석할 수 있으며, commandline에서 패키지 단위로 pylint를 실행하면 내가 작성하고 있는 Python 패키지가 10점 만점에 몇 점인지 손쉽게 알 수 있다.

# pylint 설치하기
pip install pylint

# pylint로 패키지 채점해보기
pylint konlpy (or your package)

************* Module konlpy
konlpy/__init__.py:1:0: C0114: Missing module docstring (missing-module-docstring)
************* Module konlpy.internals
konlpy/internals.py:25:0: C0325: Unnecessary parens after 'if' keyword (superfluous-parens)
konlpy/internals.py:1:0: C0114: Missing module docstring (missing-module-docstring)
konlpy/internals.py:10:0: C0116: Missing function or method docstring (missing-function-docstring)
konlpy/internals.py:10:0: R1710: Either all return statements in a function should return an expression, or none of them should. (inconsistent-return-statements)
konlpy/internals.py:16:0: C0116: Missing function or method docstring (missing-function-docstring)
konlpy/internals.py:25:8: R1705: Unnecessary "elif" after "return" (no-else-return)
konlpy/internals.py:40:0: C0116: Missing function or method docstring (missing-function-docstring)
.
.
.
************ Module konlpy.tag._komoran
konlpy/tag/_komoran.py:61:2: W0511: FIXME: Cannot execute without sudoing (fixme)
konlpy/tag/_komoran.py:18:0: C0301: Line too long (154/100) (line-too-long)
konlpy/tag/_komoran.py:38:0: C0301: Line too long (125/100) (line-too-long)
konlpy/tag/_komoran.py:39:0: C0301: Line too long (146/100) (line-too-long)
konlpy/tag/_komoran.py:62:0: C0301: Line too long (137/100) (line-too-long)
konlpy/tag/_komoran.py:71:0: C0301: Line too long (120/100) (line-too-long)
konlpy/tag/_komoran.py:89:81: C0326: Exactly one space required after comma
        pytoj = lambda string: jpype.JPackage("java").lang.String(string.encode(),'UTF8')
                                                                                 ^ (bad-whitespace)
konlpy/tag/_komoran.py:1:0: C0114: Missing module docstring (missing-module-docstring)
konlpy/tag/_komoran.py:66:8: C0103: Variable name "komoranJavaPackage" doesn't conform to snake_case naming style (invalid-name)
konlpy/tag/_komoran.py:90:8: W0612: Unused variable 'jtopy' (unused-variable)

-----------------------------------
Your code has been rated at 6.75/10

가장 유명한 것은 pylint이지만 이 외에도 훌륭한 linter로 여러 도구를 모은 인터페이스인 flake8, 보안 이슈를 체크해주는 bandit등이 있다. 특히나 flake8의 경우 굉장히 많은 Extension들을 지원해 flake8 명령으로 pylint를 실행할 수도 있고 그 외 import-order, 추후 formatting에서 설명할 black 등을 한꺼번에 실행하도록 할 수도 있다.

코드 스타일이라는 개념 자체가 굉장히 주관적일 수 있어 다양한 입맛의 linting 도구들이 우후죽순 생겨났다. 비교적 최근에 생긴 wemake-python-styleguide라는 한 linting 종파 (…)에서는 다음과 같은 주장을 하기도 한다. pylint 등의 rule이 너무 시시하다면 wemake-python-styleguide를 사용해보는 것도 나쁘지 않을 것 같다.

  flake8 pylint black mypy wemake-python-styleguide
Formats code?
Finds style issues? 🤔 🤔
Finds bugs? 🤔
Finds complex code? 🤔
Has a lot of strict rules? 🤔
Has a lot of plugins? 🤔

Reformatting 하기 - isort + black

linter들을 열심히 사용하고 있자니 손으로 수정해야할 코드가 너무나 많을 것이다. 그럴 땐 black과 isort를 설치하고 IDE와 연동해 파일을 저장할 때마다 실행될 수 있도록 설정하면 정말 좋다. isort는 패키지들의 import 순서를 빌트인 패키지에서 서드파티, 자체 패키지까지의 흐름으로 적절히 바꾸어주고 사용되지 않는 패키지 import를 없애주는 도구이다. python 표준 라이브러리를 import하는 경우 import의 순서는 상관 없지만, import 하는 데에만 1초 이상 걸리는 TensorFlow 같은 서드파티 라이브러리 등을 함께 사용하는 실무 환경에서 순환참조등을 줄여 도움이 될 수 있다. black은 그 외의 모든 구문들을 ‘타협하지 않는’ black만의 강력한 rule을 기반으로 reformatting해주는 도구이다. 자기주장이 굉장히 강한 formatting 도구이지만 그만큼 굉장히 formatting을 잘 수행해주기 때문에 그에게 코드를 믿고 맡기고 싶다. 특별히 설정에 필요한 노고도 별로 없기에 정말 추천하는 편이다.

pip install black
black my_module.py

pytest / continuous integration / coverage

pytest는 python에서 가장 많이 사용되는 유닛 테스트 패키지이다. 다양한 확장 플러그인을 지원하며 규격화된 unittest를 진행하도록 도와준다. 이는 IDE 등에서도 통합하여 사용할 수 있지만, 가장 좋은 것은 GitHub Action, Travis-CI 등 Continuous Integration 혹은 pipeline 도구를 통해 pull-request, push 단위로 unittest를 진행하는 것이다. 그러면 패키지의 배포 환경에서도 무사히 동작이 수행되는 지 확인할 수 있어 좋다. 다만, Data scientist나 ML engineer의 경우에는 조금 다른 점이 있다면 Python 패키지 수준의 종속성이 아닌 시스템 종속성 (NVIDIA 그래픽 드라이버 등)이 굉장히 많이 포함되어 있어 파이프라인 자동화가 어렵고, 으레 unittest를 실행해주는 CI 서비스등의 인스턴스의 성능은 처참한 수준이므로 학습이나 추론 코드의 unittest는 다소 우회적인 방법으로 실행해야 하거나, 실행하지 않게 된다. Google의 TensorFlow의 경우 자체 구축된 아주 복잡한 Jenkins CI 파이프라인을 통해 유지보수를 진행하고 있다.

# pytest 설치하기
pip install pytest
my_package/tests/my_first_unit_test.py

# 간단한 Test 코드 작성하기

def test_my_first_module():
    test_my_first_function()

# pytest 실행하기
pytest -s -v

기본 Python 인터프리터로 디버깅하지 않기, 최대한. 정말로.

Jupyter Notebook, Jupyter Lab, VSCode Jupyter 플러그인 등을 활용하자. 인터프리터 환경에서 실행하기 쉬운 실험적인 코드를 시각적으로 확인하고, 저장하고, 버젼관리하고, 문서화하게 해준다. 하다못해 IPython을 사용하자. 반복적으로 함수 등을 테스트하며 구현하려고 한다면, 여러번 같은 코드를 작성하지 않도록 하자.

나만의 만능 패키지 만들기

이미 짜여진 도구들, 라이브러리들을 충분히 활용하고 있고 여전히 부족함을 느낀다면 나만의 패키지를 통해 자잘한 기능들을 구현하고, 내가 반복적으로 개발에 사용하는 규칙들을 문서로 만들어 기억하자. Google의 경우, Abseil이라는 도구를 개발하여 그들만의 code의 boilerplate으로 활용하고 있다. TensorFlow 등 Google에서 작성한 공식 Example들이나 문서들을 읽게되면 그들이 분산환경에서 logging, commandline argument parsing 등을 하는 데에 반복적으로 이 패키지를 사용하는 것을 알 수 있다. 내 눈에는 그것이 어찌하여 그들의 모범답안이 되었는지 완전히 이해되진 않는 편이라서, 이런 도구들을 점진적으로 스스로 만들어보는 것도 좋은 것 같다. 직접 만들어봄직한 패키지의 기능들은 다음과 같다.

  • 나나 회사의 필요에 맞는 통일된 logging 기능
  • 나만의 시스템 자원 관리 기능 (Example: GPU 자원 모니터링 등)
  • 나만의 병렬화 인터페이스 (joblib, multiprocessing 등 패키지의 공식문서를 충분히 읽었음에도 부족함을 느끼는 경우에만 만들자.)
  • 나만의 Configuration 관리 도구 - 나의 경우 .yaml을 통해 모든 configuration 문서를 작성하고 이를 dictionary가 아닌 namedtuple 형식의 Namespace로 불러와주는 함수를 만들어 사용중이다.
  • 나만의 코딩 컨벤션

이러한 도구들은 완전히 오픈소스로 만들어도 되고, 회사 내부에서만 사용해도 되고, 경우에 따라서는 정말 나만 사용해도 된다. 어떤 식으로든 자신이 구현한 그 편리함을 두고두고 사용하면 개발의 속도는 더욱 더 향상될 것이다.