PEP8 - Style Guide for Python Code

Python 코드의 스타일 가이드로, 가독성과 일관성을 높이기 위한 다양한 규칙과 권장사항을 제시한다.
중요한 점은

  1. 이 가이드라인들은 제안사항이며, 프로젝트의 일관성이 더 중요하다.
  2. 기존 코드의 스타일을 존중해야 한다.
  3. 일부 규칙은 특수한 상황에서 무시될 수 있다.
  4. 가독성이 최우선.
    프로젝트별로 자체적인 스타일 가이드가 있을 경우 해당 가이드를 우선시

코드 레이아웃 (Code Layout)

  • 들여쓰기 (Indentation)
    • 4개의 스페이스를 사용.
    • 연속된 줄은 괄호 안에서 수직으로 정렬하거나 hanging indent를 사용한다.
    • 탭은 사용하지 않음.
    • 탭과 공백은 혼용하지 않는다.
  • 라인 길이
    • 최대 79자
    • 문서화 문자열(docstring)과 주석은 72자
    • 긴 줄은 여러 줄로 나누어 작성.
    • 줄 연결은 괄호나 백슬래시를 사용한다.
  • 줄바꿈
    • 연산자 앞에서 줄을 바꾸는 것이 더 가독성이 좋다.

올바른 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 괄호 안에서 수직 정렬
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Hanging indent
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    # 함수 내용은 4칸 들여쓰기
    print(parameter_1)
    
    # if문도 4칸 들여쓰기
    if True:
        # if문 내부는 추가로 4칸 들여쓰기
        print("Nested content")
        
    # 여러 줄의 리스트
    my_list = [
        1, 2,
        3, 4,
        5, 6
    ]

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

# 1. 괄호를 사용한 줄 나누기
long_string = (
    "이것은 매우 긴 문자열이라서 "
    "여러 줄로 나누어 작성했습니다."
)

# 2. 연산자 앞에서 줄 바꾸기
total = (
    first_variable
    + second_variable
    - third_variable
)
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

# 3. 함수 인자 나누기
def long_function_name(
        var_one, var_two,
        var_three, var_four):
    print(var_one)

잘못된 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 인자가 첫 줄에 있으면 안 됨
foo = long_function_name(var_one, var_two,
    var_three, var_four)


def long_function_name(
  parameter_1,    # 2칸만 들여씀
    parameter_2,  
      parameter_3 # 불필요하게 많이 들여씀
):
	print(parameter_1)  # 탭 사용
    if True:
       print("Wrong indent")  # 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())

# 1. 한 줄이 너무 김
long_string = "이것은 매우 긴 문자열이라서 한 줄에 전부 작성하면 79자를 훨씬 넘어가게 되어 가독성이 떨어지게 됩니다."

# 2. 잘못된 줄 나누기
total = first_variable + \
        second_variable + \
        third_variable
        
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

# 3. 잘못된 함수 인자 나누기
def long_function_name(var_one, var_two
    , var_three, var_four):  # 쉼표가 잘못된 위치에 있음
    print(var_one)

임포트 (Import)

  • 임포트는 항상 파일의 맨 위에 작성한다.
  • 각 임포트는 별도의 줄에 작성한다.
  • 임포트는 다음 순서로 그룹화한다.
    1. 표준 라이브러리
    2. 관련된 서드파티 라이브러리
    3. 로컬 애플리케이션 / 라이브러리

올바른 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. 표준 라이브러리
import os
import sys
from datetime import datetime, timedelta

# 2. 서드파티 라이브러리
import numpy as np
import pandas as pd

# 3. 로컬 애플리케이션
from myproject.models import User
from myproject.utils import helper
from . import localmodule

잘못된 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. 한 줄에 여러 임포트
import sys, os, datetime

# 2. 잘못된 순서
from myproject.models import User
import os
import pandas as pd

# 3. 와일드카드 임포트
from mymodule import *  # 이것은 피해야 함

# 4. 불필요한 임포트
from mymodule import MyClass, MyClass  # 중복

표현식과 문장의 공백

  • 적절한 공백 사용은 코드의 가독성을 크게 향상시킨다.
  • 일관된 공백 사용이 중요하다.
  • 괄호, 대괄호, 중괄호 안쪽에 불필요한 공백을 넣지 않는다.
  • 쉼표, 세미콜론, 콜론 앞에는 공백을 넣지 않는다.

올바른 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 할당 연산자
x = 1
y = 2

# 2. 연산자
result = x + y * (z - 1)

# 3. 쉼표 후 공백
items = [1, 2, 3, 4, 5]
def func(x, y, z):
    pass
if x == 4: print(x, y); x, y = y, x

# 1. 괄호
spam(ham[1], {eggs: 2})

# 2. 딕셔너리
dict = {'key': 'value'}

# 3. 리스트/튜플
list = [1, 2, 3]
tuple = (1, 2, 3)

잘못된 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 불일치하는 공백
x=1
y= 2
z =3

# 2. 연산자 주변 공백 누락
result=x+y*(z-1)

# 3. 쉼표 후 공백 누락
items = [1,2,3,4,5]
def func(x,y,z):
    pass
if x == 4 : print(x , y) ; x , y = y , x

# 1. 불필요한 공백
spam( ham[ 1 ], { eggs: 2 } )

# 2. 불일치하는 공백
dict = { 'key':'value' }
dict = {'key' :'value'}

# 3. 리스트/튜플의 불필요한 공백
list = [ 1,2,3 ]
tuple = ( 1,2,3 )

명명 규칙(Naming Conventions)

  • 일관된 이름 규칙은 코드의 가독성을 높인다.
  • 의미 있고 설명적인 이름을 사용해야 한다.
타입규칙예시설명
패키지/모듈짧은 소문자
필요시 언더스코어
utils
email_validator
data_parser
모듈은 import 시 파일명이 되므로 짧고 간단하게 작성
클래스CapWords(Pascal Case)UserProfile
EmailValidator
DatabaseConnection
각 단어의 첫 글자를 대문자로 작성
함수/메서드소문자 + 언더스코어
(snake_case)
calculate_total()
get_user_info()
validate_email()
기능을 명확히 설명하는 동사로 시작
변수소문자 + 언더스코어
(snake_case)
user_name
total_count
items_list
데이터의 내용을 명확히 설명
상수대문자 + 언더스코어MAX_VALUE
DEFAULT_TIMEOUT
PI
변경되지 않는 값임을 명확히 표시
보호 속성앞에 언더스코어 1개_internal_name
_protected_method()
직접 접근을 권장하지 않는 내부 사용 속성
비공개 속성앞에 언더스코어 2개__private_name
__private_method()
클래스 외부에서 접근을 제한하는 속성
특별 메서드앞뒤 더블 언더스코어__init__
__str__
__len__
Python에서 특별한 의미를 가진 메서드
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 1. 패키지/모듈 예시
import email_validator
from data_processing import utils

# 2. 클래스 예시
class UserProfile:
    def __init__(self, name):
        self.name = name

class EmailValidator:
    def validate(self, email):
        pass

# 3. 함수/메서드 예시
def calculate_total(items):
    return sum(items)

def get_user_info(user_id):
    pass

# 4. 변수 예시
first_name = "John"
total_count = 0
items_list = []

# 5. 상수 예시
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30
PI = 3.14159

# 6. 클래스에서의 보호/비공개 속성 예시
class Customer:
    def __init__(self):
        self._internal_id = 123       # 보호 속성
        self.__private_data = "secret"  # 비공개 속성
    
    def _protected_method(self):      # 보호 메서드
        pass
    
    def __private_method(self):       # 비공개 메서드
        pass
    
    def __str__(self):               # 특별 메서드
        return f"Customer {self._internal_id}"

추가적인 명명 규칙 지침:

  1. 단일 문자 변수명으로 ’l’, ‘O’, ‘I’는 사용하지 않는다.
    이는 숫자 1과 0과 혼동될 수 있기 때문이다.
  2. 가능한 짧고 간결한 이름을 사용하되, 필요한 경우 가독성을 위해 언더스코어를 사용한다.
  3. 함수나 변수 이름에 타입 정보를 포함시키지 않는다.
    (예: phone_number는 좋지만 phone_number_str은 피한다).
  4. 패키지와 모듈 이름은 짧고 모두 소문자여야 하며, 언더스코어 사용을 최소화한다.

함수 및 메서드 인자(Function and Method Arguments)

  1. 인스턴스 메서드의 첫 번째 인자는 항상 ‘self’를 사용해야 한다.
  2. 클래스 메서드의 첫 번째 인자는 항상 ‘cls’를 사용해야 한다.
  3. 함수 인자의 이름이 예약어와 충돌하는 경우, 일반적으로 약어나 철자 변형보다는 단일 밑줄을 뒤에 붙이는 것이 좋다.

올바른 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 1. 클래스 이름 (CapWords 규칙)
class CustomerProfile:
    pass

class EmailValidator:
    pass

# 2. 함수와 변수 이름 (소문자와 언더스코어)
def calculate_total():
    pass

user_name = "John"
first_name = "Mike"

# 3. 상수 (대문자와 언더스코어)
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30

# 4. 함수 및 메서드 인자
class ExampleClass:
    def instance_method(self, arg1, arg2):
        pass

    @classmethod
    def class_method(cls, arg1, arg2):
        pass

def function(arg1, class_):
    pass

잘못된 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 1. 잘못된 클래스 이름
class customer_profile:  # 언더스코어 사용
    pass

class emailValidator:  # 소문자로 시작
    pass

# 2. 잘못된 함수와 변수 이름
def CalculateTotal():  # 대문자 사용
    pass

userName = "John"  # 카멜케이스 사용
FirstName = "Mike"  # 대문자로 시작

# 3. 잘못된 상수 이름
maxConnections = 100  # 카멜케이스 사용
Default_Timeout = 30  # 혼합된 대소문자

# 4. 함수 및 메서드 인자
class ExampleClass:
    def instance_method(this, arg1, arg2):  # 'self' 대신 'this' 사용
        pass

    @classmethod
    def class_method(self, arg1, arg2):  # 'cls' 대신 'self' 사용
        pass

def function(arg1, clss):  # 'class_' 대신 'clss' 사용
    pass

상속을 위한 디자인(Designing for Inheritance)

상속을 위한 디자인 기본 원칙
클래스의 메서드와 인스턴스 변수 결정
  • 클래스 설계 시 메서드와 인스턴스 변수가 public인지 non-public인지 명확히 해야 한다.
  • 불확실할 경우 non-public으로 만드는 것이 좋다.
  • public 속성은 나중에 쉽게 non-public으로 변경할 수 없기 때문이다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class User:
    def __init__(self, name):
        self.name = name          # public
        self._email = None        # non-public
        self.__password = None    # private

    def get_info(self):          # public 메서드
        return f"Name: {self.name}"

    def _validate_email(self):    # non-public 메서드
        return '@' in self._email

속성의 분류

  • 공개(Public) 속성: 관련 없는 클라이언트가 사용할 것으로 예상되는 속성으로 하위 호환성을 유지해야 한다.
  • 비공개(Non-public) 속성: 제3자가 사용하지 않도록 의도된 속성으로 변경되거나 제거될 수 있다.
  • 서브클래스 API: 상속을 통해 확장 또는 수정되도록 설계된 클래스에서 중요한 속성.
Public 속성 명명 규칙

public 속성은 다음과 충돌하지 않는 이름을 사용해야 한다.

  • 예약어
  • 내장 함수/타입
  • 인스턴스 메서드
  • 믹스인 클래스의 속성
1
2
3
4
5
6
7
8
class Example:
    # 잘못된 예시
    list = []        # 내장 타입과 충돌
    str = "text"     # 내장 함수와 충돌
    
    # 올바른 예시
    items_list = []
    text_str = "text"
Private 변수와 메서드 사용
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Account:
    def __init__(self):
        self.__balance = 0    # private 변수
        self._transactions = []  # protected 변수

    def __update_balance(self):  # private 메서드
        self.__balance = sum(self._transactions)

    def add_transaction(self, amount):
        self._transactions.append(amount)
        self.__update_balance()
상속 관련 특별 고려사항
메서드 오버라이딩
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Parent:
    def __init__(self):
        self._protected_var = 10
        
    def method(self):
        self._internal_method()
    
    def _internal_method(self):
        pass

class Child(Parent):
    def _internal_method(self):  # protected 메서드 오버라이드
        super()._internal_method()
        print("Additional functionality")
특별 메서드 (매직 메서드)
1
2
3
4
5
6
7
8
9
class CustomList:
    def __init__(self, items):
        self._items = items

    def __len__(self):  # 항상 public
        return len(self._items)

    def __getitem__(self, index):  # 항상 public
        return self._items[index]
주요 가이드라인 정리
Public 속성 규칙
1
2
3
4
5
6
7
8
9
class DataProcessor:
    def get_data(self):           # public 메서드
        return self._process_data()
    
    def _process_data(self):      # protected 메서드
        return self.__raw_data()  # private 메서드 호출
    
    def __raw_data(self):         # private 메서드
        return "data"
상속을 고려한 설계
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Base:
    def __init__(self):
        self._common = 1          # protected: 자식 클래스가 접근 가능
        self.__private = 2        # private: 자식 클래스가 접근 불가

    def public_method(self):      # public: 누구나 접근 가능
        return self._protected_method()

    def _protected_method(self):  # protected: 자식 클래스에서 오버라이드 가능
        return "Base protected method"

    def __private_method(self):   # private: 자식 클래스에서 접근 불가
        return "Base private method"

class Derived(Base):
    def _protected_method(self):
        parent_result = super()._protected_method()
        return f"Derived: {parent_result}"
실제 적용 예시
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class BankAccount:
    def __init__(self, initial_balance=0):
        self._balance = initial_balance    # protected
        self.__transactions = []           # private
        
    def deposit(self, amount):            # public
        if self.__validate_amount(amount):
            self.__transactions.append(amount)
            self._update_balance()
            return True
        return False
    
    def _update_balance(self):            # protected
        self._balance = sum(self.__transactions)
    
    def __validate_amount(self, amount):  # private
        return amount > 0

class SavingsAccount(BankAccount):
    def __init__(self, initial_balance=0, interest_rate=0.01):
        super().__init__(initial_balance)
        self._interest_rate = interest_rate
    
    def _update_balance(self):            # protected 메서드 오버라이드
        super()._update_balance()
        # 이자 계산 추가
        interest = self._balance * self._interest_rate
        self._balance += interest
주의사항
이름 맹글링 (Name Mangling)
1
2
3
4
5
6
7
class Example:
    def __init__(self):
        self.__private = "private"  # 실제로는 _Example__private로 변환됨

# 접근 방법 (권장하지 않음)
obj = Example()
print(obj._Example__private)  # private 변수에 접근 가능
상속 시 주의사항
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Parent:
    def __init__(self):
        self._protected = "protected"
        self.__private = "private"

class Child(Parent):
    def __init__(self):
        super().__init__()
        # self._protected 접근 가능
        # self.__private 접근 불가능

공개 및 내부 인터페이스 (Public and Internal Interfaces)

공개 및 내부 인터페이스의 기본 원칙
  1. 모든 public 인터페이스는 문서화되어야 한다.
  2. 모든 non-pulic 인터페이스는 _ 접두사로 시작해야 한다.
  3. 내부적으로만 사용되는 인터페이스는 _ 접두사로 시작해야 한다.
  4. 한 모듈 내에서만 사용되는 인터페이스는 _ 접두사로 시작해야 한다.
인터페이스 구분
Public 인터페이스
  • 외부에서 사용되도록 의도된 인터페이스
  • 하위 호환성이 보장되어야 함
  • 문서화가 필수적
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class User:
    def __init__(self, name):
        self.name = name  # public 속성
    
    def get_info(self):  # public 메서드
        """사용자 정보를 반환합니다.
        
        Returns:
            str: 사용자의 이름 정보
        """
        return f"Name: {self.name}"
Internal 인터페이스
  • 패키지/모듈 내부에서만 사용되는 인터페이스
  • _ 접두사로 시작
  • 외부 사용자에 의존성을 가지지 않음
1
2
3
4
5
6
class _DatabaseConnection:
    def __init__(self):
        self._connection = None
    
    def _connect(self):  # internal 메서드
        self._connection = "DB Connection"
실제 구현 예시
모듈 레벨 구현
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# mymodule.py

# Public 인터페이스
def calculate_total(numbers):
    """숫자 리스트의 합계를 계산합니다."""
    return _validate_and_sum(numbers)

# Internal 인터페이스
def _validate_and_sum(numbers):
    """내부용 검증 및 합계 계산 함수"""
    if not all(isinstance(n, (int, float)) for n in numbers):
        raise ValueError("모든 요소는 숫자여야 합니다")
    return sum(numbers)

# Private 구현
def __private_helper():
    """모듈 내부에서만 사용되는 헬퍼 함수"""
    pass
클래스 레벨 구현
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DataProcessor:
    """데이터 처리를 위한 클래스입니다."""

    def __init__(self):
        self.public_data = []     # public 속성
        self._internal_data = []  # internal 속성
        self.__private_data = []  # private 속성

    def process_data(self, data):
        """데이터를 처리합니다. (Public 인터페이스)"""
        self._validate_data(data)
        self._process_internal(data)
        return self.get_result()

    def _validate_data(self, data):
        """데이터를 검증합니다. (Internal 인터페이스)"""
        if not isinstance(data, list):
            raise ValueError("데이터는 리스트 형식이어야 합니다")

    def __process_private(self, item):
        """private 처리 로직 (Private 구현)"""
        return item * 2
인터페이스 사용 가이드라인
문서화
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class APIClient:
    """외부 API와 통신하기 위한 클라이언트 클래스.
    
    이 클래스는 외부 API와의 모든 통신을 처리합니다.
    
    Attributes:
        base_url (str): API의 기본 URL
        timeout (int): 요청 타임아웃 시간(초)
    """

    def __init__(self, base_url, timeout=30):
        self.base_url = base_url
        self.timeout = timeout
        self._session = None  # internal 속성

    def get_data(self, endpoint):
        """API에서 데이터를 조회합니다.
        
        Args:
            endpoint (str): API 엔드포인트
            
        Returns:
            dict: API 응답 데이터
            
        Raises:
            ConnectionError: API 연결 실패 시
        """
        self._ensure_session()
        return self._make_request(endpoint)
패키지 레벨 구현
1
2
3
4
5
# __init__.py
from .public_interface import PublicClass
from ._internal_module import _InternalClass

__all__ = ['PublicClass']  # 공개 인터페이스만 노출
실제 사용 사례
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# database_handler.py
class DatabaseHandler:
    """데이터베이스 처리를 위한 핸들러 클래스."""

    def __init__(self, connection_string):
        self.connection_string = connection_string
        self._connection = None
        self.__transaction_count = 0

    # Public 인터페이스
    def connect(self):
        """데이터베이스 연결을 수립합니다."""
        self._create_connection()
        return self.is_connected()

    def execute_query(self, query):
        """SQL 쿼리를 실행합니다.
        
        Args:
            query (str): 실행할 SQL 쿼리
            
        Returns:
            list: 쿼리 결과
        """
        if not self.is_connected():
            self.connect()
        return self._execute(query)

    # Internal 인터페이스
    def _create_connection(self):
        """내부 연결 생성 로직"""
        self._connection = f"Connected to {self.connection_string}"
        self.__update_transaction_count()

    def _execute(self, query):
        """내부 쿼리 실행 로직"""
        return f"Executing: {query}"

    # Private 구현
    def __update_transaction_count(self):
        """트랜잭션 카운터 업데이트"""
        self.__transaction_count += 1
주의사항 및 모범 사례
명확한 인터페이스 구분
1
2
3
4
5
6
7
8
9
class Config:
    def get_setting(self, key):      # public
        return self._load_setting(key)
    
    def _load_setting(self, key):    # internal
        return self.__read_file(key)
    
    def __read_file(self, key):      # private
        pass
문서화 규칙
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Logger:
    """로깅을 처리하는 클래스.
    
    이 클래스는 애플리케이션의 로깅을 담당합니다.
    
    Attributes:
        log_level (str): 현재 로그 레벨
        output_file (str): 로그 출력 파일 경로
    """

    def log(self, message, level="INFO"):
        """메시지를 로깅합니다.
        
        Args:
            message (str): 로깅할 메시지
            level (str, optional): 로그 레벨. 기본값은 "INFO"
        """
        self._write_log(f"[{level}] {message}")

Naming Style

  • b (single lowercase letter)
    일시적이거나 카운터 변수에 주로 사용

    1
    2
    3
    4
    5
    
      for i in range(10):
      	pass
    
      def f(x):
      	return x + 1
    
  • B (single uppercase letter)
    클래스의 간단한 예제나 특별한 의미를 가진 상수

    1
    2
    3
    4
    
    class C:
    	pass
    
    N = 100  # 수학적 상수로 사용될 때
    
  • lowercase
    간단한 함수나 변수
    한 단어로만 구성

    1
    2
    3
    
    name = "John"
    def run():
        pass
    
  • lower_case_with_underscores (Snake Case)
    함수, 변수, 모듈 이름
    Python에서 가장 선호되는 일반적인 네이밍 스타일

    1
    2
    3
    4
    
    first_name = "John"
    def calculate_total_price():
        pass
    import my_module
    
  • UPPERCASE
    상수
    단일 단어 상수에 사용

    1
    2
    
    MAX = 100
    PI = 3.14
    
  • UPPER_CASE_WITH_UNDERSCORES
    모듈 레벨 상수
    여러 단어로 구성된 상수에 사용

    1
    2
    
    MAX_CONNECTIONS = 1000
    DEFAULT_CONFIG_PATH = "/etc/app/config"
    
  • CapitalizedWords(CapWords, or CamelCas)
    클래스 이름
    각 단어의 첫 글자를 대문자로
    약어는 모두 대문자로

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    class CustomerService:
        pass
    
    class HTTPClient:    # 약어 HTTP 모두 대문자
        pass
    
    class XMLParser:     # 약어 XML 모두 대문자
        pass
    
    class SQLDatabase:   # 약어 SQL 모두 대문자
        pass
    
  • mixedCase (differs from CapitalizedWords by initial lowercase character!)
    변수나 함수 (Java 스타일)
    첫 단어만 소문자로 시작, 나머지 단어는 대문자로 시작

    1
    2
    3
    4
    
    # 권장되지 않는 스타일
    firstName = "John"
    def computeTotal():
        pass
    
  • Capitalized_Words_With_Underscores
    가독성이 떨어져서 권장되지 않음

    1
    2
    3
    
      # 권장되지 않는 스타일
      class My_Custom_Class:
      	pass
    

주석 (Comments)

  • 주석은 코드의 논리를 설명하는데 사용된다.
  • 명확하고 완전한 문장으로 작성한다.
  • 코드의 동작이 아닌 의도를 설명한다.
  • 인라인 주석은 문장과 최소 두 칸 띄운다.

올바른 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 블록 주석
# 이 함수는 사용자의 나이를 계산합니다.
# birth_year를 입력받아 현재 나이를 반환합니다.
def calculate_age(birth_year):
    current_year = 2024
    return current_year - birth_year

# 2. 인라인 주석 (최소 2개의 공백 후)
x = x + 1  # 카운터 증가

# 3. docstring
def complex_function(arg1, arg2):
    """이 함수는 두 인자를 처리합니다.
    
    Args:
        arg1 (int): 첫 번째 매개변수
        arg2 (str): 두 번째 매개변수
    
    Returns:
        bool: 처리 결과
    """
    return True

잘못된 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1. 불충분한 설명
def calculate_age(birth_year):
    #나이계산
    current_year = 2024
    return current_year - birth_year

# 2. 잘못된 인라인 주석
x = x + 1# 공백 없는 주석
y = y + 1 #공백이 하나뿐인 주석

# 3. 잘못된 docstring 형식
def complex_function(arg1, arg2):
    '''한 줄로 된 docstring - 큰따옴표를 사용해야 함'''
    return True

문자열 (Strings)

  • 작은따옴표(’)와 큰따옴표(")는 동등하게 취급된다.
    하나를 선택해 일관성 있게 사용.
  • 문자열 안에 작은따옴표나 큰따옴표가 포함되어 있다면, 백슬래시를 사용한 이스케이프를 피하기 위해 다른 종류의 따옴표를 사용한다.
  • 여러 줄 문자열의 경우, 항상 큰따옴표 세 개(""")를 사용한다.
    이는 docstring 규칙과 일치.

올바른 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 일관성 있는 따옴표 사용
print('Hello, world')
print('I\'m using Python')

# 백슬래시 이스케이프 피하기
print("It's a beautiful day")
print('He said, "Python is awesome!"')

# 여러 줄 문자열
long_string = """
This is a long string
that spans multiple lines.
"""

잘못된 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 일관성 없는 따옴표 사용
print('Hello, world")
print("I'm using Python')

# 불필요한 백슬래시 사용
print('It\'s a beautiful day')
print("He said, \"Python is awesome!\"")

# 여러 줄 문자열에 작은따옴표 사용
long_string = '''
This is a long string
that spans multiple lines.
'''

프로그래밍 권장사항 (Programming Recommendations)

  • ‘is’ 또는 ‘is not’을 사용하여 None과 비교한다.
  • 부울(bool) 값을 직접 비교하지 않는다.
  • 예외처리는 가능한 한 구체적인 예외를 명시한다.
  • 문자열 접두사/접미사 확인은 startswith()와 endswith() 메서드를 사용한다.
  • isinstance()를 사용하여 객체 타입을 확인한다.
  • 시퀀스의 빈 값 확인은 시퀀스의 부울(bool) 컨텍스트를 직접 사용한다.
  • 간단한 함수도 가능한 def를 사용하여 정의한다.

올바른 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1. None과의 비교:
if x is None:
    # do something
    
# 2. 부울 값 비교:
if greeting:
    # do something

# 3. 예외 처리:
try:
    value = dictionary[key]
except KeyError:
    return None
# 4. 문자열 접두사/접미사 확인:
if foo.startswith('bar'):
    # do something

# 5. 객체 타입 비교:
if isinstance(obj, int):
    # do something

# 6. 시퀀스의 빈 값 확인:
if not seq:
    # do something
if seq:
    # do something
if items:  # 빈 리스트 검사
    pass
# 7. lambda 표현식 대신 def 사용
def f(x): return 2*x

잘못된 예

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 1. None과의 비교
if x == None:
    # do something

# 2. 부울 값 비교:
if greeting == True:
    # do something

# 3. 예외 처리:
try:
    value = dictionary[key]
except:  # 너무 광범위한 예외 처리
    return None

# 4. 문자열 접두사/접미사 확인:
if foo[:3] == 'bar':
    # do something

# 5. 객체 타입 비교:
if type(obj) is type(1):
    # do something
    
# 6. 시퀀스의 빈 값 확인:
if len(seq) == 0:
    # do something
if len(seq) > 0:
    # do something
if len(items) > 0:  # 불필요한 길이 검사
    pass
        
# 7. lambda 표현식 대신 def 사용
f = lambda x: 2*x

PEP 8 가이드라인을 적용한 예시 코드

  • 코드 레이아웃
    • 들여쓰기 (4칸)
    • 최대 줄 길이 (79자)
    • 임포트 순서와 그룹화
    • 공백 사용
  • 명명 규칙
    • 패키지/모듈 (lowercase)
    • 클래스 (CapWords)
    • 함수/변수 (lowercase_with_underscores)
    • 상수 (UPPER_CASE)
    • Protected (_single_leading_underscore)
    • Private (__double_leading_underscore)
  • 프로그래밍 권장사항
    • 함수 어노테이션
    • 적절한 예외 처리
    • return 문 사용
    • 문자열 포매팅
    • 비교 연산자 사용
  • 문서화
    • 모듈 docstring
    • 함수/클래스 docstring
    • 인라인 주석
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/usr/bin/env python3
"""Example module showing PEP 8 style guidelines.

This docstring follows PEP 257 guidelines:
- Starts with a one-line summary
- Followed by a blank line
- More detailed description
"""

# ==========
# Imports are grouped in the following order:
# 1. Standard library imports
# 2. Related third party imports
# 3. Local application/library specific imports
# ==========
import os  # Standard library imports are listed first
import sys
from datetime import datetime
from typing import Dict, List, Optional, Union  # Multiple imports from same module

import numpy as np  # Third party imports after a blank line
import requests
from fastapi import FastAPI  # Imports are in alphabetical order

from my_module import my_function  # Local imports after another blank line


# ==========
# Constants
# - Use UPPER_CASE
# - Define at module level
# ==========
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30


# ==========
# Classes
# - Use CapWords convention
# - Two blank lines before class definition
# ==========
class DatabaseError(Exception):
    """Custom exception class following PEP 8.

    - Class docstring indented at same level as class code
    - Inherits from appropriate base class (Exception)
    """
    pass


class UserProfile:
    """Class demonstrating PEP 8 class formatting guidelines."""

    # Class attributes are defined at the top
    default_settings = {
        'timeout': DEFAULT_TIMEOUT,  # Dictionary formatted with space after :
        'retry_count': 3,
        'max_attempts': 5
    }

    def __init__(
        self,                     # Long parameter lists can be indented
        username: str,            # Type hints used for parameters
        email: str,
        *,                        # Enforce keyword-only arguments
        is_active: bool = True    # Default values after type hints
    ) -> None:                    # Return type hint
        """Initialize UserProfile.

        Args:
            username: The user's username
            email: The user's email
            is_active: User's active status
        """
        # Validate parameters before assignment
        if not username or not email:
            raise ValueError(
                "Username and email must not be empty"  # Line breaking before operators
            )

        # Protected attributes start with single underscore
        self._username = username
        self._email = email
        
        # Private attributes start with double underscore
        self.__password_hash = None
        
        # Public attributes without underscore
        self.is_active = is_active
        self.created_at = datetime.now()

    def get_profile_data(self) -> Dict[str, Union[str, datetime]]:
        """Get user profile data.

        Returns:
            Dictionary containing user profile information
        """
        # Return dictionary with consistent formatting
        return {
            'username': self._username,
            'email': self._email,
            'created_at': self.created_at,
            'is_active': self.is_active
        }

    def _validate_password(self, password: str) -> bool:
        """Protected method for password validation.

        Args:
            password: Password to validate

        Returns:
            Boolean indicating if password is valid
        """
        # Use inline comment only for complex logic
        return bool(password and len(password) >= 8)  # Minimum length check


# ==========
# Functions
# - Use snake_case for function names
# - Two blank lines before function definitions
# ==========
def calculate_user_statistics(
    users: List[UserProfile],
    *,                              # Enforce keyword-only arguments
    include_inactive: bool = False,  # Default value
    max_count: Optional[int] = None  # Optional parameter type
) -> Dict[str, int]:                # Return type annotation
    """Calculate user statistics.

    Args:
        users: List of user profiles
        include_inactive: Whether to include inactive users
        max_count: Maximum number of users to process

    Returns:
        Dictionary with user statistics
    """
    # Use meaningful variable names
    total_users = len(users)
    active_users = sum(1 for user in users if user.is_active)

    # Dictionary proper formatting
    stats = {
        'total_users': total_users,
        'active_users': active_users
    }

    # Explicit is better than implicit
    if include_inactive:
        stats['inactive_users'] = total_users - active_users

    return stats


def process_data(
    data: List[Dict[str, Union[str, int]]],  # Complex type hints
    **kwargs: Dict[str, any]                  # Variable keyword arguments
) -> List[Dict[str, Union[str, int]]]:
    """Process input data with optional parameters.

    - Docstring uses proper indentation
    - Args and Returns sections properly formatted
    """
    try:
        # Good: Use meaningful temporary variable names
        processed_data = []
        
        # Good: Explicit iteration over sequence
        for item in data:
            if _should_process_item(item):  # Protected function call
                processed_item = _process_single_item(item)
                processed_data.append(processed_item)
                
    except Exception as e:
        # Good: Exception variable named 'e'
        # Good: Error message uses string formatting
        print(f"Error processing data: {str(e)}", file=sys.stderr)
        raise

    return processed_data


# Protected functions start with underscore
def _should_process_item(item: Dict[str, any]) -> bool:
    """Check if item should be processed."""
    # Good: Return boolean conditions directly
    return bool(item and item.get('active', True))


def _process_single_item(item: Dict[str, any]) -> Dict[str, any]:
    """Process a single item."""
    # Good: Create new dictionary instead of modifying input
    return {
        **item,
        'processed': True,
        'processed_at': datetime.now()
    }


# ==========
# Main execution block
# - Two blank lines before
# - Proper if __name__ == '__main__' block
# ==========
if __name__ == '__main__':
    # Example usage
    users = [
        UserProfile(
            username='user1',
            email='user1@example.com',
            is_active=True
        ),
        UserProfile(
            username='user2',
            email='user2@example.com',
            is_active=False
        )
    ]

    # Good: Use keyword arguments for clarity
    stats = calculate_user_statistics(
        users=users,
        include_inactive=True
    )

    # Good: Use f-strings for string formatting
    print(f"User statistics: {stats}")

    # Good: Line breaking before binary operators
    total_active_users = (
        stats['total_users']
        - stats.get('inactive_users', 0)
    )

    # Good: Consistent spacing in expressions
    if total_active_users > 0 and stats['total_users'] < MAX_CONNECTIONS:
        print("System is operating within normal parameters")

참고 및 출처