Python Style Guide in 29CM
29CM의 파이썬 스타일 가이드를 소개합니다

스타일 가이드는 가독성이 높고 코드의 일관성을 유지하는데 중요한 역할을 합니다. 스타일이 통일된 코드는 유지보수 비용을 낮출 수 있기 때문에 29CM에서 이전에 논의하고 합의된 스타일 가이드를 소개하려고합니다.
스타일 가이드는 Google Python Style Guide를 토대로 하고 PEP8을 참고하여 작성되었습니다.
Import
- 동일 모듈에서 import 하는 경우 컴마(,)를 사용하여 나열하는 것이 가능합니다
- 두 줄의 이상으로 넘어가는 경우 소괄호를 이용하여 줄을 나누고, 다음 줄의 내용은 여는 괄호 “(“ 의 시작 부분에 맞추도록 합니다
- 모듈 이름이 동일할 경우, alias를 추가하여 구분을 하는 것이 좋습니다
- 상수(const.py)의 경우 alias를 사용하지만,
django_api_standard.const
는 공통 모듈로서 alias를 사용하지 않고 필요한 속성을 직접 import 하는 것을 권장합니다.
import os
import sysfrom item import const as item_const
from item import const as a, b, c, d, e, f
from item import const as (a, b, c, d
e, f, g, h)
from django_api_standard.const import TRUE, FALSE
Exception
- 의미가 맞는경우 built-in 에러를 사용합니다
assert
는 값을 검증하는 로직에서는 사용하지 않습니다.assert
는 예기치 않는 사건이나 바른 사용을 체크하는데 사용하는 것이 아니라 내부 정확성을 보장하는데 사용합니다- 라이브러리 또는 패키지 들에서 구현해둔
exception
이 존재할 때는, 상속하여 사용하고Error
로 끝나야만 합니다 - 에러를 re-raise 할게 아니라면 절대로 모든예외를 catch하지 않습니다
- try~except 사이의 코드를 줄어야 잡고자 하는 에러를 명확히 할 수 있습니다
raise MyError('Error Message')
raise MyError()
def connect_to_next_port(self, minimum):
"""Connects to the next available port.
Args:
minimum: A port value greater or equal to 1024.
Raises:
ValueError: If the minimum port specified is less than 1024.
ConnectionError: If no available port is found.
Returns:
The new minimum port.
"""
if minimum < 1024:
raise ValueError('Minimum port must be at least 1024, not %d.' % (minimum,))
port = self._find_next_open_port(minimum)
if not port:
raise ConnectionError('Could not connect to service on %d or higher.' % (minimum,))
assert port >= minimum, 'Unexpected port %d when minimum was %d.' % (port, minimum)
return port
전역변수
- 전역변수는 사용을 지양하고, 필요시에 사용합니다.
- 전역변수 값의 할당은 모듈이 처음 import 될 때만 수행되기 때문에 현재시간을 호출하는 등의 모듈의 동작기능이 생각과 다르게 동작할 수 있습니다.
중첩 클래스 함수
- 직접적으로 테스트를 할 수 없고 함수를 더욱 크게 만들고 가독성을 떨어뜨립니다
- 중첩 함수는 지양하고, 단지 사용자에게 감추고 싶은 것이면
protected
모듈로 만들어 (이름앞에 “_”를 붙여서)사용한다
List comprehension & Dictionary comprehension
- 조건문과 반복문 각각 한개씩 사용하는 경우 권장
- 두개이상의 경우, 지양
result = [mapping_expr for value in iterable if filter_expr]
result = [{'key': value} for value in iterable
if a_long_filter_expression(value)]
result = [complicated_transform(x)
for x in iterable if predicate(x)]
descriptive_name = [
transform({'key': key, 'value': value}, color='black')
for key, value in generate_iterable(some_input)
if complicated_condition_is_met(key, value)
]
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
return {x: complicated_transform(x)
for x in long_generator_function(parameter)
if x is not None}
squares_generator = (x**2 for x in range(10))
unique_names = {user.name for user in users if user is not None}
eat(jelly_bean for jelly_bean in jelly_beans
if jelly_bean.color == 'black')
Iterator & Generator
- 컨테이너를 반복할때는 아래와 같은 형식을 사용합니다
for key in adict: ...
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
for k, v in six.iteritems(adict):
Lambda
- 한줄의 경우 사용가능하며, 일반적인 연산(
lambda x, y: x * y
→operator.mul
)은 내장operator module
을 사용합니다
매개변수 초기값
container
타입의 변수에는 초기값을None
으로 설정합니다
Yes: def foo(a, b=None):
if b is None:
b = []Yes: def foo(a, b: Optional[Sequence] = None):
if b is None:
b = []Yes: def foo(a, b: Sequence = ()): # tuple은 불변객체므로 빈값으로 초기값 선언해도 괜찮습니다.
Properties
- 단순한 케이스에서만 사용하는 것을 권장합니다
- 프로퍼티의 사용가능한 최대 개수는 50개로 제한합니다
import math
class Square(object):
"""사각형의 크기를 설정하면 한 변 또는 둘레를 알 수 있는 클래스
To use:
>>> sq = Square(3)
>>> sq.area
9
>>> sq.perimeter
12
>>> sq.area = 16
>>> sq.side
4
>>> sq.perimeter
16
"""
def __init__(self, side):
self.side = side
@property
def area(self):
return self._get_area()
@area.setter
def area(self, area):
return self._set_area(area)
def _get_area(self):
return self.side ** 2
def _set_area(self, area):
self.side = math.sqrt(area)
@property
def perimeter(self):
return self.side * 4
Boolean 값의 평가
- 직접적인
None
에 대한 평가가 필요하지 않은 경우,not
연산자를 사용합니다 - 조건문의 질의의도는 긍정으로 작성하여야 직관적인 해석에 도움됩니다
if not users:
print('no users')
if foo == 0:
self.handle_zero()
if i % 10 == 0:
self.handle_multiple_of_ten()
def f(x=None):
if x is None:
x = []
Decorator
- 분명한 이점이 있을 때 사용을 하고 해당부분은 꼭 테스트 코드를 작성하여 기능 동작을 분명히 합니다
- 데코레이터는
import
시점(모듈이 임포트되는 시점)에 평가되기 때문에 이부분에 유의해야 합니다. - 또한, 데코레이터로인한 에러는 디버깅하기 까다롭습니다.
class C(object):
@my_decorator
def method(self):
# method body ...
= 위와 아래 코드 기능은 동일
class C(object):
def method(self):
# method body ... method = my_decorator(method)
Staticmethod & Classmethod
staticmethod
는 사용하지 않습니다. 관련 클래스 근처에 작성하거나 유틸 모듈로 분리하는 것이 좋습니다.classmethod
는 클래스 또는 생성자별 특정 루틴에 관한 로직을 작성할 때 사용합니다 예) 프로세스 캐쉬, 로깅
classmethod 는 쓰임새가 많은 게 확실하지만, staticmethod 는 사용해야하는 이유를 잘 모르겠다.
클래스와 함게 작동하지 않는 함수를 정의하려면, 단지 함수를 모듈에 정의하면 된다. 아마 함수가 클래스를 건드리지는 않지만,
그 클래스와 밀접히 연관되어 있어서 클래스 코드 가까운 곳에 두고 싶을 수는 있을 것이다. 그런 경우에는 클래스의 바로 앞이나 뒤에서 함수를 정의하면 된다.
- 전문가를 위한 파이썬 '루시아누 하말류'
스타일 가이드
- 한 라인의 최대 글자 개수는 120자 단,
url
, 경로, 코멘트의 경우는 120자 이상에 대한 예외 규정을 둡니다 - backslash 문자는 with연산자를 사용하는 경우가 아니면 사용하지 않습니다
with very_long_first_expression_function() as spam, \
very_long_second_expression_function() as beans, \
third_thing() as eggs:
place_order(eggs, beans, spam, beans)
공백 및 정렬
- 소괄호를 사용하는 경우, 다음줄로 넘어갈때는 여는 괄호에 시작을 맞추고 닫는 괄호는 마지막 수식뒤에 붙입니다
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)
- 컨테이너 타입 데이터의 경우, 열고 닫는 괄호는 아래와 같이 옆에 수식을 쓰지 않도록 합니다.
- 그리고, 다음줄로 넘어갈때는 4개 space 여백으로 시작합니다.
foo = {
long_dictionary_key: value1 +
value2,
...
}
foo = {
long_dictionary_key: (value1 +
value2)
...
}
문자열 Concatenate
- 문자열 연결은 % 기호, f string, formet연산자 등을 사용하는 것을 권장합니다
- + 연산자로 문자열 연결은 지양합니다
x = a + b
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}'.format(first, second)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
x = f'name: {name}; score: {n}' # Python 3.6+
- 반복문 내부에서 문자열을 연결해야 하는 경우, List에 담아놓고, 마지막에 join 연산자 등으로 한번에 생성하도록 합니다.
- +=, = 연산자는 내부임시변수를 생성하므로 수행시간이 0 → 0²으로 늘어나기 때문입니다
items = ['<table>']
for last_name, first_name in employee_list:
items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
items.append('</table>')
employee_table = ''.join(items)
Private vs Protected
- 내부변수의 경우, 기본적으로 public을 사용하고 필요에 따라서 protected 와 private를 적절히 사용합니다
protected
- _ 한개로 시작하는 메소드 또는 인스턴스 변수를 의미합니다.
- 이 경우, 외부 모듈단위 import 할때 참조되지 않지만, 직접 지정하여 import 할 때는 사용가능합니다
class Example:
def a(self):
pass
def _b(self):
pass
>> import Example
e = Example()
e.a // 가능
e._b // 불가능
private
- __ 두개로 시작하는 메소드 또는 인스턴스 변수입니다
- 파이썬에는 실질적인 private가 존재하지 않고, 어디에서든 참조가 가능합니다.
- 직접지정 또는 모듈단위로 import가 불가능합니다
- 필요시에 사용하도록 합니다
해당 가이드는 PEP8을 기준으로 29CM의 개발자들이 정한 내용이 조화되어 있습니다. 가이드가 조금이나마 도움이 되었으면 좋겠습니다.
감사합니다.