Python PEP 8 명명 규칙: 변수와 함수는 snake_case

PEP 8은 함수와 변수 이름을 소문자로 쓰고, 가독성에 필요한 만큼 단어를 밑줄로 구분하라고 합니다. 이것이 snake_case입니다. total_price, send_invoice()처럼요. 클래스는 CapWords(InvoiceSender)를, 상수는 밑줄로 연결한 대문자(MAX_RETRIES)를 쓰고, 모듈에는 짧은 전체 소문자 이름을 붙입니다. 여러분이 쓰게 될 Python 코드의 대부분이 이 범위에 들어갑니다.
쓸모 있는 세부 내용은 PEP 8 명명 규칙 절의 나머지 부분에 있습니다. 앞뒤 밑줄의 의미, 약어를 다루는 방법, 피해야 할 한 글자 이름, 규칙을 깨는 것이 맞는 경우가 그것입니다. 아래 내용은 모두 PEP 8 원문과 대조해 확인했고, 따옴표 안의 영어는 PEP 8의 원문 그대로입니다.
한눈에 보는 PEP 8 명명 규칙
| 이름을 붙일 대상 | PEP 8 스타일 | 예 |
|---|---|---|
| 모듈 | 짧은 전체 소문자. 도움이 되면 밑줄 사용 | billing.py, tax_rules.py |
| 패키지 | 짧은 전체 소문자. 밑줄은 권장하지 않음 | payments |
| 클래스 | CapWords | InvoiceSender |
| 예외 | CapWords. 오류라면 이름 끝에 Error | PaymentDeclinedError |
| 함수, 메서드 | snake_case | send_invoice() |
| 변수, 인스턴스 변수 | snake_case | total_price |
| 메서드의 첫 번째 인자 | self, 클래스 메서드는 cls | def from_settings(cls, settings) |
| 상수 | 밑줄로 연결한 대문자, 보통 모듈 수준 | MAX_RETRIES |
| 타입 변수 | 짧은 CapWords | T, AnyStr |
| 비공개 이름 | 앞에 밑줄 하나 | _rate_cache |
작은 모듈 하나에 표의 대부분을 담아 보면 이렇습니다.
# billing.py
MAX_RETRIES = 3
_rate_cache = {}
class PaymentDeclinedError(Exception):
pass
class InvoiceSender:
def __init__(self, smtp_host):
self.smtp_host = smtp_host
self._attempts = 0
def send_invoice(self, invoice_id):
self._attempts += 1
...
@classmethod
def from_settings(cls, settings):
return cls(settings["smtp_host"])알아 둘 만한 드문 행이 두 가지 있습니다. 타입 변수에는 공변성이나 반공변성을 나타내는 _co 또는 _contra 접미사를 붙일 수 있습니다. VT_co = TypeVar('VT_co', covariant=True)가 그 예입니다. 그리고 전역 변수는 함수 규칙을 따릅니다. 모듈을 from module import *로 쓰도록 만들었다면, PEP 8은 공개 이름을 __all__에 나열하라고 권합니다.
왜 snake_case일까요?
PEP 8은 2001년 7월에 나왔고, 저자로 올라 있는 사람은 Guido van Rossum, Barry Warsaw, Alyssa Coghlan입니다. 출발점은 Guido의 관찰 하나, 즉 코드는 쓰이는 횟수보다 훨씬 더 자주 읽힌다는 것입니다. 명명 규칙은 이 목표를 위한 것입니다. 밑줄은 긴 이름(days_since_last_login)에서도 단어 경계가 보이게 해 주고, snake_case 값과 CapWords 클래스를 나누면 이름을 찾아보기 전에 그것이 어떤 종류인지 알 수 있습니다. PEP 8은 공개 API에 대해서도 가장 우선하는 원칙을 세웁니다. 이름은 구현 방식이 아니라 사용 방식을 반영해야 한다는 것입니다.
밑줄의 의미
Python에는 private 키워드가 없어서, 그 역할을 밑줄이 관례로 대신합니다. PEP 8은 네 가지 형태를 인정합니다.
_single_leading: 약한 “internal use”(내부용) 신호입니다.from module import *는 이런 이름을 건너뛰지만, 그 밖에는 누가 써도 막을 방법이 없습니다.single_trailing_: 키워드와의 충돌을 피합니다.class_가clss보다 낫지만, PEP 8은 동의어를 쓰는 편이 더 나을 수 있다고 덧붙입니다.__double_leading: 클래스 안에서 이름 맹글링(name mangling)을 일으킵니다. PEP 8은 서브클래스의 속성과 충돌하는 것을 피할 때만 쓰라고 하며, 이를 싫어하는 사람도 있다고 인정합니다.__double_leading_and_trailing__:__init__,__file__같은 “magic” 이름입니다. PEP 8은 이런 이름을 새로 만들지 말고 문서화된 것만 쓰라고 합니다.
이름 맹글링을 비공개 처리로 여기는 사람은 놀라게 됩니다. 이것은 이름을 바꾸는 것일 뿐 잠그는 것이 아닙니다.
class Account:
def __init__(self):
self.__pin = "1234"
acct = Account()
acct.__pin # AttributeError
acct._Account__pin # "1234": the mangled name still works상수는 규칙이 아니라 관례입니다
이름을 대문자로 쓴다고 해서 누가 다시 대입하는 것을 막지는 못합니다. Python은 MAX_RETRIES = 4를 아무 문제 없이 실행합니다. 도구가 강제하게 하고 싶다면 이름에 typing.Final을 붙이세요.
from typing import Final
MAX_RETRIES: Final = 3
MAX_RETRIES = 4 # still runs, but mypy reports:
# Cannot assign to final name "MAX_RETRIES"열거형 멤버도 대문자가 나오는 곳입니다. Python 문서의 enum HOWTO는 UPPER_CASE 멤버 이름을 강하게 권하는데, 열거형이 상수를 나타내기 때문이기도 하고 메서드 이름과의 충돌을 피하기 위해서이기도 합니다. class Color(Enum)에 RED와 GREEN 멤버가 있는 식입니다.
약어, 한 글자 이름, 그 밖의 예외
클래스 이름 속 약어
PEP 8은 CapWords에서 약어의 모든 글자를 대문자로 쓰라고 하므로 HTTPServerError가 HttpServerError보다 낫습니다. 표준 라이브러리도 http.server.HTTPServer, json.JSONDecodeError 같은 곳에서 이를 따릅니다. snake_case에서는 약어도 그냥 소문자가 됩니다. parse_json, http_status처럼요.
도구로 이름을 바꿀 때는 이 점이 중요합니다. 저희 대소문자 변환기는 processOrder를 process_order로, HTTPServerError를 http_server_error로 바꾸는데, snake_case에서는 이것이 정확히 맞습니다. 하지만 PascalCase 출력은 약어를 일반 단어처럼 쓰므로 HTTP server error는 HttpServerError가 되고, PEP 8에 맞추려면 대문자를 직접 고쳐야 합니다.
피해야 할 글자
한 글자 이름으로 l(소문자 L), O(대문자 오), I(대문자 아이)를 절대 쓰지 마세요. 일부 글꼴에서는 1, 0과 구별되지 않습니다.
mixedCase
camelCase 함수 이름은 하위 호환성을 위해 그 스타일이 이미 지배적인 곳에서만 허용됩니다. PEP 8이 직접 드는 예는 threading 모듈입니다.
표준 라이브러리는 왜 자기 규칙을 어길까요
PEP 8은 Python 라이브러리의 명명이 “a bit of a mess”(좀 엉망)라고 인정합니다. logging.getLogger()와 unittest의 assertEqual()은 mixedCase이고, datetime.datetime 같은 소문자 클래스도 만나게 됩니다. list와 dict 같은 내장 타입은 의도적으로 소문자입니다. PEP 8은 내장 이름에 별도의 관례가 있다고 설명하며, CapWords는 예외 이름과 내장 상수에만 쓴다고 합니다.
PEP 8의 규칙 두 가지가 이 이름들이 대체로 그대로 남아 있는 이유를 설명합니다. 첫째, 기존 라이브러리가 다른 스타일을 쓰는 곳에서는 내부 일관성을 우선합니다. 둘째는 “A Foolish Consistency is the Hobgoblin of Little Minds”(어리석은 일관성은 소인배의 도깨비다)라는 제목의 절입니다. PEP 8과의 일관성도 중요하지만 프로젝트 안의 일관성이 더 중요하고, 모듈이나 함수 안의 일관성이 가장 중요하다는 내용입니다. 규칙을 따르겠다고 하위 호환성을 깨뜨리지 마세요. unittest의 테스트 클래스가 지금도 setUp()을 정의하는 이유가 이것입니다. 프레임워크가 바로 그 이름을 호출하기 때문입니다.
Ruff나 pep8-naming으로 이름을 자동 검사하세요
이름을 눈으로 일일이 검토할 필요는 없습니다. flake8 플러그인 pep8-naming이 검사해 주고, Ruff는 같은 검사를 N 규칙으로 구현합니다. 다음과 같은 파일이 있다고 해 보겠습니다.
maxRetries = 3
class order_processor:
def processOrder(self, orderId):
totalPrice = 0
return totalPrice
class InvalidOrder(Exception):
passRuff는 이 파일의 모든 이름을 지적합니다.
$ ruff check --select N --output-format concise orders.py
orders.py:1:1: N816 Variable `maxRetries` in global scope should not be mixedCase
orders.py:4:7: N801 Class name `order_processor` should use CapWords convention
orders.py:5:9: N802 Function name `processOrder` should be lowercase
orders.py:5:28: N803 Argument name `orderId` should be lowercase
orders.py:6:9: N806 Variable `totalPrice` in function should be lowercase
orders.py:10:7: N818 Exception name `InvalidOrder` should be named with an Error suffix
Found 6 errors.프로젝트 전체에 이 규칙을 켜려면 pyproject.toml에 추가하세요.
[tool.ruff.lint]
extend-select = ["N"]붙여넣은 파일에서 식별자 하나만 바꿀 때는 저희 찾기 및 바꾸기 도구가 몇 건을 바꿨는지 보여 주므로, 결과를 다시 붙여넣기 전에 간단히 점검할 수 있습니다. 코드베이스 전체라면 다른 파일의 참조도 따라 바뀌도록 편집기의 이름 바꾸기 리팩터링을 쓰세요.
PEP 8 명명 체크리스트
- 함수, 메서드, 변수, 인자에는 snake_case.
- 클래스에는 CapWords. 약어는 대문자로 쓰고, 예외에는
Error접미사를 붙입니다. - 모듈 수준 상수에는 밑줄로 연결한 대문자. 검사기가 강제하게 하려면
Final도 씁니다. - 모듈 이름은 짧은 소문자로, 패키지 이름에는 밑줄을 피합니다.
- 내부용 이름에는 앞에 밑줄 하나. 밑줄 두 개는 서브클래스와의 충돌을 피할 때만 씁니다.
- 기존 라이브러리가 다른 스타일을 쓴다면 그 라이브러리에 맞춥니다.
다른 언어와 오가시나요? 개발자 치트시트에 Python의 규칙이 JavaScript, Java, Go, Rust 등과 나란히 정리되어 있습니다.


