Go 언어에는 gofmt라는 도구가 있다. Go를 설치하면 기본적으로 제공되는 콘솔용 코드 포매팅 프로그램인데, gofmt -w main.go같은 커맨드를 통해 Go 코드를 포매팅하는 식이다. indent 맞춰 주고, 구조체를 align 시켜주는 등 코드의 비주얼적인 개선 작업들을 한다. Visual Studio Code에서 Go extension을 설치하면, 파일을 저장할 때마다 gofmt를 실행해 주어서 매우 편했었다. 이건 lint와 관련된 몇 가지 도구들을 더 쓰는지 unused import, variable같은 것도 지워준다.

ZnVFz.gif

파이썬에는 PEP 8이라는 공신력 높은 코드 스타일 가이드가 있으며, unused import를 찾는 것과 같은 정적 검사도 가능할테니, 이런 비슷한 걸 해주는 도구가 있지 않을까 싶었다. 이 글에서는 파이썬 코드 스타일 체킹과 포매팅에 관련된 이런저런 이야기들과 함께 대표적인 도구들 몇 개를 간단히 소개하고, 이어지는 글에서는 내가 자주 쓰는 Black이라는 도구를 소개하고자 한다.

코드 스타일!

코드 스타일?

코드 스타일은 쉽게 말해 편집 규약이다. 코드 컨벤션이라고도 말한다. 어차피 코드를 기계가 읽을 때는 괄호 위치나 네이밍 컨벤션같은 것들이 결과에 영향을 끼치진 않겠지만, 사람이 더 잘 읽을 수 있게 배려하는 것이 좋다. 따라서 코드 스타일은 코드의 퀄리티를 측정하는 지표가 되기도 한다.

코드 스타일은 통일될수록, 대표적인 스타일을 사용할수록 좋다. 파이썬을 사용하는 조직에서 별다른 코드 스타일 없이 마음대로 코드를 작성하는 것보다, PEP 8같은 스타일 가이드를 따르며 코드를 작성하는 게 결과적으로 더 높은 퀄리티의 코드를 만들어낼 가능성이 높다.

사실 좀 귀찮다.

파이썬은 다행히도 언어를 만든 쪽에서 직접 코딩 컨벤션을 지정해 두었기 때문에, 취향 차이로 엇갈릴 일이 비교적 적다. 그러나 컨벤션에 따라 함수와 함수 사이에는 blank line을 두 개 둔다거나, line length가 너무 길어지면 코드를 여러 줄로 나누어 작성한다거나 하는 게 마땅히 해야 하는 일임에도 불구하고 사실 좀 귀찮다.

파이썬의 공식 스타일 가이드인 PEP 8에는 아래와 같은 것들이 명시되어 있다.

  • indent는 4 spaces를 사용한다.
  • 함수와 함수 사이에 blank line을 2개 둬야 한다.
  • line length의 최대치는 79다.
  • 각각의 import들은 comma로 연결하지 말고, 별도의 라인으로 나눈다.

다들 알만한 것들만 몇 개 가져왔지만 사실 이보다 훨씬 많은 스타일 가이드들이 명시되어 있다. 그런데 보통 컨벤션이라는 게 기계적으로 측정 가능한 것들이 많아서, 따로 외우고 다닐 필요 없이 그냥 자동으로 검사해주는 도구가 있을 법 하다. 코드를 체크만 해주는 style checker, 체크한 결과를 가지고 변경까지 해주는 formatter로 나눌 수 있겠다.

Code Style Checker

pycodestyle(pep8)

PEP 8에 명시된 스타일 가이드를 기반으로 파이썬 코드를 체크해서 알려주는 CLI 라이브러리다. pep8이라는 이름으로 더 많이 알려져 있다. 실제로 이 라이브러리는 pep8이라는 이름으로 개발하기 시작해, 2016년에 귀도 반 로썸의 부탁으로 이름을 pycodestyle로 변경하여 개발을 지속하고 있는 상태다. usage는 아래와 같다.

$ pycodestyle --first optparse.py
optparse.py:69:11: E401 multiple imports on one line
optparse.py:77:1: E302 expected 2 blank lines, found 1
optparse.py:88:5: E301 expected 1 blank line, found 0
optparse.py:222:34: W602 deprecated form of raising exception
optparse.py:347:31: E211 whitespace before '('
optparse.py:357:17: E201 whitespace after '{'
optparse.py:472:29: E221 multiple spaces before operator
optparse.py:544:21: W601 .has_key() is deprecated, use 'in'

pyflakes

pyflakes는 pycodestyle처럼 파이썬 코드를 verification해주는 툴인데, 스타일에 대한 검사가 제외되어 있다. error checker에 더 가까울 수 있다고 볼 수 있는데, 예를 들면 아래와 같은 것들이다.

  • import되었지만 사용하지 않는 라이브러리. ex) 'click' imported but unused
  • 정의되지 않은 이름. ex) undefined name 'res'

스타일 체킹이 빠졌으니 일반적으로 pycodestyle처럼 스타일까지 체크하는 툴들보다 빠르다. usage는 아래와 같다.

$ pyflakes ./parse.py
./parse.py:2: 'click' imported but unused
./parse.py:3: undefined name 'res'

이미 PyCharm같이 잘 되어 있는 IDE를 쓰고 있다면 IDE가 warning을 보여주는 것에 익숙해서 별 감흥이 없을 수도 있을텐데, vim같은 걸로 일반 텍스트 파일 수정하듯 파이썬 코드를 작성한다고 생각하면 이게 왜 쓰이는지를 느낄 수 있을 것이다.

pylint

lint라는 이름이 달린 툴답게 이런저런 방면에서 코드를 검사해 준다. usage를 보면 어떤 느낌인지 알 수 있을 것이다.

$ pylint simplecaeser.py
************* Module simplecaesar
simplecaesar.py:16:19: C0326: Exactly one space required around assignment
            encoded=encoded + letters[x]
                   ^ (bad-whitespace)
simplecaesar.py:1:0: C0111: Missing module docstring (missing-docstring)
simplecaesar.py:5:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:6:0: C0103: Constant name "choice" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:7:0: C0103: Constant name "word" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:8:0: C0103: Constant name "letters" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:9:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)

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

style checking, error checking, 리팩토링과 관련된 조언과 함께 코드에 점수도 매겨 준다. 외부 의존성으로 syntax tree를 위한 astroid, 알파벳 순서로 import를 정리하는 isort, McCabe 복잡도를 구현한 mccabe만을 가지고 있는 것을 보아, 자체적인 lint 알고리즘을 사용했다는 것을 알 수 있다. 설정을 customize할 수 있는 범위도 꽤 다양해서, IDE를 사용하고 있더라도 서드파티로 추천하고 싶다.

flake8

pycodestyle + pyflakes + 복잡도 검사 기능이라고 생각하면 된다. pylint처럼 복잡도 검사를 위해 mccabe를 사용하지만, style/error checking에 대해 pycodestylepyflakes를 사용한다. 그냥 라이브러리 여러 개 묶은 wrapper 형태다. 아래는 usage다.

$ flake8 ./parse.py
./parse.py:2:1: F401 'logging' imported but unused
./parse.py:3:1: E302 expected 2 blank lines, found 0
./parse.py:6:5: E303 too many blank lines (2)
./parse.py:7:24: W291 trailing whitespace
./parse.py:8:15: E221 multiple spaces before operator
./parse.py:9:18: E701 multiple statements on one line (colon
./parse.py:15:1: W391 blank line at end of file

Exxx, Wxxx 형태의 pycodestyle 실행 결과Fxxx 형태의 pyflakes 실행 결과가 함께 보인다. 복잡도가 높은 부분이 발견되었다면, 해당 detection 정보가 C9xx 형태의 에러 코드로 보여진다.

Code Formatter

autopep8

pycodestyle의 결과를 기반으로 코드를 수정해 준다. 그런데 개인적으로는 조금 더 적극적으로 formatting해주면 더 좋을 것 같았다. README.md에 적혀 있는 Usage 파트를 살펴보면 너무 보수적인 formatting 규칙을 알 수 있다.

yapf

Google이 만든 formatter인데, autopep8처럼 특정 가이드라인 기반으로 lint error를 파악하고 이것을 제거하는 것보단 '시각적으로 좋아 보이는 코드'를 만드는 데에 주력한다. 정말 괜찮다고 느꼈는데, 아래와 이어지는 글에서 이야기할 black을 써보고 나서부턴 그냥 계속 black만 쓰고 있다.

black

CPython의 개발을 주도하고 있는 파이썬 공식 organization인 Python에서 개발한 formatter다. README에 black이 어떤 방식으로 코드를 포매팅하는지 직관적이고 쉽게 설명되어 있고, black이 자동으로 파일들을 포매팅하도록 PyCharm에서 설정하는 방법이 제공되는 등 code reformatting 라이브러리에게 사용자가 무엇을 원하는지 잘 알고 있는 툴처럼 느껴졌다.

필자의 환경

  • PyCharm에 File Watcher 플러그인을 설치해서, 파이썬 모듈이 저장될 때마다 black을 실행하게 해 두었다. README.md의 Editor integration 파트에 잘 설명되어 있다.
  • PyCharm에 pylint 플러그인을 설치해서 real-time으로 warning이 보여지게 해 두었다.