save and auto / shift + option + f 는 이제 넣어둘때입니다! PEP 8 기준 python code style tool - Linter로 사용할 수 있는 flake8 그리고 조합이 좋은 black을 살펴보고 pre-commit 까지 설정해보자.
처음부터 끝까지 나 혼자만 작업하는 코드, toy project라면 코드 스타일은 어짜피 하나 밖에 없어서 혼란이 없다. 하지만 거의 대부분의 경우엔 협업으로 진행한다. (아이러니하게 혼자만든 코드도 다음날 보면 왜 이렇지? 하는게 많다 🥹)
사람마다 선호하는 코드 스타일이 있다. 대표적으로 sigle('), double quotation(")을 생각할 수 있다. 언어의 유연함이나 자유로움이 크면 클 수록 이런 면모가 두드러진다.
하지만 개발을 하는 사람이 많아질 수 록 _ 언더바 사용의 경우, 한 라인의 글자 수 제한하기, 함수 정의시 매개변수를 줄바꿈으로 묶어주기, in-line으로 매개변수 정의하기 등
과 같은 경우는 다양해진다.
처음보는 code는 책과 같다. 화자의 의도를 파악해야 한다. 근데 그 화자가 10명, 20명이면 가독성은 산으로 간다. 나와 남을 위해 마치 한 사람이 짜서 맞춘 스타일의 코드 가 필요하다.
린트(lint) 또는 린터(linter)는 소스코드를 분석하여 의심스럽거나, 에러를 발생하기 쉬운 코드에 표시(flag)를 달아 놓는 것을 말한다. 원래는 C 언어에서 사용하던 용어였으나 지금은 다른 언어에서도 일반적으로 사용된다.
pip install flake8
설치 후 linting 하고 싶은 file을 flake8 filename
으로 사용하면 된다. 파일 명 뿐 아니라 특정 디렉토리를 적어도 실행이 가능하다. >> flake8 ./proxy_working_test.py --show-source
./proxy_working_test.py:17:80: E501 line too long (87 > 79 characters)
res = requests.get("https://icanhazip.com/", proxies=proxy_dict, timeout=2)
^
--show-source
option으로 수정 사항을 소스 코드에서 바로 표기해 준다. > flake8 --select E123,W503 ./proxy_working_test.py
> flake8 --extend-ignore E203,W234 ./proxy_working_test.py
./proxy_working_test.py:17:80: E501 line too long (87 > 79 characters)
위와 같이 특정 exception(error)를 제외 또는 타겟팅해서 linting이 가능하다. 다양한 option과 error code에 대한 설명은 공식가이드에 상세하게 설명되어 있다.
Flake8 Rules 와 같이 정리해둔 곳에서 찾아보는 것이 더 빠르다.
Configuration File
을 만들어서 (user 별로) 전역 사용이 가능하다.
위와 같이 전역 파일을 생성하여 flake8 사용 설정이 가능하다. 하지만 가장 많이 사용되는 방법은 project의 최상위 경로에 보통 setup.cfg
, tox.ini
, .flake8
등과 같은 파일을 만들어서 설정을 한다.
.flake8 파일을 아래와 같이 만들어두면 좋다.
[flake8]
show-source = true
extend-ignore = E203
exclude =
# No need to traverse our git directory
.git,
# There's no value in checking cache directories
__pycache__,
# The conf file is mostly autogenerated, ignore it
docs/source/conf.py,
# The old directory contains Flake8 2.0
old,
# This contains our built documentation
build,
# This contains builds of flake8 that we don't want to check
dist
max-complexity = 10
flake8
만 cli로 실행해도 다음 cli를 실행한 것과 같은 효과를 얻는다. (참고로 E203는 Whitespace before ':' 이다)flake8 --show-source --extend-ignore E203 \
--exclude .git,__pycache__,docs/source/conf.py,old,build,dist \
--max-complexity 10
하지만 flake8은 코드 스타일의 강제화와 강제 변환이 없는 단점(노력하면 사실 장점..)이 있다. 그래서 black과 많이들 섞어 사용한다.
black에서 보통 line 제한을 88개로 하기 때문에 flake8에서 max-line-length = 88
이상으로 하는것이 서로 충돌안하게 좋다.
Black is the uncompromising Python code formatter, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.
pip install black
설치 후 linting 하고 싶은 file을 black filename
으로 사용하면 된다. 파일 명 뿐 아니라 특정 디렉토리를 적어도 실행이 가능하다. (flake8과 동일)
만약 당신이 아래와 같은 코드를 전임자로 부터 받았다고 하자. flake8을 돌리고 나오는 error를 한땀 한땀 고치고 싶을까?
from seven_dwwarfs import Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey, Doc
x = { 'a':37,'b':42,
'c':927}
x = 123456789.123456789E123456789
if very_long_variable_name is not None and \
very_long_variable_name.field > 0 or \
very_long_variable_name.is_debug:
z = 'hello '+'world'
else:
world = 'world'
a = 'hello {}'.format(world)
f = rf'hello {world}'
if (this
and that): y = 'hello ''world'#FIXME: https://github.com/psf/black/issues/26
class Foo ( object ):
def f (self ):
return 37*-2
def g(self, x,y=42):
return y
def f ( a: List[ int ]) :
return 37-a[42-u : y**3]
def very_important_function(template: str,*variables,file: os.PathLike,debug:bool=False,):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, "w") as f:
...
# fmt: off
custom_formatting = [
0, 1, 2,
3, 4, 5,
6, 7, 8,
]
# fmt: on
regular_formatting = [
0, 1, 2,
3, 4, 5,
6, 7, 8,
]
> flake8 test.py
test.py:1:1: F401 'seven_dwwarfs.Grumpy' imported but unused
from seven_dwwarfs import Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey, Doc
^
test.py:1:1: F401 'seven_dwwarfs.Happy' imported but unused
from seven_dwwarfs import Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey, Doc
^
test.py:1:1: F401 'seven_dwwarfs.Sleepy' imported but unused
from seven_dwwarfs import Grumpy, Happy, Sleepy, Bashful, Sneezy, Dopey, Doc
^
...(중략)
test.py:2:6: E201 whitespace after '{'
x = { 'a':37,'b':42,
^
test.py:2:11: E231 missing whitespace after ':'
x = { 'a':37,'b':42,
^
test.py:2:14: E231 missing whitespace after ','
x = { 'a':37,'b':42,
^
test.py:2:18: E231 missing whitespace after ':'
x = { 'a':37,'b':42,
^
test.py:4:1: E128 continuation line under-indented for visual indent
'c':927}
^
test.py:4:4: E231 missing whitespace after ':'
'c':927}
^
test.py:8:4: F821 undefined name 'very_long_variable_name'
if very_long_variable_name is not None and \
very_long_variable_name.field > 0 or \
very_long_variable_name.is_debug:
^
...(하략)
black
을 쓴다. > black test.py
reformatted test.py
All done! ✨ 🍰 ✨
1 file reformatted.
-l
: 한 라인에 최대 글자 수 (초기값 88)
--diff
: 파일을 변경하지 않고 변경되는 부분을 콘솔로 보여준다.
--color
: --diff를 사용했을 때 변경점에 색을 입힌다.
더 다양한 옵션은 공식 가이드 에서 확인할 수 있다.
black {파일명 또는 폴더명} -l 80 --diff --color
으로 코드 변경을 강제화 하지않고, 바뀌는 부분을 아래와 같이 체크가 가능하다.
.flake8
파일을 만드는 flake8과 같이, PEP518을 통해 .pyproject.toml
file을 가지고 설정할 수 있다. 공식 홈페이지 설정 확인하기
조금 다양한 설정 값들이 있는 .pyproject.toml
은 여기 깃허브 링크 에서 체크 가능하다. 심플하게는 아래와 같이 사용할 수 있다.
[tool.black]
line-length = 88
target-version = ['py37', 'py38']
include = '\.pyi?$'
extend-exclude = '''
/(
# The following are specific to Black, you probably don't want those.
| blib2to3
| tests/data
| profiling
)/
'''
# We use preview style for formatting Black itself. If you
# want stable formatting across releases, you should keep
# this off.
preview = true
[project]
name = "black"
description = "The uncompromising code formatter."
requires-python = ">=3.7"
authors = [{ name = "Nuung", email = "hyeon.wo.dev@gmail.com" }]
이제 위 code style linting을 vscode save, git commit 할 때 (git hook) 등을 활용해 강제화 해버리자!
F1
button으로 python linter 검색해서 .vscode > settings.js
file을 만들자! 그리고 아래 그럼과 같이 세팅한다.{
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.lintOnSave": true,
"python.formatting.provider": "black",
"editor.formatOnSave": true
}
"python.linting.flake8Args": [
"--config=django_all_about/.flake8",
"--max-line-length=120",
"--ignore=W291",
],
--config
값과 같이 .flake8
과 같은 linting config file이 root directory에 존재하지 않는다면 꼭 명시해줘야 한다. 그렇지 않으면 린팅 옵션이 default로만 세팅되어서 [ 왜 안돼!? ] 와 같은 물음이 반복될 것이다. Git 에는 특정 이벤트에 특정 스크립트를 실행하는 Hook 이라는 기능이 있다. 이 중 커밋하기 전에 실행되는 훅인 pre-commit 에서 Lint를 하는 작업을 하게 할 수 있다. pip install pre-commit
으로 pip lib을 설치하자. (전역적으로 brew install pre-commit
을 사용할 수 도 있다.)
pre-commit sample-config > .pre-commit-config.yaml
으로 sample 기반으로 .pre-commit-config.yaml
file을 만들어주자. 기본 내용은 아래와 같다. (다양한 hook 설정값 확인하기)
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- id: check-json
- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black
name: black
description: "Black: The uncompromising Python code formatter"
entry: black
language: python
minimum_pre_commit_version: 2.9.2
require_serial: true
types_or: [python, pyi]
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
args: ["--config=django_all_about/.flake8"]
name: flake8
description: "`flake8` is a command-line utility for enforcing style consistency across Python projects."
entry: flake8
language: python
types: [python]
require_serial: true
이렇게 설정한 뒤 pre-commit autoupdate
를 통해서 우리가 세팅한 .pre-commit-config.yaml
file을 알맞게 버전 세팅을 해주자!
참고로 위 .vscode > setting.json
때와 같이 flake8의 conf파일이 root에 없으면 pre-commit conf에서도 args
를 줘서 저렇게 경로 명시를 꼭 해줘야 한다.
이게 끝이 아니라 실제 commit 을 할 때마다 위 파일이 pre-commit 단계에 실행되도록 pre-commit install
를 실행해주자! 그러면 pre-commit installed at .git/hooks/pre-commit
라고 나온다.
최초 실행시에는 env setting을 위한 intalling 이 있어서 시간이 좀 걸린다. 해당 작업이 다 된다면 아래와 같이 나온다.
이제 [ vscode (IDE) linting config + flake8 & black 전역설정 + pre-commit ] 으로 누구나 어떤 코드를 짜던 한 사람이 짠 것 같은 코드 스타일을 추구할 수 있게 되었다.
하지만 이런 설정은 강제가 되어서는 안된다. PEP8에서도 "절대적으로 따르는 것은 멍청하다" 라는 표현을 한다. 가독성을 살리면서 창의적이고 다른 훨씬 좋은 표현방법을 찾을 수 있도록 해야한다.
하지만 현업에서 쓰는 코드는 본인의 일기장이나 연습장이 아니다. 적어도 지켜야하는 최소한의 규칙은 지키고 유지 보수를 위해 뒤를 위해 읽히는 코드를 작성하자.
마무리로 이번에 ifkakao의 "섬세한 ISFP의 코드 가독성 개선 경험" 글을 추천한다.