Python

[PEP series] PEP-008 Style Guide (2)

Jaedey 2025. 2. 2. 22:51

Comments

Common Tips

  1. 주석에 쓰이는 언어를 모르는 사람이 없다고 확신할 때만 영어가 아닌 다른 언어를 쓸 것
  2. 첫 문장은 무조건 대문자로 쓸 것 (아니면 identifier로 헷갈릴 수 있다.)
  3. identifier의 대문자 소문자는 꼭 구별해서 잘 쓸 것
  4. 마지막 문장을 제외하면 각 문장마다 공백을 써서 가독성을 높일 것

Block Comment

블록 코멘트는 #와 이후 따라오는 single space로 쓰여진다.

또, 해당 코멘트로 설명하려는 코드와 동일한 indent level을 가져야 한다.

Inline Comment

블록 코멘트와 같이 # 와 이후 따라오는 single space로 쓴다.

inline comment는 해당 코드와 동일한 라인에 적는데, 코드와 적어도 2개 이상의 공백으로 분리한다.

PEP에서는 inline comment를 적게 쓰는 것을 권장한다.

왜냐하면 single line에 대한 설명이기 때문에, 너무 당연한 내용에 대해 서술한다면, 오히려 가독성만 떨어지기 때문이다. 다만, 특정 경우에는 유용하다. ( clean code에서 나왔듯, 코드에 대한 직접적인 설명보다는 코드의 의도나 다른 부가적인 설명같은 경우를 말하는 것 같다.)

# Useless Case
x = x + 1                 # Increment x

# Useful Case: Trying to expalin its intention
x = x + 1                 # Compensate for border

Documentation String

Docstring에 대해서는 PEP257에서 메인으로 다룬다.

Docstring은 모든 public module, function, class, method에 대해 작성되어야 한다.

non-public인 경우에는 docstring이 필수는 아니지만, 해당 내용에 대한 주석이 필요하다. 이 주석은 def 라인 이후에 나와야 한다.

Naming Conventions

Overriding Principle

user에게 공개되는 public part는 내부 구현보다는 usage에 맞는 컨벤션으로 이름이 작성되어야 한다.

Descriptive: Naming Styles

Python에서 쓰이는 Naming Style 자체에 대한 내용이다.

크게

  • UPPERCASE
  • lowercase
  • lower_snake_case
  • lowerCamelCase
  • UpperCamelCase
  • UPPER_CASE_WITH_UNDERSCORE

가 쓰인다. (이 때, 줄임말에 대해서는 모두 uppercase를 쓴다. e.g. HTTPServerError - (O) HttpServerError - (X) )

또한, underscore 역시 의미있게 쓰이는데,

  • _single_leading_underscore: internal use only. from M import *를 통해 import 되지 않는다.
  • single_trailing_underscore: keyword와 겹치는 것을 막기 위해 쓰는 경우
    e.g. def invert(class_='ClassName')
  • __double_leading_underscore: C++의 private과 동일한 역할을 한다.
    foo.__bar등을 통해 접근할 수는 없고, foo._foo__bar를 통해 접근할 수 있다.
  • __double_leading_trailing_underscore: magic object/attribute를 위한 것이다.

Prescriptive: Naming Styles

그대로 쓰다 보니 내용에 비해 글이 너무 복잡하게 나왔다.

Naming Style 마지막에 정리해놓을테니, 아래 내용은 그냥 읽어보고 중요한 것만 나중에 외우도록 하자.

Basics

lower L, upper O, upper i를 단독으로 쓰지 말 것, (or 폰트를 맞추거나) ASCII compatible한 문자로 이름을 지을 것

Pacakges, and Modules

lowercase로 작성하자. 만약 가독성을 높일 수 있다면, underscore를 사용해도 좋다.

python package에서 같이 사용되는 C/C++ package의 경우 single leading underscore를 사용하자.

Class Names

UpperCamelCase로 쓰되, 우선적으로 callable로 쓰이는 class라면, function convention을 따른다.

Typing Variable Names

typing hint를 위한 variable List, Dict등 처럼, UpperCamelCase로 짧게 쓰는 것을 추천한다.

suffix로 _co, _contra를 통해 covariance를 나타낸다.

Exception

class와 동일하게, 단, suffix로 Error를 쓴다.

Global Variable Names (Not Extern)

function과 동일하게 사용

Functions and Variables

function: lower_snake_case

variable: the same

Functions and Method Arguments

self, cls는 항상 첫 번째 인자로 쓸 것

Method Names and Instance Variables

lower_snake_case

Constants

UPPER_CASE_WITH_UNDERSCORE

Naming Style Summary

package: lower case

class, typing variable name, exception: UpperCamelCase

function, method, global variable, variable, instance variable: lower_snake_case

constants: UPPER_CASE_WITH_UNDERSCORE

Designing for Inheritance

python에는 C++의 private같은 단어가 존재하지 않는다. (foo._foo__bar처럼 우회적으로 지원할 뿐)

그래도 public/private이 헷갈린다면 private으로 가정하는 것이 좋다고 한다.

class design에 대한 가이드로는

  1. public attribute에 leading underscore를 쓰지 말 것
  2. public attribute가 keyword와 충돌한다면 trailing underscore를 쓸 것
  3. 만약 subclass에서 사용하고 싶지 않은 attribute가 있다면 leading double underscore를 쓸 것

Public and Internal Interfaces

반복되는 내용이 계속해서 나오는데, 문서화가 된 내용들 중실험용/내부용이라고 언급되지 않는 경우는 모두 public을 간주할 수 있다고 한다.

또한, internal은 굳이 import하거나 접근하지 않아야 하며,

작성자의 입장에서는 public과 internal을 __all__에 명시하여 구분해야 한다고 한다.

Programming Recommendations

General

  • 다양한 Python Implementation 고려하기

    CPython 외에 Pypy나 JPython 등 다양한 Python 구현에 대해 동일하게 작동하는 코드를 작성해야 한다.

    이를 위해서는 공통적으로 사용할 수 있는 표현들에 대해서 공부해야 하는 조금 어려운 작업이고, 예시도 잘 떠오르지 않을 수 있다.

    대표적인 예시가 inplace string concatenation이다.

    str = str1 + str2 혹은 str1 += str2와 같은 구현은 CPython에서는 잘 작동할 수 있지만, 다른 파이썬 구현체에서는 잘 작동하지 않을 수 있다. (in linear time)

    따라서, 이런 경우에는 ''.join(str_list)등을 사용해야 한다고 한다.

  • Singleton 비교 시, is, is not 사용하기

    None의 경우 Singleton이기 때문에 ==, !=등을 사용하기 보다는 is, is not을 사용하는 것을 권장한다. (equality가 아닌 identity를 검사해야 하기 때문에)

    이는 if x: 패턴에서 특히 중요한데,

    if x:가 false로 판단될 때는 x = 0, {}, [] 등 여러 값이 가능하기 때문이다.

  • 비교 연산자는 모두 구현하기

    비교 연산자로 (__eq__, __ne__, __lt__, __le__, __gt__, __ge__) 가 있는데, 이를 다 구현하는 것이 성능 최적화에 더 좋다고 한다.

    물론 이를 다 구현하는 것은 귀찮겠지만 from functools import total_ordering에서 decorator를 통해 일부만 구현하더라도 나머지를 자동적으로 생성해준다.

    (@total_ordering__eq__, __lt__만 있어도, 나머지가 정상 작동할 수 있다.)

    • Why?

      x < yy > x 는 PEP207에서 reflexivity를 가정하기 때문에 동일한 결과값을 얻을 수 있지만,

      x < yx__lt__을 사용한 것이고, y > xy__gt__를 사용한 것이기 때문에 내부 동작에서 차이가 나타날 수 있다.

  • Lambda Expression with Assignment

    f = lambda x: 2*x 처럼 lambda expression을 대입해서 사용할 경우, def를 사용하는 것이 더 이득이 크다고 한다.

    인자에 대해 헷갈리거나, 디버깅 시 나오는 trace 등에서 불이익이 생길 수 있다고 한다.

    또한, lambda 자체가 더 큰 expression에서 사용하기 위한 임시 expression인데, 대입하여 사용하는 것 자체가 lambda expression의 장점을 덮어버리는 일이라고 한다.

  • About Exception

    • BaseException이 아닌 Exception을 상속받아 사용할 것

      BaseException은 최상위 Exception이지만, KeyBoardInterrupt등 다른 중요한 시그널도 잡아버리는 경우가 있다.

    • 예외는 발생한 위치가 아니라, 예외를 처리할 코드 내용 기준으로 작성하고 사용할 것

    • 예외 체이닝을 통해 Trace를 보존할 것

      raise NewException from OriginalException 형식을 사용하면, 원본 traceback을 유지할 수 있다.

    • BaseException과 마찬가지로 bare exception은 사용하지 않는 편이 좋다.

      try:
          perform_operation()
      except:
          log_error()
          raise  # 예외를 다시 발생시킴  

      위의 형태 같은 bare exception은 KeyBoardInterrupt와 같은 다른 중요한 시그널들도 모두 잡아버린다. 따라서, 사용하지 않거나, 구체적인 Error를 잡는 편이 좋다.

      모든 에러를 처리해야 한다면 차라리 except Exception as e를 통해 처리한다.

    • then, when to use bare exception?

      1. traceback을 통해 유저에게 에러 사실을 알릴 때
      2. clean-up 후 예외를 다시 작동시킬 때, 단 이때는 try ... finally를 사용하는 것이 좋다.
  • local/particular section에서 필요한 resource를 얻는 경우 with 혹은 try ... finally를 사용한다.

    자동적으로 할당 해제를 해준다.

  • return statement는 최대한 일관성 있게 작성하자

    # 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)
    
    # Wrong:
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
    
    def bar(x):
        if x < 0:
            return
        return math.sqrt(x)

    만약 해당 라인까지 닿을 수 있다면 명시적으로 return None을 써주도록 하자 (만약 리턴 값이 없다면) value/expression/no-return 등 여러 가지를 혼합해서 리턴할 수 있는 것은 파이썬의 장점이지만, 사람은 헷갈린다.

  • slicing 대신 ''.startswith() and ''.endswith()를 통해 체크하자

  • object type checking은 isinstance를 사용하자

    # Correct:
    if isinstance(obj, int):
    # Wrong:
    if type(obj) is type(1):
  • sequence에 대해서 empty check는 직접적으로 쓰자 (sequence임에 확신이 있다면)

    # Correct:
    if not seq:
    if seq:
    # Wrong:
    if len(seq):
    if not len(seq):
  • finally of try ... finally에서 return/ break/ continue를 사용하지 말 것

    finally 안의 구문은 항상 실행되기 때문에 위의 return/break/continue가 사용된다면 에러가 발생되었는지 모르고 컨트롤이 적용되지 않을 수 있기 때문에 사용을 지양하는 것이 좋다.

  • try 구문은 최대한 적게 유지할 것. masking bug가 생길 수 있다.

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

Function Annotations

PEP-484 참조

Function Annotators에 관한 내용이다 Type Hint, Type Checker, How to ignore Type Checker 등의 내용이 있다.

Variable Annotation

PEP-526 참조

동일하게, variable type과 관련된 내용이다.

# 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

소감

생각보다 예쁜 코드를 쓰는 건 귀찮은 일 같다. 마음 편하게 포매터를 쓰는 것도 좋을 것 같다. (사실 그러면 굳이 이 글을 읽을 필요도 없다.)

다만, 스타일 가이드의 개념 자체가 “더 가독성이 좋은 코드를 쓰기 위한 방법이 있다면 그것을 선택하는 것이 좋다.” 라는 뜻을 가지고 있으므로, 여기에 정리된 방법이 항상 정답은 아닐 것이다.

PEP8 대로 썼을 때 복잡해지는 코드도 있을 것이고, 포매터로 인해 정리했을 때, 복잡해질수도 있다.

이를 적당히 센스있게 쓰고 깊거나, 팀 내 규칙도 따로 있는 경우에는 한 번쯤 읽어봐도 좋을 것 같다.

'Python' 카테고리의 다른 글

[PEP series] PEP-008 Style Guide (1)  (1) 2025.02.02