파이썬은 특정인 또는 집단이 언어 발전을 주도하기 보다는, 수많은 사람들의 의견을 청취하고 토론하며 수용하여 발전해왔다. 이 과정에서 사용자로부터 제안된 수많은 주제들을 PEP(Python Enhancement Proposals)라는 prefix가 붙은 문서로 관리하고 있다.

PEP-8은 그 중, 8번째 문서이며 파이썬 코드 스타일 가이드의 내용을 담고 있다. 본 포스트에서는 해당 내용 중, 자주 사용되는 항목들을 발췌, 의역, 재구성하여 다룬다. 쓰다보니 다 중요해보여서 거의 전부 담았다.

PEP8 요약 : https://yongwookha.github.io/Study/2022-02-26-pep8-summary

Docstring

본격적으로 내용을 다루기 전에, PEP-8에서는 다루지 않는 PEP-257 (Docstring)에 대한 내용들을 확인한다.

Docstring은 module, function, class, or method를 정의할 때, 설명을 위해 기술되는 텍스트다. 이러한 Docstring은 object__doc__ attribute로 지정된다.

모든 module은 보통 docstring을 가지고 있어야하며, module에 의해 export되는 모든 functionclass 또한 docstring을 가지고 있어야한다. __init__과 같은 Public method도 마찬가지다.

이렇게 적히는 docstring은 코드에서 documentation과 같은 역할을 하게 된다. Python bytecode 컴파일러에서 docstring 인식되지 않는다.

docstring : """ 로 둘러싸인 텍스트 comment : # 이후의 텍스트

One-line Docstring

One-line Docstring은 간단하고 명확하다.

1
2
def kos_root():
    """Return the pathname of the KOS root directory."""

Docstring 전에 공백 라인을 포함시키지 않는다. Docstring은 function이나 method의 효과를 설명이 아닌 명령으로 표현한다. 예를들어, “이 함수는 A를 하고 B를 리턴합니다.”가 아니라 “A하여 B리턴”과 같이 간단하게 적는다.

1
2
def function(a, b):
    """function(a, b) -> list"""

위와 같은 형식은 함수 내부를 확인할 수 없는 built-in 같은 C function 에만 적합하다. 일반적인 상황에서는 아래와 같이 쓰는 것이 좋다.

1
2
def function(a, b):
    """Do X and return a list."""

Multi-line Docstring

Multi-line의 경우 Docstring의 구성이 나뉜다. 그 구성은 다음과 같다.

  1. One-line Docstring과 같은 요약
  2. 한 줄 띄우기
  3. 상세한 설명
1
2
3
4
5
6
7
8
9
10
def complex(real=0.0, imag=0.0):
    """Form a complex number.

    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0)
    """
    if imag == 0.0 and real == 0.0:
        return complex_zero
    ...

요약 부분은 자동 indexing tool 등에서 이용될 수 있다. 한 줄에 맞추는게 중요하고, 나머지 Docstring과는 빈 줄로 구분되어야 한다. 요약 줄은 여는 따옴표(""") 뒤에 또는, 바로 아래 줄에 쓰도록 한다. 그리고 모든 Docstring은 여는 따옴표와 같은 indent에 쓴다. docstring을 모두 작성하면 따옴표를 닫고, 한 줄을 띄우고 작성한다.

이외에, script, module, package, function, class에 따라 참고해야할 사항을 아래에 정리한다.

대상 참고 사항
script - “usage” 메시지를 사용할 수 있어야함. (--help 옵션 등을 이용)
- 이러한 Docstring은 script의 기능이나 커맨드 라인 문법, 환경변수, 파일 등을 기술한다.
- “usage”메시지는 새로운 유저가 이해할 수 있을 정도로 가능하면 상세하고 충분하게 작성.
module - 외부로 export되는 class, exception, function들의 목록과 각각의 “one-line summary”을 간략하게 기술
package - 외부로 export되는 module, subpackage들의 목록과 각각의 “one-line summary”을 간략하게 기술
function - 함수의 동작과 arguments, return value(s), side-effects, exceptions, 호출 제약사항을 요약 서술
- Optional arguments는 표시되어야 함
class - public method와 instance들의 목록에 대한 요약 서술
- subclass가 있고, 추가된 interface가 있는 경우, 해당 목록에 대해 별도로 표시
- __init__ method를 포함한 개별 method들은 각자의 Docstring을 가짐.

그 외에, Handling Docstring Indentation 등의 Docstring 스타일 trimming 방법에 대한 내용들이 있는데, 이번 포스트에서는 다루지 않는다.


PEP-8

위에서 언급한 대로, PEP-8은 하나의 고정된 제약 사항이 아니다. Python 언어의 발전과 변화에 따라 끊임 없이 수정되거나 추가되어 과거의 convention과 달라질 수 있다. 따라서 PEP-8의 Introduction에서는 만약, style guideline이 있는 프로젝트에서 PEP-8의 내용과 충돌하는 convention이 있다면, 해당 프로젝트의 guideline을 따르라 충고한다.

일관성은 중요하다. 프로젝트에서의 일관성은 더 중요하다. 하나의 module이나 function에서의 일관성은 가장 중요하다. 하지만, PEP-8의 style guide는 “추천”이다. 적용이 어렵거나, 의심이 드는 경우, 프로그래머의 “판단”이 우선한다. 이 모든 것은 가독성을 위함이며 가독성은 장기적 업무 효율을 위함이다. 가독성을 훼손하거나, 업무 효율을 저해하는 경우, 굳이 강박적으로 style guide를 적용할 필요는 없다.

PEP-20에는 파이썬 디자인 원칙을 간결하게 정리한 “The Zen of Python”이라는 가 한편 있다. 읽어봄직해서 가져왔다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested. 
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it. 
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
아름다운게 못생긴 것보다 낫다.
분명한게 모호한 것보다 낫다.
간단한게 복잡한 것보다 낫다.
복잡한게 어려운 것보다 낫다.
수평이 계층보다 낫다.
여유로운게 밀집된 것보다 낫다.
가독성은 중요하다.
특별한 경우도 규칙을 어길 정도로 특별하진 않다.
허나 실용성은 순수성에 우선한다.
에러는 결코 조용히 지나가지 말아야한다.
명시적으로 지나가도록 한게 아니라면.
모호성 앞에서 추측하겠다는 유혹을 거부하라.
어떤 일이든 바람직하고 유일하며 명확한 방법이 있다.
비록 처음에는 그대에게 그 길이 명확해보이지 않더라도
지금 하는게 아예 안하는 것보다 낫다.
종종 아예 안하는게 지금 "당장"하는 것보다 나을 때도 있지만.
설명하기 어렵다면, 나쁜 아이디어다.
설명하기 쉽다면, 좋은 아이디어다.
Namespace는 아주 좋은 아이디어다. 막 쓰자.

Code Lay-out

Indentation

4개의 스페이스(space)를 이용한 들여쓰기를 한다.

이어지는 라인에서는 괄호 안에 들어오는 인자(argument)들을 첫번째 인자 기준으로 수직으로 배치한다. 또는 Hanging indentation를 쓸 수도 있다. Hanging indentation는 구문에서, 첫줄을 제외한 나머지 모든 줄에 indtent를 주는 방법이다. 그래서 Hanging(달려있다)인가 보다. 첫 줄에 인자가 없어야하며, indentation level을 추가하여 다음에 기술된 코드들과 인자들을 구별하기 쉽게 만들어야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Correct:

# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Add an extra level of indentation to distinguish arguments from the rest.
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# Hanging indents should add a level.
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
1
2
3
4
5
6
7
8
9
10
11
# Wrong:

# Arguments on first line forbidden when not using vertical alignment.
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# Further indentation required as indentation is not distinguishable.
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

if문의 조건 부분이 여러 줄을 할당해야 할만큼 길다면, if 두 글자와 띄어쓰기( ) 하나, 여는 괄호(()를 조합하면 자연스럽게 4-space indent를 만들 수 있다는 점을 주목하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# No extra indentation.
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# Add a comment, which will provide some distinction in editors
# supporting syntax highlighting.
if (this_is_one_thing and
    that_is_another_thing):
    # Since both conditions are true, we can frobnicate.
    do_something()

# Add some extra indentation on the conditional continuation line.
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

위의 예시에서 두번째와 세번째처럼, 주석이나 extra indentation을 통해 조건문을 다음 코드들과 시각적으로 분리한다.

listfunction, class 등이 multi-line consturct로 구성되는 경우에는 아래처럼, 닫는 괄호를 리스트의 마지막 줄의 첫번째 글자에 맞추거나 construct의 첫번째 글자에 맞춘다.

1
2
3
4
5
6
7
8
my_list = [
    1, 2, 3,
    4, 5, 6,
    ]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
    )
1
2
3
4
5
6
7
8
9
10
# 또는

my_list = [
    1, 2, 3,
    4, 5, 6,
]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
)

Tabs or Spaces

Intendation에서 언급한 대로, Tab 보다는 Space를 이용한 intendation을 이용하는 것이 선호된다. 파이썬은 tab과 space의 혼용을 금지하므로, 이미 tab으로 쓰여있는 코드에 작업을 더하는 경우가 아니라면 space를 쓰자.

최근에는 대부분의 에디터가 .py 파일의 작성에서는 유저가 키보드의 tab을 입력하더라도 미리 설정된 n개의 space로 치환해서 입력해준다. 내가 쓰는 에디터를 확인해보자.

Maximum Line Length

모든 라인은 79개의 character로 제한한다. Docstrings 이나 주석의 경우 72자로 제한한다. 제한의 이유는 에디터에서 여러개의 파일을 화면 좌우로 분할하여 열었을 때의 편의와 코드 리뷰에서 버전에 따른 코드 비교를 원활하게 하기 위함이다. 특정 팀이 코드를 내부적으로, 또는 주도적으로 유지하는 경우 글자 제한을 99자까지 상향해도 좋다. 하지만 Docstring과 주석은 여전히 72자로 제한한다.

긴 구문을 79자 제한을 지키기 위해서 분리해야할 때, 괄호 내부에서는 에서 언급한 대로 암시적으로 라인을 바꾸는 것이 좋다. 긴 구문의 경우, 이용 아래와 같이 backslash(\)를 이용한다.

1
2
3
with open('/path/to/some/file/you/want/to/read') as file_1, \
     open('/path/to/some/file/being/written', 'w') as file_2:
    file_2.write(file_1.read())

Binary Operator와 line break

결론적으로, Operator 앞에서 line break을 하는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Wrong:
# operators sit far away from their operands
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

# Correct:
# easy to match operators with operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

Blank Lines

functionclass의 정의는 두 줄의 공백으로 구분한다. class안의 method는 한 줄의 공백으로 구분한다. 연관된 function들끼리 그룹을 나누기 위해 추가적인 공백 라인을 이용할 수도 있다.

Source File Encoding

UTF-8을 쓰자.

Imports

import는 항상 소스 코드의 최상단, Module docstring의 바로 뒤에 작성하며 다음의 순서로 배치하며 1-3번 그룹 사이에는 공백 라인을 추가한다.

  1. Standard library imports
  2. Related 3rd party imports
  3. Local application / library specific imports

라이브러리는 라인을 구분하는 것이 원칙이다.

1
2
3
4
5
6
7
8
9
# Wrong:
import sys, os

# Correct:
import os
import sys

# Correct:
from subprocess import Popen, PIPE

코드 가독성과 에러 메시지에서 오류 패키지 위치를 명확하게 알려줄 수 있다는 점에서 Absolute import가 권장된다. 하지만 복잡한 구성의 패키지에서는 꼭 Absolute import를 고수할 필요 없이, Relative import를 이용하는 것도 괜찮다.

1
2
from .sibling import example
from foo.bar.myclass import myClass

Wildcard(*) imports는 namespace에 어떤 name들이 있는지 불명확하게 하므로 사용하지 않도록 한다. 내부 API를 외부에 공개하는 경우에만 제한적으로 이용하자.

1
2
# Wrong:
from <module> import *

Module Level Dunder Names

Module level에서 __all__, __author__, __version__과 같이 __으로 둘러쌓인 object를 지칭하는 dunders__future__ import 를 제외한 import문 최상단에 배치한다.

__future__ import는 docstring을 제외하고는 최상단에 위치해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
"""This is the example module.

This module does stuff.
"""

from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

import os
import sys

String Quotes

파이썬에서는 string을 표현할 때 따옴표(') 또는 쌍따옴표(")를 모두 이용할 수 있고, 둘은 똑같은 기능을 한다. 가독성을 위해 따옴표 종류 하나를 정하고 그것을 일관적으로 이용하는 것을 추천한다.

따옴표 또는 쌍따옴표로 string 표현을 정하고 나면, 백슬래스(\)의 사용을 줄이고 가독성을 높이기 위해 string 내부에는 남은 따옴표를 이용한다. e.g.) x = "abc and 'd'", y = 'efg and "h"'


Whitespace in Expressions and Statements

Pet Peeves (거슬리게 하는 것)

쓸데 없는 whitespace를 피하라. 다음의 상황에서는 공백을 없앤다.

  • 괄호 바로 안 공백

    1
    2
    3
    4
    5
    
    # Correct:
    spam(ham[1], {eggs: 2})
    
     # Wrong:
    spam( ham[ 1 ], { eggs: 2 } )
    
  • trailing comma(,)와 닫는 괄호 사이 공백
    1
    2
    3
    4
    5
    
    # Correct:
    foo = (0,)
    
    # Wrong:
    bar = (0, )
    
  • ,, ;, : 바로 전 공백
    1
    2
    3
    4
    5
    
    # Correct:
    if x == 4: print(x, y); x, y = y, x
    
    # Wrong:
    if x == 4 : print(x , y) ; x , y = y , x
    
  • slice에서 :은 binary operator와 같이 작동. : 양쪽에는 같은 개수의 공백이 있어야 함.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    # Correct:
    ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
    ham[lower:upper], ham[lower:upper:], ham[lower::step]
    ham[lower+offset : upper+offset]
    ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
    ham[lower + offset : upper + offset]
    
    # Wrong:
    ham[lower + offset:upper + offset]
    ham[1: 9], ham[1 :9], ham[1:9 :3]
    ham[lower : : upper]
    ham[ : upper]
    
  • 여는 괄호 바로 앞
    1
    2
    3
    4
    5
    6
    7
    
    # Correct:
    spam(1)
    dct['key'] = lst[index]
    
    # Wrong:
    spam (1)
    dct ['key'] = lst [index]
    
  • assignment operator 앞, 하나 이상의 공백
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # Correct:
    x = 1
    y = 2
    long_variable = 3
    
    # Wrong:
    x             = 1
    y             = 2
    long_variable = 3
    

그 외

  • Trailing whitespace는 지양하자.
  • =, +=, -=, ==, >, <, !=, <>, <=, >=, in, not in, is, is not, and, or, not과 같은 binary operator의 좌우에는 하나의 공백을 둔다.
  • 우선도가 다른 operator를 함께 사용하는 경우, low priority operator 좌우에 공백을 하나 추가해서 가독성을 높이는 것은 좋다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    # Correct:
    i = i + 1
    submitted += 1
    x = x*2 - 1
    hypot2 = x*x + y*y
    c = (a+b) * (a-b)
    
    # Wrong:
    i=i+1
    submitted +=1
    x = x * 2 - 1
    hypot2 = x * x + y * y
    c = (a + b) * (a - b)
    
  • Function annotation에서 화살표(->) 좌우에 공백을 둔다
    1
    2
    3
    4
    5
    6
    7
    
    # Correct:
    def munge(input: AnyStr): ...
    def munge() -> PosInt: ...
    
    # Wrong:
    def munge(input:AnyStr): ...
    def munge()->PosInt: ...
    
  • Default value를 표현할 때, 또는 keyword argument를 표현할 때 사용되는 = 좌우에 공백을 넣지 않는다.
    1
    2
    3
    4
    5
    6
    7
    
    # Correct:
    def complex(real, imag=0.0):
        return magic(r=real, i=imag)
      
    # Wrong:
    def complex(real, imag = 0.0):
        return magic(r = real, i = imag)
    

    하지만 annotation과 default value를 조합할 때는 = 좌우에 공백을 넣는다.

    1
    2
    3
    4
    5
    6
    7
    
    # Correct:
    def munge(sep: AnyStr = None): ...
    def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
    
    # Wrong:
    def munge(input: AnyStr=None): ...
    def munge(input: AnyStr, limit = 1000): ...
    
  • 한 라인에 여러 구문을 적는 것은 지양하자.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    # Correct:
    if foo == 'blah':
        do_blah_thing()
    do_one()
    do_two()
    do_three()
    
    # Wrong:
    if foo == 'blah': do_blah_thing()
    do_one(); do_two(); do_three()
    

When to Use Trailing Commas

Trailing Comma는 element가 하나인 tuple을 표현하기 위한 용도와 괄호 안에서 다음 줄이 연결된다는 정보를 명시적으로 알리기 위해 이용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Correct:
FILES = ('setup.cfg',)
FILES = [
    'setup.cfg',
    'tox.ini',
    ]
initialize(FILES,
           error=True,
           )

# Wrong:
FILES = 'setup.cfg',
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

Comments

코드의 실제 내용과 다른 주석은 없느니만 못하다. 항상 주석을 업데이트하는데 신경써야 한다. 주석은 완성된 문장이어야 하며, 변수를 지칭하는게 아니라면 영문 주석에서 첫 글자는 대문자로 적는다.

블록 주석은 일반적으로 완전한 문장으로 구성된 하나 이상의 단락으로 구성되며, 각 문장은 마침표와 띄어쓰기 두 번으로 끝난다.

주석은 최대한 읽기 쉽고 명확하게 써야한다. 그리고 외국인이 절대 읽지 않는다는 확신이 120% 있지 않는 한, 왠만하면 영어로 쓴다.

Block Comments

보통, 아래에 위치한 코드들을 설명하며 해당 코드와 같은 indentation으로 작성한다. 각 줄은 #와 하나의 띄어쓰기로 함께 시작하며, 단락이 나뉘는 경우 # 하나로 단락 사이를 구분한다.

Inline Comments

무분별한 inline comment 사용은 지양한다.

Inline comment는 코드 구문과 같은 줄에 위치한다. 구문 후에 최소 두 칸을 띄우고 #, 그리고 한 칸을 더 띄우고 시작한다.

코드가 명확한 경우에는 inline comment를 쓰지 않는다. 이는 오히려 주의를 산만하게 하기 때문이다. 코드의 의미를 설명할 때는 유용하게 사용 가능하다. 예를들면 아래와 같다.

1
2
3
4
5
# Wrong:
x = x + 1                 # Increment x

# Correct:
x = x + 1                 # Compensate for border

Documentation Strings

위의 Docstring 참조.


Naming Conventions

파이썬의 많은 라이브러리들은 Naming Conventions이 제각각인 경우가 많다. 하지만 아래는 Python Standard라고 생각하고 새로운 module이나 package를 만들 때에는 지키도록 노력해보자. Standard를 따르지 않는 기존 라이브러리의 개발에는 일관성을 위해 해당 라이브러리의 Convention을 고수해도 무관하다.

Overriding Principle

공개 API의 일부인 경우, 구현보다는 사용 측면의 convention을 따른다.

Descriptive Naming Styles

Descriptive : 설명, 기술적인, 서술적인 → 표현 방식

다양한 Naming Style이 있다. naming style의 용도보다도, 어떤 것들이 있는지를 파악하고 있으면 도움이 된다.

번호 예시 설명
1 b Single lowercase letter
2 B Single uppercase letter
3 lowercase 소문자만 이용
4 lower_case_with_underscores 소문자 + _로 Word단위 구분
5 UPPERCASE 대문자만 이용
6 UPPER_CASE_WITH_UNDERSCORES 대문자 + _로 Word단위 구분
7 CapitalizedWords 대문자 시작, 대문자로 Word단위 구분
8 mixedCase 소문자 시작, 대문자로 Word단위 구분
9 Capitalized_words_With_Underscores 대문자 시작, _와 대문자로 Word단위 구분

아래는 _를 이용하는 Special case에 대한 설명이다.

번호 예시 설명
1 _single_leading_underscore 약한(weak) ‘내부 사용’을 표시
eg. from M import *_로 시작하는 이름의 object를 import하지 않음.
2 single_trailing_underscore_ Python keyword와의 충돌을 피하기 위해 이용
tkinter.Toplevel(master, class_='ClassName')
3 __double_leading_underscore Name Mangling(상속 시에 attribute name 충돌을 피하기 위해 인터프리터가 변수 이름을 자동으로 변경함)
4 __double_leading_and_trailing_underscore__ magic object 또는 dunder object

Prescriptive: Naming Conventions

Prescriptive : 규범적인, 규정하는 → 지켜야하는 방식

Names to Avoid

I, l, O를 Single character로 표기하지 않는다.

ASCII Compatibility

Package and Module Names

packagemodule은 짧은 소문자의 이름을 가져야 한다. Module은 필요하다면 _도 이용 가능하지만, Package에서는 사용을 지양한다.

module은 특정 목적을 위해 object들을 모아놓은 .py 파일이다. packagemodule을 모아놓은 namespace이다.

1
2
3
4
5
Student(Package)
├─ __init__.py (Constructor)
├─ details.py (Module)
├─ marks.py (Module)
└─ collegeDetails.py (Module)

Class Names

class 이름은 일반적으로 CapitalizedWords Convention을 사용한다.

인터페이스가 문서화되고 주로 callable으로 사용되는 경우, function의 Convention을 따를 수 있다.

builtin name에 대한 Convention은 별도로 있다는 사실에 유의하자. builtin에서는 exception이나 builtin constants에서만 CapitalizedWords를 이용한다.

Type Variable Names

PEP 484에서 Type Hinting을 다루고 있다. Type variable은 짧은 이름의 CapitalizedWords를 이용한다.

covariant 또는 contravairant로 사용하는 경우, 아래와 같이 _co, _contra를 붙이는 것을 권장한다.

1
2
3
4
from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)

Exception Names

exceptionclass여야하기 때문에, class의 Convention을 따른다. Exception name 뒤에 “Error”를 붙여 exception임을 명시하자.

Global Variable Names

Convention과는 크게 관련있지 않지만, Global variable은 하나의 모듈 안에서만 이용하길 바란다. function의 convention을 따른다.

Wildcard(*)를 통해 from M import *로 쓰이도록 만든 module__all__ 메커니즘을 이용해서 _으로 시작하는 “non-public” object를 export하지 않도록 한다.

Function and Variable Names

variablefunction name은 모두 소문자로 적고, _를 이용해서 단어를 구분한다.

mixedCase(camelCase)는 기존에 이미 그렇게 쓰인 라이브러리에 적용해도 좋다.

Function and Method Arguments

instance method 앞에는 항상 self를 붙인다.

class method 앞에는 항상 cls를 붙인다.

function의 인자가 예약 keyword와 충돌한다면, _를 해당 인자의 이름 뒤에 붙여주는 것이 좋다. 예를 들어, class_clss보다 낫다.

Method Names and Instance Variables

소문자만 사용하고, _로 단어를 구분하는 function의 Convention을 따른다.

non-public method는 _로 시작한다.

subclass와 이름이 충돌하는 것을 막기 위해 __를 이름 앞에 붙여서 파이썬의 name mangling rule을 이용한다.

Mangling Rule
파이썬은 __가 이름 앞에 붙은 attribute를 발견하면 이름을 변환한다. 예를들어, class Foo__a라는 attribute를 가진다고 하자. 우리는 __a에 접근할 때, Foo.__a를 사용하는게 아니라 Foo._Foo__a를 이용해야 한다.

일반적으로, 이름 앞에 붙는 __는 subclass의 attribute와 충돌을 막을 목적으로만 사용해야한다.

Constants

상수는 보통 module level에서 정의되며 대문자로만 사용하여 _로 단어를 구분한다. ex) MAX_OVERFLOW, TOTAL

Designing for Inheritance

class의 method와 instance variable을 만들 때는 항상 public인지 non-bulic인지 정해야 한다. 만약 잘 모르겠다면, non-public을 선택하자. non-public을 public으로 만드는게 public을 non-public으로 만드는 것보다 쉽다.

public attribute는 class 내부 로직을 모르는 사람이 사용할 것으로 기대되는 attribute이고, 이전 버전과 호환되지 않는 수정 사항을 피하기 위한 방법이다. non-public attribute는 3rd-party에서 사용되지 않을 의도로 만들며, 개발자는 해당 attribute가 수정되지 않거나 없어지지 않을거라고 보장하지 않는다.

파이썬에서는 진정한 의미의 private attribute는 없으므로 “private”이라는 용어를 쓰지 않는다.

다른 언어에서 “protected”라고 불리는 subclass API를 살펴보자. 어떤 class는 상속을 통해 용도를 확장하거나 수정하도록 설계된다. 이러한 class를 디자인할 때에는 attribute의 용도에 대해 명확하고 명시적인 결정을 내려야 한다.

  • public attribute의 이름 앞에는 _를 붙이지 않는다.
  • public attribute의 이름이 파이썬 예약어와 충돌한다면, 이름의 뒤에 _를 붙인다.
  • 간단한 public data attribute라면, 복잡한 accessor/mutator를 만드는 것 보다는 그냥 attribute name을 노출하는 것이 낫다. 간단한 attribute data지만, functional한 동작이 필요하다면 property를 이용하자.
    1. attribute의 functional 동작이 side-effect를 발생시키지 않도록 주의하자
    2. 연산량이 많은 경우 property를 쓰지 말자.
  • subclass에서 사용될 목적으로 class를 만들면서, 특정 attribute는 subclass에서 이용되지 않길 바란다면, __로 시작하는 이름을 붙인다. (mangling)
    1. 만약 subclass에서도 같은 class 이름과 attribute 이름을 사용한다면, 여전히 충돌 가능성이 있음에 유의하자.
    2. name mangling은 debugging과 __getattr__()를 쓸 때 조금 불편할 수 있다.
    3. 모두가 name mangling을 좋아하지는 않을 수 있다.

Public and Internal Interfaces

모든 하위 호환성 보장은 public 인터페이스에만 적용된다. 따라서, 사용자가 public과 내부 인터페이스를 명확하게 구별할 수 있도록 하는 것은 중요하다.

Documentation에서 따로 명시하지 않는 한, 문서화된 인터페이스는 public으로 간주된다. 그리고 문서화되지 않았다면, 모두 내부 인터페이스로 본다.

더 친절하게, module 내에서 __all__을 이용하여 public API의 list를 명시할 수 있다. 만약, __all__이 빈 list라면, 해당 모듈에는 public API가 없는 것이다.

1
__all__ = ['Foo', 'Bar']

__all__으로 public API를 명시했더라도, 여전히 내부 인터페이스의 이름에는 _를 앞에 붙여야 한다.

내부적인 namespace를 이용하는 인터페이스가 있다면 이 인터페이스 또한 내부 인터페이스로 취급한다.

Programming Recommendations

파이썬에는 비슷한 동작을 하는 몇가지 방법들이 있다. 아래는 그 중, 권장되는 방법을 소개한다.

  • string을 연결할 때 a += b, a = a + b보다는 ''.join([a, b])를 이용한다. ab가 string이며, linear time에 수행됨을 명확히 할 수 있다.
  • None과 같은 singleton(유일한 객체)와의 비교에는 항상 is 또는 is not을 이용한다. ==는 쓰지 않는다.
  • if x is not None을 쓰려고 할 때, if x로 쓰는 것은 주의하자. xNone이 아닌 객체가 들어올 때, if문이 false가 될 수 있는 경우가 있다.
  • not ... is 보다는 is not을 쓴다.
    1
    2
    
    # Correct:
    if foo is not None:
    
    1
    2
    
    # Wrong:
    if not foo is None:
    
  • 다양한 비교를 위해 ordering operation을 구현할 때에는 모든 비교 연산자(__eq__, __ne__, __lt__, __le__, __gt__, __ge__)를 모두 구현해주는 것이 가장 좋다. 품을 줄이기 위해서 functools.total_ordering() decorator를 이용하는 것도 좋다. __eq__와 나머지 비교 연산자 중 하나만 작성하면 나머지는 자동으로 작성된다.
  • lambda를 identifier에 바로 bind해서 쓰지 말자.
    1
    2
    3
    4
    5
    
    # Correct:
    def f(x): return 2*x
    
    # Wrong:
    f = lambda x: 2*x
    

    전자는 tracebacks에 유리하고, 후자는 “할당”을 통해 labmda의 장점이 모두 사라진다.

  • BaseException보다는 Exception을 상속하여 예외를 구현한다. BaseException를 직접 상송하는 것은 예외가 catch되는 것이 거의 대부분 잘못된 경우에 사용한다. 예외 처리에는 “문제가 생겼다.” 보다는 “무엇이 잘못되었는가?”에 초점을 두자.
  • 예외의 연결을 정확하게 쓰자. raise X from Y를 통해 original traceback을 유지할 수 있다.
  • 예외 처리에서는 모든 exception을 잡는 것 보다는 가능하면 상세한 exception을 명시한다.
    1
    2
    3
    4
    
    try:
        import platform_specific_module
    except ImportError:
        platform_specific_module = None
    
  • try/except 구문에서 try에 들어가는 코드는 에러가 발생 가능한 최소한으로 제한한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    # Correct:
    try:
        value = collection[key]
    except KeyError:
        return key_not_found(key)
    else:
        return handle_value(value)
    
    # Wrong:
    try:
        # Too broad!
        return handle_value(collection[key])
    except KeyError:
        # Will also catch KeyError raised by handle_value()
        return key_not_found(key)
    
  • 코드의 일부에서 local resource를 써야하는 경우 with 문을 이용해서 사용한 후 clean up 하도록 한다.
  • function 또는 method가 특정 resource를 이용하는 경우에는 Context manager를 이용한다.
    1
    2
    3
    
    # Correct:
    with conn.begin_transaction():
        do_stuff_in_transaction(conn)
    
    1
    2
    3
    
    # Wrong:
    with conn:
        do_stuff_in_transaction(conn)
    

    위 예시의 후자에서는 __enter____exit__에 대한 아무런 정보를 주지 않고 사용 후에 connection을 종료해버린다. 명시적인 것이 중요하다.

  • 일관성을 지키자. return 구문에서도 일관성이 중요하다. 뭔가를 return 하는 구문이 있다면, 아무것도 return 하지 않는 구문도 명시적으로 return None을 쓰자.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    # Correct:
    
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
        else:
            return None
    
    def bar(x):
        if x < 0:
            return None
        return math.sqrt(x)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    # Wrong:
    
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
    
    def bar(x):
        if x < 0:
            return
        return math.sqrt(x)
    
  • str의 앞, 뒤를 slicing할 때는 ''.startswith() 또는 ''.endswith()를 쓰자.
    1
    2
    
    # Correct:
    if foo.startswith('bar'):
    
    1
    2
    
    # Wrong:
    if foo[:3] == 'bar':
    
  • object의 타입 비교는 항상 isinstance()를 쓴다.
    1
    2
    
    # Correct:
    if isinstance(obj, int):
    
    1
    2
    
    # Wrong:
    if type(obj) is type(1):
    
  • 빈 sequence(string, list, tuple)은 false라는 사실을 이용하자.
    1
    2
    3
    
    # Correct:
    if not seq:
    if seq:
    
    1
    2
    3
    
    # Wrong:
    if len(seq):
    if not len(seq):
    
  • boolean==으로 비교하지 말자.
    1
    2
    3
    4
    5
    6
    
    # Correct:
    if greeting:
    
    # Wrong:
    if greeting == True:
    if greeting is True:
    
  • try/fianlly 구문 안에 flow control(return, break, continue)를 넣지 말자.
    1
    2
    3
    4
    5
    6
    
    # Wrong:
    def foo():
        try:
            1 / 0
        finally:
            return 42
    

Function Annotations

  • PEP-484의 type hinting을 사용한다. type hinting은 비교적 최근에 채택되었으며, 파이썬의 Standard library도 점진적으로 수용중인 표준이다.
  • .py의 상단에 다음의 코드를 작성하면, type checker가 function annotation을 무시하도록 설정할 수 있다.
    1
    
    # type: ignore
    
  • linter와 같이 type checker는 option이며 별개의 도구다. 파이썬 인터프리터는 type checking 오류에 대한 메시지를 출력하거나, annotation에 의해 동작을 변경하지 않는다.
  • type checker의 이용은 자유이지만, 코드 완성도를 높이는데 도움이 된다.

Variable Annotations

PEP-526에서 variable annotation을 소개한다. Style은 function annotation과 비슷하다.

  • module, class, variable의 annotation은 : 후에 한 칸을 띄운다.
  • : 전에 띄우지 않는다.
  • 오른쪽에 assignment(=)가 있다면, =의 양 옆은 한 칸씩 띄운다.
    1
    2
    3
    4
    5
    6
    7
    
    # Correct:
    
    code: int
    
    class Point:
        coords: Tuple[int, int]
        label: str = '<unknown>'
    
    1
    2
    3
    4
    5
    6
    7
    
    # Wrong:
    
    code:int  # No space after colon
    code : int  # Space before colon
    
    class Test:
        result: int=0  # No spaces around equality sign