먼저 한 가지 고백을 해야 할 것 같은데, Python으로 개발을 해온 지난 5년 넘게 저는 PEP8을 읽어본 적이 없었습니다. 들어본 적이야 있었지만, 실제로 읽고 적용해 봐야겠다는 생각은 하지 못했죠.

변명을 하자면 이는 타입이 없고(typeless) 엄격한 문법 속에서도 꽤나 자유롭게 개발이 가능한 Python의 특징 덕분이기도 했고, 개인적 취향과 PEP8이 많이 달랐기 때문이기도 했습니다. 그렇지만 취업을 하고, 개인적인 프로젝트에서 사용하는 스크립트 수준이 아닌 시스템을 구성하는 수준의 코드를 작성해야 할 때 더 이상 PEP8을 읽지 않고 따라가기는 힘들더라구요. 그래서 쏟아지는 코드 리뷰의 피드백 속에, 저는 PEP8을 읽게 되었습니다.

이번 글에서는 PEP8의 짧은 요약, 그리고 이로부터 얻은 깨달음과 느낀 점을 적어 보려 합니다.

PEP시리즈에 대해

PEP8은 PEP 중에서도 유독 유명한 제안서인데요, Python의 문법 자체에 대한 내용을 담고 있어서 그렇습니다. (PEP에 대한 정의와 어떤 내용을 담고 있는지를 알고 싶다면 PEP1을 읽는 것을 추천합니다.) PEP8을 대략 3개의 큰 내용으로 나눠 보자면 다음과 같습니다.

  • Python 코드 자체의 문법

    탭과 스페이스(스페이스를 쓴다면 몇 칸에 탭을 맞출 것인지)부터 import는 어떤 순서로 정렬할 것이며 연산자에 대해 띄어쓰기는 어떻게 할 것인지와 같은 코드 자체의 문법을 설명합니다.

  • Python 코드 이외의 문법들

    튜플을 정의할 때 콤마를 마지막에 써야 하는 경우에 대한 설명이나 주석, Doc String과 같은 내용들을 설명합니다.

  • 언어적 규칙들

    Python에서 어떤 명명 규칙을 사용하는지, 어떤점들이 OOP 규칙과 다른지 등을 설명합니다.

Python 코드 자체의 문법 (Code Lay-out)

이 부분에서는 코드 그 자체를 작성하는 방법에 대해 주로 설명합니다. 들여쓰기, 한줄 당 들어가는 최대 글자 수, 언제 띄어 쓰고, 언제 개행하는지 등의 내용입니다.

  1. 들여쓰기는 스페이스 4칸

    각 스코프는 스페이스 4칸으로 구분되며, 스코프가 생기지 않는 구문들(함수의 파라미터를 개행한다던지, 백 슬래시로 개행을 한다던지)에 대해서는 조금 여유 있게 적용합니다.

  2. 탭 VS 스페이스

    탭과 스페이스는 케케묵은 싸움이지만, PEP8에서는 스페이스를 권장합니다.

  3. 한 줄에 적는 글자 수는 79자 이내로

    Python 기본 라이브러리는 전부 79자 이내로 적혀 있습니다. 상황에 맞게 더 늘릴 수는 있지만, 그때도 99자는 넘지 않을 것을 권장합니다.

  4. 함수와 클래스의 정의 앞에는 빈 줄을 2개 넣는다

  5. 연산자가 들어간 여러 줄의 계산식은 연산자를 제일 앞에 둬라

    문장의 제일 뒤에 있으면 알아보기 힘들고, 넘어가기 쉽기 때문에 제일 마지막이 아닌, 다음 라인의 첫 번째에 써주도록 권장합니다.

    funiest_joke = ("파이가"
                     + "햇볕을 쬐면,"
                     + "파이썬")
  6. 소스코드의 인코딩은 UTF-8으로

  7. 와일드카드 임포트(**import *)는 피하라**

    1급 함수 언어답게 이름을 자유롭게 할당할 수 있는 Python에서 같은 이름을 재사용하는 경우는 종종 생깁니다. 그렇다 보니 와일드카드로 임포트를 하게 되면 어떤 이름이 현재 스코프에 존재하는지 명확하지 않게 되죠. 그러므로 헷갈리지 않기 위해 임포트 할 때에는 명확하게 이름으로 임포트 해야 합니다.

  8. 모듈 레벨의 Dunder변수들은 최상단에

    Dunder변수는 언더바 두 개로 시작하는 변수들을 말하는데, __all__이나 __version__과 같은 변수들입니다. 물론 Doc String이 언제나 최상단이고 다음은 import future가, 그 뒤에 Dunder변수들, 그리고 일반적인 import문과 변수, 함수들이 오면 됩니다.

Python 코드 이외의 문법들

  1. 큰따옴표와 따옴표는 마음대로, 하지만 일관성 있게

    PEP8에서는 어느 한쪽을 권장하지 않고, 원하는 쪽으로 결정하라고 합니다. 하지만 한번 결정한 것은 계속해서 지킬 것을 권장하고, "menu = 'spam'"와 같이 양쪽 다 사용될 경우에는 여유있게 사용하면 가독성을 높일 수 있다고 합니다.

  2. 스페이스를 넣는 법은 상황에 따라 다르다

    이 부분은 PEP8에서 온갖 예시를 들면서 설명하기에 딱히 요약하기 힘든 부분입니다. 그렇지만 대부분의 경우는 일반적인 문장 부호를 넣는 법과 비슷하게 스페이스와 콤마를 사용하도록 권장합니다.

    ham[1:9], ham[1:9:3]
    if x == 4: print x, y; x, y = y, x
    ham[lower + offset : upper + offset]
    menu["spam"] = spam[1]
  3. 콤마

    이 외에는 딱히 사용하지 않아도 괜찮지만 사용한다면 여러 이점을 얻을 수 있는 것들입니다. 가령 깃으로 소스코드를 관리하고 있다면 한 줄 추가 시 다음과 같은 상황이 나타나게 됩니다.

    @@ -2,5 +2,6 @@ menus = [
         'egg and bacon',
         'egg, bacon and spam',
    		 'egg and spam',
    -    'egg, bacon and spam'
    +    'egg, bacon and spam',
    +    'egg, bacon, sausage and spam'
    ]

    이를 막기 위해 리스트의 마지막에도 콤마를 추가한다면 언제나 마지막 줄만 수정된 것으로 인식될 것입니다.

  4. 주석

    주석이 있다면 코드의 수정과 함께 주석을 수정해 주어야 합니다. 코드만 수정하고 주석은 수정하지 않는다면 주석은 코드와 다른 이야기를 하게 되고 헷갈릴 수 밖에 없죠. 그래서 Python에서는 주석이 없이도 이해할 수 있는 코드가 최선이고 어쩔 수 없이 주석을 달아야 한다면 최소한으로 달 것을 권장합니다.

  5. Documentation String

    모든 함수, 모듈, 클래스와 메서드에 대해 Doc String을 작성할 것을 권장하는데 정작 잘 작성하기 위한 내용은 PEP257을 읽으라는 것으로 대신합니다.

언어적 규칙들

  1. 공개 api의 경우는 아래의 규칙을 무조건 따르기보다 용도에 알맞게 작명할 것

  2. Python에는 수많은 네이밍 규칙들이 있다.

    Python에서는 많은 네이밍 규칙을 적용하는데, 대부분은 일반적인 언어들과 크게 다르지 않습니다. 그렇지만 특별한 것들이 몇 가지 있는데, 언더바를 붙이는 이름들이 그 예시입니다.

    _single_leading_underscore     # 전역 변수나 함수의 이름으로 사용한 경우 import *이 가져오지 않는다.
    single_trailing_underscore_    # Python 키워드와 충돌을 피하기 위해 사용함
    __double_leading_underscore    # 외부에서 참조할 때, 자동으로 이름을 변경함
    __double_leading_and_trailing_underscore__ # Python에서 사용하는 매직 변수들. 따로 만들지 말 것을 권장.
  3. 일반적인 네이밍 규칙들

    대문자 I(아이), 대문자 O(오), 소문자 l(엘)등을 변수명으로 쓰지 말 것을 시작으로, 해야할 것과 하지 말 것들에 대해 설명합니다.

  4. OOP를 위한 규칙들

    엄밀히 말하자면 일반적인 OOP 기반 언어와 다른 점을 설명하는 부분입니다. 보통은 getter, setter를 이용해 클래스 내부 상태를 조절하지만, Python에서는 간단한 변수의 경우 이름으로 직접 접근해서 수정할 것을 권장합니다. private이 특별히 존재하지 않는 것도 특징 중 하나입니다. 구현 레벨에서 이야기를 하고 있어서 어떤 특징들이 있는지 쉽게 알 수 있습니다.

  5. 좀 더 나은 Python코드를 위한 팁들

    이 부분도 예시를 들면서 어떻게 하면 코드를 더 예쁘게, 명확하게 작성할 수 있는지에 대해 이야기합니다. Python2와 3의 차이부터 with키워드를 사용하는 방법까지 많은 부분을 이야기하고 있는데요. 많이 어렵지 않고 한 번씩 보면 이해할 만한 내용들이라서 직접 보는 것을 추천합니다.

  6. Type hint를 염두에 둔 작성법

    PEP8에서는 함수에서의 사용법과 변수에서의 사용법에 대해 설명합니다. 사용하고 싶지 않을 땐 어떻게 하는지, 어떤 경우 추가해야 하는지, 과거 호환성은 어떻게 하는지 등이죠. 설명은 간략하게 하고 넘어가는데, 좀 더 자세한 내용은 각각 PEP484와 PEP526을 참고하라고 합니다.

소감

소문난 이름에 비해 PEP8은 상당히 짧고 간단한 내용을 담고 있습니다. 실제로 그중 대부분은 예시를 통해 설명하는 페이지이며, 웬만한 책 한 챕터보다 더 짧은 구성입니다. 그렇다고 PEP8의 내용이 얕다는 것은 아닙니다. Python 코드가 지향하는 점, 어떤 식으로 작성하도록 설계했는지, 어떤 방식을 쓰면 더 가독성을 높일 것인지에 대해 생각해 볼거리가 많습니다.

결론적으로, PEP8에서 이야기하는 것은 꽤나 명확합니다.

읽기 쉬운 코드를 작성하자.

PEP8의 소개를 제외한 첫 번째 문단에서도 어떤 코드가 읽기 쉬운 코드인지, 왜 읽기 쉬운 코드를 작성해야 하는지에 대해 이야기합니다.

One of Guido's key insights is that code is read much more often then it is writen. 
(귀도의 가장 중요한 통찰중 하나는 우리가 작성보다 더 많이 읽기 때문에 우리는 작성하기 쉬운 코드가 아닌,
 읽기 쉬운 코드를 만들어야 한다는 것이다.)

PEP8을 읽고 나니 그동안 깨닫지 못했던 저의 잘못들이 눈에 들어왔습니다. 소소한 띄어쓰기 하나도 사실은 가독성을 해치고 있었는데, 저는 대수롭지 않게 여겼던 것 뿐이었죠. 앞으로도 제대로 된 코드란 무엇인가, 읽기 쉬운 코드란 무엇인가에 대해서 생각해 볼 것들이 여전히 많이 남아있을 것 같네요.

🙌
연애의 과학 프로덕트팀에서는 백엔드 엔지니어를 채용중이에요 프로덕트와 서버, DevOps를 사랑하고 능력있는 Pro-duck들이 모여, 연애의 과학을 만들고 있어요. 지금 연애의 과학팀에 합류하세요! → 채용공고 바로가기