Logo

Black으로 파이썬 코드 스타일 통일하기

코드 스타일

파이썬과 같이 사용차층이 넓은 범용 프로그래밍 언어의 경우, 개발자들이 선호하는 코드 스타일이 다양해지게 됩니다. 개인 프로젝트에서는 자신이 선호에 따라 어떤 방식으로 코드를 포맷팅하든지 코드가 돌아가기면 하면 큰 상관이 없지만, 협업 프로젝트에서는 이러한 개발자 간의 사소한 코드 스타일 차이로 불필요한 감정 싸움이 발생하기도 합니다.

예를 들어, 똑같은 문자열을 표현하기 위해서 개발자 A는 홑따옴표를 사용하고 싶은데, 개발자 B는 쌍따옴표를 사용하고 싶습니다. 만약에 개발자 A가 작성한 코드를 나중에 개발자 B가 수정하면서 홑따옴표를 모두 쌍따움표로 바꿨는데, 코드 리뷰 과정에서 개발자 A가 이 사실을 알게된다면? 이게 참 별 일도 아닌데 직접 곁어보면 따지기도 그렇고 모른체 하기도 찝찝하고… 암튼 이렇게 협업 프로젝트에 표준화된 코드 스타일이 없으면 팀워크에 나쁜 영향을 줄 수 있습니다.

이런 경우, 코드 포맷팅 도구(Code Formatter)를 사용해서 코드 스타일을 통일시키면 문제를 해결할 수 있습니다.

Black이란?

Black은 최근 파이썬 커뮤니티에서 가장 널리 쓰이고 있는 있는 코드 포맷터입니다. 기존 코드 포맷터와 달리 Black은 설정의 여지가 거의 없어서 정해놓은 특정 포맷팅 규칙을 그대로 따라야합니다. 그래서 처음에 Black을 접햇을 때 Black이 모든 코드를 일률적으로 포맷팅하는 방식에 거부감이 느껴질 수도 있습니다. 이처럼 유연하지 않은 코드 포맷터가 개발자들 사이에서 이렇게 인기를 얻을 수 있었던 이유는 무엇일까요?

바로 팀 내에서 개발자간에 코드 스타일을 협의하고 동의 하에 표준화하는 과정 자체에 상당한 소모적이기 때문입니다. 게다가 대부분의 개발자들이 문자열을 표현하기 위해서 홑따옴표를 사용하든 쌍따옴표를 사용하든 크게 개의치 않습니다. 사실 정말 중요한 것은 하나의 코드 스타일을 기준으로 모든 개발자가 일관성 있는 코드를 작성하는 것입니다.

협업 프로젝트에서 Black을 사용하게 되면 더 이상 코드 스타일에 대해서 개발자간에 왈가왈부 할 일이 없어집니다. Black이 자신의 코드를 포맷팅 하는 방식이 좋든 싫든 더 이상 개인의 특정 선호는 중요하지 않게 됩니다.

Black에서 정해놓은 코딩 스타일들은 오랜 커뮤니티의 다양한 의견이 수렴을하고 여러 프로젝트에서 여러 가지 실험을 통해 결정었습니다. 따라서, Black은 매우 특수한 프로젝트가 아닌 이상 대부분의 프로젝트에서 무난하게 사용할 수 있습니다. 이것이 수많은 오픈 소스 파이썬 프로젝트들과 파이썬을 사용하는 수많은 기업들에서 Black을 정식 코드 포맷터로 채택해서 사용하는 이유입니다.

Black CLI

Black은 기본적으로 터미널 상에서 CLI 도구로 손쉽게 접해볼 수 있습니다. 먼저 pip 패키지 매니저를 이용해서 Black 패키지를 설치합니다.

$ pip install black

그 다음, 파이썬 파일을 하나 생성 후에 다음과 같이 코드 스타일이 엉망인 코드를 작성합니다.

  • main.py (black으로 포맷팅 전)
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/python/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:
     ...

regular_formatting = [
    0,  1,  2,
    3,  4,  5,
    6,  7,  8,
]

그 다음, 작성한 파일을 대상으로 black 커맨드를 --check 옵션을 줘서 실행해보면 하나의 파일이 포맷팅이 필요하다고 나옵니다.

$ black --check main.py
would reformat main.py
Oh no! 💥 💔 💥
1 file would be reformatted.

이 번에는 --check 옵션을 빼고 실행을 해보면 black이 파일 내의 코드를 포맷팅하여 저장해주는 것을 볼 수 있습니다.

black main.py
reformatted main.py
All done! ✨ 🍰 ✨
1 file reformatted.

실제 파일을 열어보면 다음과 같이 읽기 쉽게 깔끔하게 정돈된 코드를 볼 수 있으실 겁니다. 💅

  • main.py (black으로 포맷팅 후)
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/python/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:
        ...


regular_formatting = [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
]

코드 편집기 설정

코드의 양이 많은 실제 프로젝트에서는 위와 같이 터미널에서 매번 Black을 실행하여 포맷팅을 하는 것은 어리석은 일일 것입니다. 다행히도 대부분의 파이썬 코드 편집기에서 변경된 코드를 저장할 때 마다 Black이 자동으로 샐행되도록 설정을 해줄 수 있습니다.

예를 들어, 제가 주로 쓰는 VSCode의 경우, 다음과 같은 설정을 .vscode/settings.json에 추가해주기만 하면 됩니다. 첫번째 설정은 코드가 저장할 때 마다 자동으로 포맷팅을 하기 위함이고, 두번째 설정은 VSCode의 기본 포맷터 대신에 Black을 사용하기 위함입니다.

{
  "editor.formatOnSave": true,
  "python.formatting.provider": "black"
}

다른 코드 편집기의 설정 방법에 대해서는 공식 레퍼런스를 참고바라겠습니다.

Git hook 설정

코드 편집기 설정은 어디까지나 개인의 선택 사항이기 때문에 프로젝트 차원에서 포맷팅이 되지 않은 코드를 커밋하려고 하는 것을 방지하는 것이 바람직할 것입니다.

먼저, Git hook 스크립트를 실행해주는 pre-commit 패키지를 설치합니다.

$ pip install pre-commit

그 다음, .pre-commit-config.yaml 파일을 생성 후에 다음과 같이 설정 내용을 추가합니다.

repos:
  - repo: https://github.com/psf/black
    rev: stable
    hooks:
      - id: black

이제, pre-commit 커맨드를 실행하여 방금 작성한 Git hook 스크립트를 설치해줍니다.

$ pre-commit install
pre-commit installed at .git/hooks/pre-commit

마지막으로, 코드 에디터의 자동 포맷팅을 해제하고, 일부로 포맷팅이 엉망인 코드를 작성 후에 저장하고 커밋을 시도해봅니다.

$ git commit
black....................................................................Failed
- hook id: black
- files were modified by this hook

reformatted /Users/dale/learn/learn-python/main.py
All done! ✨ 🍰 ✨
1 file reformatted, 9 files left unchanged.

위에서 설정한 Git hook 스크립트가 실행되어 커밋이 실패하고 Black이 포맷팅을 해주었습니다! 🤗 이제 Black이 만들어준 변경분을 다시 git add 후에 git commit하면 됩니다.

이제부터 git commit을 할 때 마다 Black이 자동으로 포맷팅을 검사하고 필요 시에 코드를 수정해주기 때문에, 코드 에디터에 자동 포맷팅 설정을 안 해놓은 개발자도 자연스럽게 Black을 통해 일관적인 코드 스타일을 유지할 수 있습니다.

pre-commit 도구에 대한 자세한 설명은 관련 포스팅를 참조바랍니다.

마치면서

이상으로 파이썬 코드 포맷터인 Black을 이용하는 방법에 대해서 간단히 살펴보았습니다. Black에 대한 더 자세한 내용은 아래 링크를 참고바라겠습니다.