Django Coding Style
Django 공식 문서의 “Django Coding Style” 문서를 번역한 글
Django에서 권장하는 코딩 스타일 가이드를 정리해보았다. (부끄러운 얘기지만 1년넘게 Django를 사용하면서 이 문서 전체를 읽어본 적은 없었다.)
코딩 스타일을 지키는 것이 중요한 가장 큰 이유는 다른 사람(혹은 미래의 나)과의 협업에 용이하기 때문이다. 공통 Rule이 있기 때문에 불필요한 논쟁을 줄여주고, 코드가 명확한 의미를 표현하도록 권장하므로 다른 사람의 코드를 읽고, 유지 보수하기도 쉽다.
정리하면서 “왜 이렇게 정했을까?”를 질문하고 고민해보고 찾아보니 대부분 이유가 있었다. 이 글을 읽는 분들도 각 항목에 대해 이유를 생각하면서 읽어보면 더 재미있을 것이다.
목차
1. Python Style
2. Imports
3. Template
4. View
5. Model
6. django.conf.settings 사용
7. 기타
1. Python Style
1-1. PEP 8
- 웬만하면 PEP8을 따름
- Flake8 Plugin 사용 (
setup.cfg
) - 한 줄에 허용되는 글자 수는 예외를 둠
- 코드 : 119자 (GitHub Code Review의 너비)
- 주석, Docstrings : 79자
1-2. Indentation (들여쓰기)
- EditorConfig를 지원하는 Editor를 사용 (
.editorconfig
에 들여쓰기 규칙을 명세) - 공백 수
- Python : 공백 4개
- HTML : 공백 2개
- 코드 라인이 길어질 경우, 수직 정렬 대신 공백 4개로 들여쓰기
- 장점 : 첫 번째 줄의 길이가 변경되어도 아래의 문자열을 다시 정렬할 필요가 없음
1
2
3
4
5
6
7
8
9# NO
raise AttributeError('Here is a multine error message ' # 이 줄의 길이가 변경되면 아래 줄을 다시 정렬해야 함
'shortened for clarity.')
# YES
raise AttributeError(
'Here is a multine error message '
'shortened for clarity.'
)
- 장점 : 첫 번째 줄의 길이가 변경되어도 아래의 문자열을 다시 정렬할 필요가 없음
1-3. Quote (따옴표)
- 단따옴표(
'
) 사용 - 문자열이 단따옴표를 포함하는 경우에만 쌍따옴표(
“
) 사용
1-4. Comment (주석)
주석에는 “우리가(We)” 라는 말은 쓰지 않음 (영미권)
1
2
3
4
5# NO
# We loop over
# YES
# Loop over
1-5. Naming (이름짓기)
Variable, Function, Method의 이름은 underscore로 작성 (camelCase X)
1
2
3
4
5
6
7# NO
def getUniqueVoters():
pass
# YES
def get_unique_voters():
passClass 혹은 Class를 반환하는 Factory Function의 이름은 첫 글자를 대문자로 함 (InitialCaps)
1
2
3
4
5
6
7# NO
class blog_writer:
pass
# YES
class BlogWriter:
pass
1-6. DocString
- DocString은 PEP 257을 따름
1-7. Test
assertRaises()
보다assertRaisesMessage()
를 사용. (예외 메시지를 확인)- Regular Expression Matching이 필요한 경우에는
assertRaisesRegex()
사용
- Regular Expression Matching이 필요한 경우에는
- Docstring에는 명확하게 예상되는 행동을 적음
- 예)
사용자 추가 후 비밀번호 일치 확인
- “이러이러한 Test”, “이러이러한 것을 보증하는” 등의 서문 금지
- 명확한 표현이 어려울 때는 Issue Number로 대체
1
2
3
4
5def test_new_user_same_pw():
"""
사용자 추가 후 비밀번호 일치 확인 (#SYS-123).
"""
...
- 예)
2. Imports
2-1. 자동 Sorting
- isort와 같은 plugin을 사용
- 주석으로 skip 가능
1
import module # isort:skip
2-2. 작성 순서
- Import Group 작성 순서
- Standard libraries
- Third-party libraries
- Django components : Absolute imports
- Local Django components : (Explicit) Relative imports
- try/excepts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31# future
from __future__ import unicode_literals
# 1. Standard libraries
import json
from itertools import chain
# 2. Third-party libraries
import bcrypt
# 3. Django components
from django.http import Http404
from django.http.response import (
Http404, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse,
cookie,
)
# 4. Local Django components
from .models import LogEntry
# 5. try/excepts
try:
import yaml
except ImportError:
yaml = None
CONSTANT = 'foo'
class Example(object):
# ...Object Import 전에 Module을 Import
1
2import module
from module import object각 행은 Alphabet 순으로, 대문자가 먼저 오도록 정렬
2-3. Indentation / Blank Line (들여쓰기 / 줄 공백)
Indentation
- 줄이 길어지면 Line Breaking 후 들여쓰기
- 마지막 Import뒤에도 Comma는 포함
- 마지막 닫는 괄호가 있는 Line에는 괄호만 존재하도록
1
2
3
4from django.http.response import (
Http404, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse, # Line Breaking 후 공백 4개로 들여쓰기
cookie, # 마지막 Import 뒤에도 Comma 포함
) # 닫는 괄호가 있는 줄에는 괄호만 존재
Blank Line
- 1줄 공백 : Import Group 간
- 2줄 공백 : 마지막 Import와 첫 Function/Class 간
1
2
3
4
5
6
7
8
9
10
11import requests
import json
# --------- 1줄 공백
from django.http.response import (
Http404, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse,
cookie,
)
# --------- 2줄 공백
# --------- 2줄 공백
class Example:
pass
3. Template
3-1. Spaces
- 중괄호와 Tag Content 사이에는 1개 Space 만 둠
1
2
3
4
5
6<!-- NO -->
{{var}}
{{ var }}
<!-- YES -->
{{ var }}
4. View
4-1. Request Parameter
- View Function의 첫 번째 Parameter의 이름은
request
를 권장1
2
3
4
5
6
7# NO
def my_view(req, foo):
pass
# YES
def my_view(request, foo):
pass
5. Model
5-1. Naming (이름 짓기)
- Field 이름은 underscore 로 지음
1
2
3
4
5
6
7
8
9# NO
class Person(models.Model):
FirstName = models.CharField(max_length=20)
Last_Name = models.CharField(max_length=40)
# YES
class Person(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=40)
5-2. 순서
- Model의 Inner Class와 Standard Method의 배치 순서
- All database fields
- Custom manager attributes
- class Meta
- def __str__()
- def save()
- def get_absolute_url()
- Any custom methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# "class Meta" 배치 예시
# NO
class Person(models.Model):
class Meta:
verbose_name_plural = 'people'
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=40)
# YES
class Person(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=40)
class Meta:
verbose_name_plural = 'people'
5-3. Choices
- Model의 Field에 choices가 있는 경우, Model Class의 Attribute로 대문자 이름의(all-uppercase) tuple의 tuple로 정의
1
2
3
4
5
6
7class MyModel(models.Model):
DIRECTION_UP = 'U'
DIRECTION_DOWN = 'D'
DIRECTION_CHOICES = (
(DIRECTION_UP, 'Up'),
(DIRECTION_DOWN, 'Down'),
)
6. django.conf.settings
사용
django.conf.settings
에 저장된 settings를 모듈의 Top Level(모듈이 import될 때 실행됨)에서 사용하지 않음. (이유는 아래의 설명을 참고.)아래의 코드는 수동으로 settings 설정하는 코드임. (
DJANGO_SETTINGS_MODULE
에 의존하지 않는)1
2
3from django.conf import settings
settings.configure({}, SOME_SETTING='foo')settings.configure
Line 이전에 settings에 접근한다면 위 코드는 작동하지 않음 (settings는 LazyObject)- 따라서 아래와 같은 모듈이 있다면, 이 모듈이 import될 때 settings가 구성되어 버림. 대신에
django.utils.functional.LazyObject
,django.utils.functional.lazy()
,lambda
를 사용1
2
3
4from django.conf import settings
from django.urls import get_callable
default_foo_view = get_callable(settings.FOO_VIEW)
7. 기타
- 국제화를 위해 모든 String을 (Translatable String으로) 마킹 (참고)
- 코드 변경 시, 더 이상 사용되지 않는 Import문 제거
- flake8 사용. (남아 있어야 하는 경우 Import문의 끝에
#NOQA
표시)
- flake8 사용. (남아 있어야 하는 경우 Import문의 끝에
- 마지막에 붙는 공백 제거
- 시각적으로 방해되거나 코드 병합 시 충돌이 발생할 수 있음
- IDE에서 자동으로 제거하도록 설정할 것
- Contribute 할 때 코드에 본인의 이름을 넣지 말 것. AUTHORS 파일에 추가할 것