PEP8(Python Enhancement Proposal)
Python 코드 작성시 따라야할 스타일 가이드 8가지
들여쓰기(Indentation)
4개의 공백 사용. Continuation lines는 소괄호, 중괄호, 대괄호로 묶거나 hanging indent를 사용하여 정렬.
# 👍🏻 좋은예:
foo = long_function_name(var_one, var_two,
var_three, var_four)
def long_function_name( # 인자들을 구분하기 위해 4개의 공백 추가
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)
# 첫번째 줄은 들여쓰기가 되어있지 않고
# 두번째 줄은 들여쓰기가 되어 있음. hanging indent.
# 이어지는 줄이라는것을 나타내기 위해 추가적인 들여쓰기를 사용하여 구분하기.
# 👎🏻 나쁜예:
foo = long_function_name(var_one, var_two, # 함수 인자를 여러 줄로 나누어 작성할때는 행의 맨 처음에 인자를 두지않고 개행을 한 후 시작.
var_three, var_four)
def long_function_name( # 함수 인자를 여러 줄로 나누어 작성할때는 함수본문과 구분이 명확하게 하기 위해 추가 들여쓰기 필요.
var_one, var_two, var_three,
var_four):
print(var_one)
# if문의 조건이 너무 길때
# if문 다음에 한칸 띄고 ()괄호를 이용해 여러 줄로 나눔. 두번째 줄은 첫번째 줄과 동일한 들여쓰기.
if (this_is_one_thing and
that_is_another_thing):
do_something()
# 조건문과 실행 블록 사이에 주석을 추가하여 추가적인 구분
if (this_is_one_thing and
that_is_another_thing):
# 주석 추가
do_something()
# 조건문의 두번째줄에 추가적인 들여쓰기를 사용하여 첫번째 줄과 구분.
if (this_is_one_thing
and that_is_another_thing):
do_something()
# 여러줄의 소괄호/중괄호/대괄호
# 마지막 항목의 첫번째 비공백 문자 아래에 괄호를 위치시키는 방법.
my_list = [
1, 2, 3,
4, 5, 6,
] # 4 아래에 괄호 위치
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
# 다중 줄 구조를 시작하 그 줄의 첫번째 문자 아래에 괄호를 위치시키는 방법.
my_list = [
1, 2, 3,
4, 5, 6,
] # m 아래에 괄호 위치
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
Tabs or Spaces?
Python에서는 Tab 대신 Spaces를 사용하는 것을 선호. 이미 Tab을 사용한 경우 일관되게 Tab 사용.
특히 python3의 경우, Tab과 Spaces 혼용 사용 불가.
Maximum Line Length
한 줄의 최대 길이를 최대 79자로 제한. docstring/comments는 72자로 제한.
Continuation lines의 경우, 소괄호/중괄호/대괄호에 넣거나 \ 사용.
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())
Should a Line Break Before or After a Binary Operator?
연산기호 전후 모두 줄바꿈 가능하나, 가독성울 위해 연산기호 전에 줄바꿈하기
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
Black Lines
가장 상위의 함수와 클래스 정의는 2줄의 빈공간
클래스 내의 메소드정의는 1줄의 빈공간
함수내의 빈줄은 가능한 자제. 논리적 부분을 표시하기 위해서 사용.
Source File Encoding
UTF-8 사용, ASCII 문자 사용
Imports
# 👍🏻 좋은예:
import os
import sys
from subprocess import Popen, PIPE
# 👎🏻 나쁜예:
import sys, os
imports는 항상 파일의 맨 위, 만약 모듈의 주석이나 docstring이 있다면 주석, docstring 바로 뒤에 위치하는 것이 좋음.
import의 순서
1. 표준 라이브러리. 내장(설치없이 사용할 수 있는) 라이브러리 임포트
2. 외부에서 설치해야하는 패키지. 표준 라이브러리와 분리하여 위치시킴.
3. 로컬 애플리케이션/라이브러리 특정 import(프로젝트내 다른 모듈이나 사용자 정의 라이브러리)
# 👍🏻 좋은예:
# Module docstring
"""Example module to demonstrate proper import order."""
import os # 표준 라이브러리
import sys
import requests # 외부 라이브러리
from myapp import local_module # 사용자 정의 라이브러리
절대 임포트 : 모듈의 전체 경로를 사용하여 해당 모듈을 임포트 하는 방식으로, 코드를 더 읽기 쉽게 만들고, import 시스템이 잘못 구성되었을때(패키지 내의 디렉토리가 sys.path에 포함되었을때) 더 나은 오류 메시지를 제공함. 라이브러리 코드의 의존성과 구조가 명확해져서 개발자가 코드를 더 쉽게 이해하고 사용할 수 있음.
상대 임포트 : 현재 모듈 위치에 상대적인 경로를 사용하여 모듈을 임포트 하는 방식. 복잡한 패키지 레이아웃을 가진 경우, 덜 번거롭고 코드를 간결하게 유지할 수 있음. 상대 임포트는 코드가 어떤 위치에 있는지 파악하기 어렵게 만들 수 있으므로, 명확한 프로젝트 구조에 주로 사용함.
# 절대 import - 가장 좋음
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
# 명시적인 상대 import
from . import sibling
from .sibling import example
# 와일드카드(*)는 피하기
from mypkg import *
직접 클래스 임포트 : 클래스를 모듈에서 직접 임포트. 모듈의 네임스페이스를 통하지 않고 바로 사용할 수 있음. 특정 클래스만을 명시적으로 임포트하여 사용하므로 코드가 간결하고 읽기 쉬워짐.
모듈 임포트 : 로컬이름이 충돌을 일으킬 경우(같은 이름의 클래스나 변수가 존재하는 경우) 모듈을 전체적으로 임포트하는 것이 좋음. 모듈 이름과 함께 클래스 이름을 명시해야함.
# 직접 클래스 임포트
from myclass import MyClass
from foo.bar.yourclass import YourClass
# 모듈 임포트
import myclass
import foo.bar.yourclass
x = myclass.MyClass()
y = foo.bar.yourclass.YourClass()
Module Level Dunder Names
Dunder Method : 언더스코어 2개가 붙는 메소드. Double UNDERscore Method.
Dunder는 모듈 docstring 뒤에, from __future__imports를 제외한 모든 import문 앞에 놓여야함.
"""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
파이썬에서는 문자열을 사용할때 작은따옴표든 큰 따옴표든 상관없지만 일관되게 사용해야함.
3개의 따옴표로 된 문장의 경우, 큰 따옴표를 사용함.
Pet Peeves
# 소괄호/중괄호/대괄호 바로 안쪽은 공백 금지
#
# 👍🏻 좋은예:
spam(ham[1], {eggs: 2})
# 👎🏻 나쁜예:
spam( ham[ 1 ], { eggs: 2 } )
# 끝의 콤마와 괄호 사이는 공백 금지
#
# 👍🏻 좋은예:
foo = (0,)
# 👎🏻 나쁜예:
bar = (0, )
# 콤마, 세미콜론, 콜론 바로 앞은 공백 금지
#
# 👍🏻 좋은예:
if x == 4: print(x, y); x, y = y, x
# 👎🏻 나쁜예:
if x == 4 : print(x , y) ; x , y = y , x
# 슬라이싱에서 콜론은 이항연산자처럼 양쪽 둘다 동일한 공백. 확장슬라이싱에서는 두 콜론의 간격이 동일해야함.
# 예외: 매개변수를 생략하면 공백도 생략.
#
# 👍🏻 좋은예:
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]
# 👎🏻 나쁜예:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : step]
ham[ : upper]
# 함수를 호출할때 여는 괄호 바로 앞은 공백 금지
#
# 👍🏻 좋은예:
spam(1)
# 👎🏻 나쁜예:
spam (1)
# 인덱스와 슬라이싱을 여는 괄호 바로 앞은 공백 금지
#
# 👍🏻 좋은예:
dct['key'] = lst[index]
# 👎🏻 나쁜예:
dct ['key'] = lst [index]
# 변수 할당시 다른 변수와 줄을 맞추는 공백 금지
#
# 👍🏻 좋은예:
x = 1
y = 2
long_variable = 3
# 👎🏻 나쁜예:
x = 1
y = 2
long_variable = 3
Other Recommendations
# 맨 뒤에오는 공백은 금지.
# 이항연산자 앞뒤로 공백.
#
# 👍🏻 좋은예:
1 + 1 = 2
# 👎🏻 나쁜예:
1+1=2
# 우선순위가 다른 연산자를 사용하는 경우, 우선순위가 낮은 연산자 앞뒤로 공백. 한칸외에 공백을 넣지말고, 앞뒤로 동일하게 공백을 주어야함.
#
# 👍🏻 좋은예:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# 👎🏻 나쁜예:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
# 함수의 어노테이션은 콜론과 동일한 규칙을 따르며, '->' 양쪽에 공백.
#
# 👍🏻 좋은예:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...
# 👎🏻 나쁜예:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
# 키워드인자를 사용할 경우, '='앞뒤에는 공백 금지.
#
# 👍🏻 좋은예:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# 👎🏻 나쁜예:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
# 기본값을 가지는 인자 어노테이션을 하비는 경우, '=' 앞뒤에 공백
#
# 👍🏻 좋은예:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
# 👎🏻 나쁜예:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
# 같은 라인에 여러개의 statements 금지
#
# 👍🏻 좋은예:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()
# 👎🏻 나쁜예:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
# 간단한 if/for/while이면 한줄에 적는 건 괜찮음. 조건이 많은 문장에서는 한줄에 적기 금지.
#
# 👎🏻 나쁜예:
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
# 👎🏻 더 나쁜예:
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()
try: something()
finally: cleanup()
do_one(); do_two(); do_three(long, argument,
list, like, this)
if foo == 'blah': one(); two(); three()
끝에 콤마를 붙이는 건 선택사항이며, 끝에 콤마를 붙인 후에는 명확성을 위해 괄호로 둘러싸자.
# 👍🏻 좋은예:
FILES = ('setup.cfg',)
# 👎🏻 나쁜예: 괄호가 없는데 뒤에 콤마를 붙인 경우.
FILES = 'setup.cfg',
끝에 콤마를 붙이는 것은 값 등이 추가될 예정이면 유지보수에 도움이 됨.
사용방법은 한 줄에 값 하나씩, 끝에 콤마를 추가하고 줄바꿈을 함.
더이상 값이 없으면 다음 줄에 괄호로 닫아줌.
# 👍🏻 좋은예:
FILES = [
'setup.cfg',
'tox.ini',
# 여기에 추가,
]
initialize(FILES,
error=True,
)
# 👎🏻 나쁜예:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
모순된 주석은 주석이 없는 것보다 나쁘다. 코드를 바꿀 때마다 주석도 업데이트 해주기!
주석은 완전한 문장으로 마침표로 끝내야하며, 여러 문장의 주석인 경우 주석사이에 한두칸의 공백을 두어야함.
특히, 내가 쓴 주석이 다른 사람이 보기에도 쉽게 이해할 수 있는지 확인해야함.
Block Comments
블록주석은 주석을 해당 코드와 동일한 레벨의 들여쓰기를 해야함.
주석은 #과 단일 공백(' ')으로 시작.
블록주석의 단락은 공백의 한 줄이 아닌 단일 # 을 포함한 줄로 구분함.
# 주석입니다
#
# 주석입니다
Inline Comments
코드와 동일한 줄에 있는 인라인주석은 자제해서 사용하기.
인라인주석을 사용해야한다면 코드와 적어도 2칸의 공백으로 구분되어야하고, 너무 명확한걸 나타내는 용으로는 사용하면 안됨. 어떤 목적의 코드인지 나타내는 용으로 사용하기.
# 👍🏻 좋은예:
x = x + 1 # 목적 : Compensate for border
# 👎🏻 나쁜예:
x = x + 1 # 증가
Documentation Strings
좋은 Documnetation Strings(docstrings)를 작성하는 방법은 PEP 257 참고.
docstrings은 퍼블릭 모듈, 함수, 클래스, 메소드 등이 수행하는 일을 설명하는데 사용되며, def 뒤에 와야함.
# 여러 줄의 docstrings는 줄바꿈 후에 """만 입력.
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
# 한 줄의 docstrings면 같은 줄에 """로 끝내기.
"""Return an ex-parrot."""
Overriding Principle
퍼블릭 API에서는 구형방식보다는 사용법에 따라 명명해야함. 즉 API를 사용하는 개발자들이 해당이름을 보았을때, 그 기능이나 역할을 쉽게 이해할 수 있어야함.
Descriptive:Naming Styles
가장 잘 쓰이는 네이밍 스타일
접두어, 접미어 사용
Prescriptive:Naming Conventions
Public and Internal Interfaces
공개 인스턴스와 내부 인스턴스를 명확히 구분할 수 있어야함.
공개 인스턴스는 일반적으로 문서화 되어있음. __all__속성을 사용하여 공개 API에 포함된 이름을 명시적으로 선언해야함.
내부 인스턴스는 이름앞에 (_)를 붙여서 내부적으로만 사용되어야함을 나타냄.
# with 문 사용
with open('file.txt', 'r') as f:
content = f.read()
# 파일 사용
# 이 시점에서 파일은 자동으로 닫힘
# try/finally 문 사용
f = open('file.txt', 'r')
try:
content = f.read()
# 파일 사용
finally:
f.close()
# 👍🏻 좋은예:
with conn.begin_transaction():
do_stuff_in_transaction(conn)
# 👎🏻 나쁜예:
with conn:
do_stuff_in_transaction(conn)
Function Annotation
함수 어노테이션은 PEP484 구문을 따라야함. 함수 어노테이션을 다르게 사용하려는 코드에 대해서는 파일 상단에 # type : ignore 주석을 추가하여 타입 체커가 모든 어노테이션을 무시하도록 지시할 수 있음.
# type은 매개변수의 예상 타압. return_type은 함수 반환값의 타입.
def function_name(parameter: type, ...) -> return_type:
...
Variable Annotation
# 👍🏻 좋은예:
code: int
class Point:
coords: Tuple[int, int]
label: str = '<unknown>'
# 👎🏻 나쁜예:
code:int # 콜론 뒤에 공백이 없음
code : int # 콜론 앞에 공백이 있음
class Test:
result: int=0 # 등호 주변에 공백이 없음