Python Style Guide in 29CM

chrisjune
chrisjune
Jun 25, 2020 · 12 min read

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

스타일 가이드는 가독성이 높고 코드의 일관성을 유지하는데 중요한 역할을 합니다. 스타일이 통일된 코드는 유지보수 비용을 낮출 수 있기 때문에 29CM에서 이전에 논의하고 합의된 스타일 가이드를 소개하려고합니다.

스타일 가이드는 Google Python Style Guide를 토대로 하고 PEP8을 참고하여 작성되었습니다.

Import

  • 두 줄의 이상으로 넘어가는 경우 소괄호를 이용하여 줄을 나누고, 다음 줄의 내용은 여는 괄호 “(“ 의 시작 부분에 맞추도록 합니다
  • 모듈 이름이 동일할 경우, alias를 추가하여 구분을 하는 것이 좋습니다
  • 상수(const.py)의 경우 alias를 사용하지만, django_api_standard.const 는 공통 모듈로서 alias를 사용하지 않고 필요한 속성을 직접 import 하는 것을 권장합니다.
import os
import sys
from 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

  • 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

매개변수 초기값

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 값의 평가

  • 조건문의 질의의도는 긍정으로 작성하여야 직관적인 해석에 도움됩니다
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

  • classmethod는 클래스 또는 생성자별 특정 루틴에 관한 로직을 작성할 때 사용합니다 예) 프로세스 캐쉬, 로깅
classmethod 는 쓰임새가 많은 게 확실하지만, staticmethod 는 사용해야하는 이유를 잘 모르겠다.
클래스와 함게 작동하지 않는 함수를 정의하려면, 단지 함수를 모듈에 정의하면 된다. 아마 함수가 클래스를 건드리지는 않지만,
그 클래스와 밀접히 연관되어 있어서 클래스 코드 가까운 곳에 두고 싶을 수는 있을 것이다. 그런 경우에는 클래스의 바로 앞이나 뒤에서 함수를 정의하면 된다.

- 전문가를 위한 파이썬 '루시아누 하말류'

스타일 가이드

  • 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

  • + 연산자로 문자열 연결은 지양합니다
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

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의 개발자들이 정한 내용이 조화되어 있습니다. 가이드가 조금이나마 도움이 되었으면 좋겠습니다.

감사합니다.

29CM 기술블로그

Guide to Better Tech — 29CM 기술블로그입니다

29CM 기술블로그

Guide to Better Tech — 29CM 기술블로그입니다

chrisjune

Written by

chrisjune

Blog https://chrisjune.dev Work for www.29cm.co.kr

29CM 기술블로그

Guide to Better Tech — 29CM 기술블로그입니다