[python] 파이썬 스타일 가이드 (PEP8)을 알아보자

김동욱·2023년 11월 26일

python

목록 보기
3/6
post-thumbnail

PEP8은 파이썬 코드를 작성할 때 지켜야 하는 스타일 가이드이다. PEP는 Python Enhancement Proposal의 약자로, 파이썬 개선 제안서를 의미하고 파이썬의 개선을 위해 제안되는 다양한 아이디어와 기능, 정보 등을 공식적으로 기술한 것이다. PEP8은 파이썬 코드의 가독성을 높이고, 파이썬 커뮤니티 내에서 일관된 코드 스타일을 유지하기 위해 만들어졌다.

이러한 스타일 가이드를 따르는 것은 코드의 일관성을 유지하고 다른 개발자가 코드를 이해하기 쉽게 돕는다. PEP8을 준수하는 것은 특히 큰 프로젝트나 많은 사람들이 함께 작업할 때 중요하다.

해달 글은 PEP8의 공식 문서를 보고 정리한 것으로, 원문을 읽고 싶으면 글 하단의 링크를 참고하길 바란다.

개요

가독성이 중요한 것은 파이썬 철학의 일부이다. 코드의 일관성은 가독성과 연관되어 있다. PEP8이 작성된 배경도 이와 동일하다. 코드의 일관성을 위해 코드 스타일과 관련된 가이드 문서이다.

가능하다면 해당 가이드에 맞춰 코드를 작성하는 것이 좋지만, 적합하지 않은 상황도 존재한다. 무리하게 가이드에 맞추기 보다 상황에 맞게 적용하면 된다. 스타일 가이드를 맞추기 어려운 상황의 예는 다음과 같다.

  • 스타일 가이드를 적용했을 때 오히려 가독성이 어려워지는 경우
  • 적용했을 때 오히려 주변 코드와 일관성이 깨지는 경우
  • 스타일 가이드 지침 이전에 작성된 코드이며, 수정할 이유가 없는 경우
  • 수정 시 이전 버전의 Python과 호환되지 않는 경우

코드 레이아웃

들여 쓰기

들여쓰기는 4개의 공백을 사용한다. 연속 줄에서는 4칸 규칙이 선택 사항이다.

# 올바른 예시들

# '(' 기호에 맞춰 정렬한다.
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# 인수를 나머지 인수와 구별하려면 공백 4개를 추가한다.
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# 들여쓰기를 하면 레벨이 추가된다.
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
    
# 구문을 닫는 문자 같은 경우는 아래와 같이 표현할 수 있다.
my_list = [
    1, 2, 3,
    4, 5, 6,
    ]

my_list = [
    1, 2, 3,
    4, 5, 6,
]

탭 or 공백?

공백이 선호되는 들여쓰기 방법이다. 탭은 이미 탭으로 들여쓰기된 코드와의 일관성을 유지하기 위해서만 사용해야 한다. Python에서는 들여쓰기를 위해 탭과 공백을 혼합하는 것을 허용하지 않는다.

최대 라인 길이

모든 줄을 최대 79자로 제한한다. 구조적 제한(독스트링 또는 주석)이 적은 긴 텍스트 블록의 경우 행 길이는 72자로 제한되어야 한다. 일부 팀은 더 긴 라인 길이를 선호한다. 해당 문제는 팀 내에서 합의를 하거나 또는 코드가 주석과 독스트링이 래핑되어 있다면 줄 길이 제한을 최대 99자로 늘려도 괜찮다.

너무 긴 라인에 대해서는 백슬래시를 사용할 수도 있다.

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())

빈 줄

최상위 함수 및 클래스 정의를 두 개의 빈 줄로 구분한다. 클래스 내부의 메서드 정의는 단일 빈 줄로 구분한다.

소스 파일 인코딩

핵심 Python 배포판의 코드는 항상 UTF-8을 사용해야 하며 인코딩 선언이 있어서는 안 된다. Python 표준 라이브러리의 모든 식별자는 ASCII 전용 식별자를 사용해야 하며, 가능할 때마다 영어 단어를 사용해야 한다.

imports

import문은 일반적으로 별도의 줄에 있어야 한다.

# Correct:
import os
import sys

# Wrong:
import sys, os

# Correct:
from subprocess import Popen, PIPE

import문은 다음 순서로 그룹화되어야 한다. 그룹화된 import문은 빈 줄로 구분한다.

  • 표준 라이브러리
  • 연관된 서드 파티
  • 로컬 어플리케이션, 라이브러리

import문은 다음과 같이 선언하는 것을 지향해야 한다.

# 일반적인 경우 절대 경로로 선언한다.
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

# 결대 경로가 불필요하게 장황할 경우 상대 경로로 선언도 일부 허용된다.
from . import sibling
from .sibling import example

# *(와일드카드)를 통한 선언은 지양해야 한다.
from mypkg.sibling import *

모듈 수준 Dunder Name

all, author, version과 같은 것들을 Dunder(매직 메소드)이라고 부른다. 이것들은 모듈 주석과 import 사이에 위치하여야 한다. 단, from future는 모듈 주석 바로 다음에 위치시키도록 한다.

"""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

문자열 따옴표

Python에서는 작은 따옴표로 묶인 문자열과 큰따옴표로 묶인 문자열이 동일하다. PEP는 이에 대한 권장 사항을 제시하지 않는다. 둘 중 하나를 선택하여 해당 방식을 준수한다. 그러나 문자열에 작은따옴표 또는 큰따옴표 문자가 포함된 경우 문자열에 백슬래시가 들어가지 않도록 다른 하나를 사용해야 한다. 이를 통해 가독성을 향상 시킬 수 있다.

표현식 및 명령문의 공백

올바른 공백 사용과 옳지 않은 공백 사용에 대한 예시를 살펴보자.

# Correct:
spam(ham[1], {eggs: 2})
# Wrong:
spam( ham[ 1 ], { eggs: 2 } )

# Correct:
foo = (0,)
# Wrong:
bar = (0, )

# Correct:
if x == 4: print(x, y); x, y = y, x
# Wrong:
if x == 4 : print(x , y) ; x , y = y , x

# 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 : : step]
ham[ : upper]

# Correct:
spam(1)
# Wrong:
spam (1)

# Correct:
dct['key'] = lst[index]
# Wrong:
dct ['key'] = lst [index]

# Correct:
x = 1
y = 2
long_variable = 3
# Wrong:
x             = 1
y             = 2
long_variable = 3

더 많은 예시가 있다. 추가적인 예시 확인은 공식 문서를 살펴보자

후행 쉼표를 사용해야 하는 경우

후행 쉼표는 선택이지만, 한 요소의 튜플을 만들 때는 필수적이다.

# Correct:
FILES = ('setup.cfg',)
# Wrong:
FILES = 'setup.cfg',

항목이 시간이 지남에 따라 확장될 것으로 예상되는 경우 아래와 같이 표현한다. 단일 튜플의 경우는 위의 예시를 따른다.

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

주석

  • 코드와 주석이 일치하도록, 코드가 변경되면 항상 주석을 최신 상태로 유지한다.
  • 주석은 완전한 문장이어야 한다. 첫 번째 단어는 소문자로 시작하는 식별자가 아닌 한 대문자로 시작해야 한다.
  • 여러 문장으로 구성된 주석에서는 마지막 문장 뒤를 제외하고 문장 종료 마침표 뒤에 공백을 1~2개 사용해야 한다.
  • 주석은 가능한 영어로 작성한다.

블록 주석

블록 주석은 일반적으로 그 뒤에 오는 일부 코드(또는 전체 코드)에 적용되며, 해당 코드와 같은 수준으로 들여쓰기 된다. 블록 주석의 각 줄은 # 기호와 한 칸의 공백으로 시작한다(단, 주석 내부에 추가적으로 들여쓰기된 텍스트가 있는 경우는 예외이다).

블록 주석 내에서 문단을 구분하려면, # 기호만 있는 줄을 사용하여 문단 사이를 나눈다.

인라인 주석

인라인 주석을 자제해서 사용해야 한다. 인라인 주석은 명령문과 같은 줄에 있는 주석이다. 인라인 주석은 문에서 최소한 두 개의 공백으로 구분되어야 한다. #과 공백 하나로 시작해야 한다.

인라인 주석은 불필요하며 명백한 내용을 언급을 피해야 한다. 하지만 다음과 같이 명백한 내용이 아닌 주의 사항과 같은 경우 사용할 수도 있다.

x = x + 1                 # Compensate for border

Documentation Strings

모든 공용 모듈, 함수, 클래스 및 메소드에 대한 docstring을 작성하자. 비공개 메소드에는 docstring이 필요하지 않지만 메소드가 수행하는 작업을 설명하는 주석이 있어야 한다. 이 주석은 def 라인 다음에 나타나야 한다.

# 여러줄 일 때
"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""

# 한 줄 일 때
"""Return an ex-parrot."""

Naming Convention

피해야 할 이름

단일 문자 변수 이름으로 문자 'l'(소문자 el), 'O'(대문자 oh) 또는 'I'를 사용하지 말자.

일부 글꼴에서는 이러한 문자가 숫자 1과 0과 구별되지 않는다. 'l'을 사용하고 싶다면 대신 'L'을 사용하자.

ASCII 호환성

표준 라이브러리에 사용되는 식별자는 ASCII와 호환되어야 한다.

패키지 및 모듈 이름

모듈 이름은 모두 소문자로 이루어져야 합다. 가독성을 높이려면 모듈 이름에 언더바를 사용할 수 있다. Python 패키지에는 모두 소문자로 된 짧은 이름이 있어야 하지만 언더바 사용은 권장되지 않는다.

클래스 이름

클래스 이름은 일반적으로 CapWords(CamelCase) 규칙을 사용해야 한다.

타입 변수 이름

타입 변수 이름은 일반적으로 CapWords를 사용하며, 짧은 이름을 선호한다. 예를 들어 T, AnyStr, Num과 같은 이름들이 사용된다. 공변(covariant) 또는 반공변(contravariant) 동작을 선언하는 데 사용되는 변수에는 각각 _co 또는 _contra 접미사를 추가하는 것이 권장된다.

예외 이름

예외는 클래스여야 하므로 여기에는 클래스 명명 규칙이 적용된다. 예외 이름에 접미사 "Error"를 사용해야 한다.

전역 변수 이름

전역변수 이름은 함수 명명 규칙을 따른다.

함수 및 변수 이름

함수 이름은 소문자여야 하며, 가독성을 높이기 위해 필요에 따라 단어를 밑줄로 구분해야 한다. 변수 이름은 함수 이름과 동일한 규칙을 따른다.

함수 및 메서드 인수

항상 인스턴스 메소드에 대한 첫 번째 인수로 self 사용하자. 항상 클래스 메소드의 첫 번째 인수로 cls 사용하자.

메소드 이름 및 인스턴스 변수

함수 명명 규칙을 사용하자. 가독성을 높이기 위해 필요에 따라 단어를 밑줄로 구분하고 소문자를 사용한다.
비공개 메서드와 인스턴스 변수에 대해서만 앞에 밑줄을 하나만 사용한다.
하위 클래스와의 이름 충돌을 피하려면 앞에 두 개의 밑줄을 사용하여 Python의 이름 맹글링 규칙을 사용한다.

상수

상수는 일반적으로 모듈 수준에서 정의되며 단어를 구분하는 밑줄과 함께 모두 대문자로 작성된다. 예를 들면 MAX_OVERFLOWTOTAL가 있다.

상속을 위한 디자인

클래스의 메서드와 인스턴스 변수(통칭하여 "속성")가 public인지 private인지 항상 결정한다. 확실하지 않은 경우 private를 선택한다. public 속성을 private로 설정하는 것보다 나중에 public로 설정하는 것이 더 쉽다.

  • public 속성에는 선행 밑줄이 없어야 한다.
  • public 속성 이름이 예약된 키워드와 충돌하는 경우 속성 이름 뒤에 언더바를 추가한다.
  • 단순한 public 데이터 속성의 경우 복잡한 접근자/변경자 메서드 없이 속성 이름만 노출하는 것이 가장 좋다.
  • 클래스를 하위 클래스로 분류하고 하위 클래스에서 사용하지 않으려는 속성이 있는 경우 앞에 밑줄을 두 개 사용하고 뒤에 밑줄을 사용하지 않고 이름을 지정하는 것을 고려한다.

프로그래밍 권장사항

  • 코드는 Python의 다른 구현체에서도 동작하게 작성한다.
  • None과 같은 싱글톤과의 비교는 항상 is 또는 is not을 사용하여 수행해야 하며, 동등 연산자를 사용해서는 안 된다.
  • 다양한 비교 기능을 갖춘 정렬 작업을 구현할 때 특정 비교만 실행하기 위해 다른 코드에 의존하기보다는 6개 작업(__eq__, __ne__, __lt__, __le__, __gt__, __ge__)을 모두 구현하는 것이 가장 좋다.
  • 람다 식을 식별자에 직접 바인딩하는 할당문 대신 항상 def문을 사용한다.
    (Correct: def f(x): return 2*x, Wrong: f = lambda x: 2*x)
  • BaseException이 아닌 Exception에서 예외를 파생시킨다.
  • 예외 체인을 적절하게 사용한다.
  • 예외를 포착할 때 except: 가 아닌 가능한 특정 예외를 언급한다.
  • 모든 try/exc 절에 대해 try 절을 필요한 최소 코드 양으로 제한한다.
  • 리소스가 특정 코드 섹션에 로컬인 경우 with 문을 사용하여 사용 후 신속하고 안정적으로 정리되도록 한다. try/finally 문도 허용된다.
  • 컨텍스트 관리자는 리소스 획득 및 해제 이외의 작업을 수행할 때마다 별도의 함수나 메서드를 통해 호출되어야 한다.
  • 반환문에 일관성을 유지한다.
  • 문자열 슬라이싱 대신 ''.startswith()''.endswith()를 사용하여 접두사 또는 접미사를 확인한다. startwith()endwith()는 더 깔끔하고 오류 발생 가능성이 적다.
  • 객체 유형 비교에서는 유형을 직접 비교하는 대신 항상 isinstance()를 사용해야 한다.
  • 시퀀스(문자열, 목록, 튜플)의 경우 빈 시퀀스가 False이라는 사실을 사용한다.
  • bool값을 True 또는 False와 비교하지 않는다.

변수 주석

  • 모듈 수준 변수, 클래스 및 인스턴스 변수, 지역 변수에 대한 주석은 콜론 뒤에 단일 공백이 있어야 한다.
  • 콜론 앞에는 공백이 없어야 한다.
  • 할당에 오른쪽이 있는 경우 등호 기호 양쪽에 정확히 하나의 공백이 있어야 한다.
# Correct:

code: int

class Point:
    coords: Tuple[int, int]
    label: str = '<unknown>'
    
# Wrong:

code:int  # No space after colon
code : int  # Space before colon

class Test:
    result: int=0  # No spaces around equality sign

참고 자료

https://peps.python.org/pep-0008/#ascii-compatibility

profile
안녕하세요! 질문과 피드백은 언제든지 환영입니다:)

0개의 댓글