Google Python Style Guide 정리본
google.github.io/styleguide/pyguide.html
styleguide
Style guides for Google-originated open-source projects
google.github.io
1. Background
2. Python Language Rules
2.1 Lint
2.2 Imports
- import 문을 사용할 때, package와 module 을 대상으로만 사용해야 하고, 각각의 클래스나 함수에 대해 사용하면 안 됨.
- 다만, typing module을 사용할 때는 예외임.
2.2.1 정의
- 하나의 모듈에서 다른 모듈로 코드를 공유하는 재사용 매커니즘
2.2.2 장점
- 이름을 짓는 방식 (namespace management convention) 이 간단함.
- 각각 식별된 소스는 하나의 일관된 방식으로 작성됨.
- x.obj 는 모듈 x에 정의된 객체 obj 를 의미함.
2.2.3 단점
- 모듈 이름이 충돌할 수 있음.
- 몇몇 모듈 이름은 불편할 정도로 김.
2.2.4 결론
- import x를 패키지와 모듈을 import 할 때 사용.
- from x import y 를 x가 패키지의 접두어(prefix)이고, y가 접두어가 없을 때 (no prefix) 사용.
- 만약 y의 이름인 모듈이 두 개 import 되거나, y가 불필요하게 너무 긴 이름을 가졌다면 from x import y as z 사용.
- import y as z를 z가 공식적인 약어인 경우에만 사용. (e.g. np는 numpy의 약어)
- import와 관련된 이름은 사용하지 말 것.
- 모듈이 같은 패키지에 있더라도, 전체 패키지 이름 사용.
- 이는 패키지를 두 번 import 하는 것을 예방함.
- 다만, typing module 을 import 할 때는 이러한 규칙들에서 예외.
2.3 Packages
- 각각의 모듈은 그 모듈의 전체 경로 위치를 사용하여 import
2.3.1 장점
- module 이름의 충돌, 작성자가 예상하지 못한 module serach path로 인한 잘못된 import를 방지함.
- 모듈을 쉽게 찾을 수 있도록 함.
2.3.2 단점
- 패키지 계층을 복제해야 함으로, 코드를 배포하기 어려움. (다만, 현대적 배포 매커니즘에서는 문제될 게 없음.)
2.3.3 결론
- 모든 새로운 코드는 그 코드의 전체 패키지 이름으로 각각의 모듈을 import 해야 함.
- import는 아래 사항을 따라야 함.
YES:
# Reference absl.flags in code with the complete name (verbose).
import absl.flags
from doctor.who import jodie
FLAGS = absl.flags.FLAGS
YES:
# Reference flags in code with just the module name (common).
from absl import flags
from doctor.who import jodie
FLAGS = flags.FLAGS
NO:
# Unclear what module the author wanted and what will be imported. The actual
# import behavior depends on external factors controlling sys.path.
# Which possible jodie module did the author intend to import?
import jodie
- the directory the main binary is located in (메인 바이너리 디렉토리) 는 sys.path에 있다고 가정되면 안 됨.
2.4 Exceptions
- 예외는 허용하지만, 주의해서 사용해야 함.
2.4.1 정의
- 예외는 코드블록에서 정상적인 상황에 발생한 에러나 다른 예외적인 상황을 다루는 방법.
2.4.2 장점
- 일반적인 연산자에 대한 제어흐름은 에러 핸들링 코드에 의해 난잡해지지 않음.
- 특정 조건이 발생했을 때, 제어 흐름이 몇몇 프레임들을 생략할 수 있음.
2.4.3 단점
- 제어 흐름이 혼란스러워 질 수 있음.
- 라이브러리를 호출할 때, 에러 상황들을 놓치기 쉬움.
2.4.4 결론
- built-in exception class (내장 예외 클래스) 사용.
- assert는 올바른 사용이나, 예상치 못한 이벤트 발생을 나타내는 것이 아니라, 내부적 정확성을 보장하기 위해 사용됨. 만약 예외가 필요하다면 raise 문 사용.
- +추가) raise: 강제로 예외 발생
- +추가) assert: 가정설정문. 조건을 걸어서 거짓이 되면 AssertionError 발생.
- 공공 API에 있는 인수의 값을 검증하기 위해 assert 문을 사용하지 말 것.
raise 문 assert 문 비교
- 논리 오류를 해결하는 방법으로 raise와 assert가 있음. assert 문은 예외를 일으킨다는 점에서 raise 문과 비슷한 명령이나, 언제, 어떤 예외를 발생시키는지가 raise 문과 다름.
- raise 문은 오류를 이미 발견한 상황에서 예외를 발생시키기 위한 명령. 따라서 무조건 예외를 발생시키며, 지정한 예외를 발생시킴.
- assert 문은 상태를 검증하지 위한 명령. 지정한 검증식을 계산하여 결과가 참일 때는 아무 것도 하지 않고, 결과가 거짓일 때만 AssertionError 예외 발생.
- assert 문을 활용하면 논리 오류를 간단히 검사할 수 있음. 계산된 결과가 올바른지 검산할 때, 어떤 중요한 명령을 실행하기 전에 준비가 잘 갖춰졌는지 확인할 때 등, 무언가 '확인이 필요한' 상황에서 assert 문을 사용하면 좋음.
비교 | raise 문 | assert 문 |
용도 | 예외의 발생 | 상태의 검증 |
언제 예외를 일으키는가? | 항상 | 검증식이 거짓일 때만 |
어떤 예외를 일으키는가? | 지정한 예외 | AssertionError |
Yes:
def connect_to_next_port(self, minimum):
"""Connects to the next available port.
Args:
minimum: A port value greater or equal to 1024.
Returns:
The new minimum port.
Raises:
ConnectionError: If no available port is found.
"""
if minimum < 1024:
# Note that this raising of ValueError is not mentioned in the doc
# string's "Raises:" section because it is not appropriate to
# guarantee this specific behavioral reaction to API misuse.
raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
port = self._find_next_open_port(minimum)
if not port:
raise ConnectionError(
f'Could not connect to service on port {minimum} or higher.')
assert port >= minimum, (
f'Unexpected port {port} when minimum was {minimum}.')
return port
No:
def connect_to_next_port(self, minimum):
"""Connects to the next available port.
Args:
minimum: A port value greater or equal to 1024.
Returns:
The new minimum port.
"""
assert minimum >= 1024, 'Minimum port must be at least 1024.'
port = self._find_next_open_port(minimum)
assert port is not None
return port
- 라이브러리나 패키지는 고유의 예외가 정의되어 있을 것.
- 기존에 존재하는 예외 클래스 (exception class)로부터 상속을 받아야 함.
- 예외 이름은 Error 로 끝나야 하고, stutter (말더듬이) 로 시작하면 안 됨 (e.g. foo.fooError)/
- 예외를 다시 발생시키거나, 쓰레드의 가장 바깥 쪽 블록에 있지 않다면 포괄적인 except: 문을 사용하거나, Exception, StandardError를 사용하지 말 것.
- 파이썬은 이와 관련하여 매우 관용적이며, 실제로 포착을 원하지 않았던 다른 모든 종류의 예외까지 잡아낼 것.
- try/except 블록의 수를 최소화.
- try 문의 내부가 커질수록 당신이 예상하지 않았던 예외가 발생할 것임.
- 이러한 상황에서 try/except 블록은 실제로 찾아야 하는 real error를 가림.
- 예외가 try 블록에서 발생하던 안하던 finally 절은 코드를 실행시킴 (e.g. 파일을 닫을 때).
2.5 Golabl variables
- 전역 변수를 사용하지 않을 것.
2.5.1 정의
- 모듈이나 클래스 속성으로 선언된 변수.
2.5.2 장점
- 가끔 편함.
2.5.3 단점
- import 되는 동안 모듈의 동작이 변경될 수 있음.
- 전역 변수의 할당은 모듈을 처음 import 할 때, 수행이 되기 때문.
2.5.4 결론
- 전역 변수를 사용하지 말 것.
- 전역 변수는 technically 하게는 변수이지만, module-level 의 상수가 허용되고, 권장됨.
- e.g. MAX_HOLY_HANDGRENADE_COUNT = 3, 상수는 반드시 모든 공백에 _ 를 넣어서 이름 생성. (Naming 참조)
- 만약 전역 변수가 필요하다면 module-level 에서 선언되고, 모듈 내부에서 이름에 _ 를 붙여 만들어야 함.
- 외부 접근은 반드시 public 단위의 module-level 함수를 통해서 동작되어야 함. (Naming 참조)
2.6 Nested/Local/Inner Classes and Functions
- nested/local/inner 클래스나 함수는 지역 변수에 접근할 때 사용하면 좋음.
2.6.1 정의
- class 는 메서드, 함수, 클래스 내에서 정의 가능.
- 함수는 메서드, 함수 내에서 정의 가능.
- 중첩 함수 (Nested function)는 해당 scope에 정의된 변수를 읽기만 가능.
2.6.2 장점
- 제한된 스코프 내에서 사용하는 유틸리티 클래스와 함수의 정의를 허용함.
- 일반적으로 decorator(데코레이터)를 구현할 때 사용.
2.6.3 단점
- nested functions과 classes는 직접 테스트 할 수 없음.
- 중첩은 외부함수를 더 길고 읽기 어렵게 만듦.
2.6.4 결론
- 몇 가지 주의사항을 지키면 사용해도 괜찮음.
- local value에 접근할 때를 제외하고, 중첩함수나 중첩 클래스 사용은 피하는게 좋음.
- 함수를 모듈 사용자에게 숨기기 위해서 중첩하지 말 것. 대신, module level에서 이름 앞에 _ 를 붙여 테스트 가능하도록 할 수 있음.
2.7 Comprehensions & Generator Expressions
- +추가) Comprehension: iterable한 object를 생성하기 위한 방법 중 하나. 한 sequence가 다른 sequence (iterable object) 로부터 (변형되어) 구축될 수 있게 하는 기능. python2 에서는 list comprehension만 지원하며, python3 에서 dict, set comprehension을 추가로 지원.
- pythonstudy.xyz/python/article/22-Python-Comprehension
- +추가) 4가지 종류의 comprehension이 있음.
- List Comprehension (리스트 내포): 입력 sequence로부터 지정된 표현식에 따라 새로운 리스트 컬렉션을 빌드하는 것. 입력 sequence란, 입력으로 사용되는 iteration이 가능한 데이터 sequence 혹은 컬렉션.
- Set Comprehension: 입력 sequence로부터 지정된 표현식에 따라 새로운 Set 컬렉션을 빌드하는 것. List comprehension과 거의 비슷하지만, 결과가 Set {...} 으로 리턴된다는 점이 다름.
- Dict Comprehension: 입력 sequence로부터 지정된 표현식에 따라 새로운 dictionary 컬렉션을 빌드하는 것. set comprehension과 거의 비슷하지만, 출력 표현식이 Key:Value pair로 표현됨. 결과로 dict 리턴.
- Generator Comprehension
2.7.1 정의
- 오래전부터 사용되어 왔던 반복문, map(), filter(), lambda를 사용하지 않고도 container 타입과 iterator를 만들 때, 간결하고 효율적인 방법을 제공함.
2.7.2 장점
- 간단한 comprehension은 다른 딕셔너리나 리스트, set을 만드는 기술보다 명확하고 간단.
- 제너레이터 표현식은 리스트 전체를 만드는 것이 아니여서, 매우 효율적.
2.7.3 단점
- 복잡한 comprehension이나 제너레이터 표현식은 읽기 힘듦.
2.7.4 결론
- 복잡하지 않은 상황에서 사용할 것. 각각의 부분은 반드시 한 라인에서 끝나야 함. (e.g. map(), filter() 표현식)
- loop 문을 통해 코드를 단순화 할 수 있으면, 단순화.
Yes:
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')
No:
result = [complicated_transform(
x, some_argument=x+1)
for x in iterable if predicate(x)]
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
return ((x, y, z)
for x in range(5)
for y in range(5)
if x != y
for z in range(5)
if y != z)
2.8 Default Iterators and Operators
- +추가) operator module: 속도 처리가 아주 중요한 프로그램의 경우, 연산 로직을 짤 때, operator module 사용.
- 리스트, 딕셔너리, 파일 등의 타입에서 기본 반복자와 연산자를 지원하는 경우에 사용.
2.8.1 정의
- 딕셔너리나 리스트 같은 컨테이너 타입은 기본 반복자와 ('in', 'not in') 같은 멤버 연산자(membership test operators; 요소가 있는지 확인하는 연산자 인 듯)를 사용함.
2.8.2 장점
- 기본 반복자와 연산자는 간단하고 효율적. 추가적인 메소드를 호출하지 않고 연산자를 직접 표현.
- default operator를 사용하는 함수는 generic (포괄적)임. 연산자를 지원하는 어떠한 타입에서도 사용할 수 있음.
2.8.3 단점
- 메서드 이름을 읽어도 객체의 타입을 유추할 수 없음. 이것은 장점이 될 수도 있음.
2.8.4 결론
- 리스트와 딕셔너리, 파일과 같은 연산자를 지원해주는 타입에서 기본 반복자와 연산자를 사용할 것.
- 내장된 타입은 기본 반복자와 메서드도 정의하고 있음.
- built-in 타입은 iterator 메서드를 정의함.
- 컨테이너를 반복하는 동안 컨테이너를 변형시키지 않아야 한다는 점을 제외하고, 리스트를 반환하는 방법보다 이런 메서드를 선호 할 것.
- 필요한 경우가 아니면, 절대 python2 의 dict.iter8()과 같은 특정 메서드를 사용하지 말 것.
Yes: 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): ...
No: for key in adict.keys(): ...
if not adict.has_key(key): ...
for line in afile.readlines(): ...
for k, v in dict.iteritems(): ...
2.9 Generators
- 필요에 따라 제너레이터 사용.
2.9.1 정의
- 제너레이터 함수는 yield문을 실행할 때마다 값을 생성해주는 이터레이터를 반환함.
- 값을 계산한 다음, 제너레이터 함수의 runtime 상태는 다음 값이 필요해질 때까지 정지됨.
2.9.2 장점
- 지역변수의 상태와 제어 흐름은 각 호출을 통해 보존되기 때문에 코드가 단순함.
- 제너레이터의 사용은 전체 리스트의 값을 단 한번 생성하기 때문에 함수를 사용하는 것보다 메모리를 적게 사용함.
2.9.3 단점
- 없음.
2.9.4 결론
- 제너레이터 함수의 docstring에서 'returns:' 대신에 'yields:' 를 사용할 것.
2.10 Lambda Functions
- 한 줄로 작성.
2.10.1 정의
- 람다는 표현에 있어 다른 구문과는 다르게 익명 함수를 정의함.
- 람다는 map(), fileter()와 같은 higher-order functions(고차 함수)에 대해 콜백이나, 연산자를 정의하기 위해 가끔 사용됨.
2.10.2 장점
- 편리함.
2.10.3 단점
- 로컬 함수에 비해 읽기 어렵고 디버깅이 힘듦. 이름이 없다는 것은 stack trace를 이해하기 어렵다는 것.
- 람다 함수에는 오직 표현식만 담을 수 있어 표현이 제한됨.
2.10.4 결론
- 람다를 한 줄로 사용할 것. 만약 코드 내부에 있는 람다 함수가 60~80 글자라면, 일반적인 levical scoping으로 정의하는 것이 나음.
- 곱셈 같은 일반 연산자에서는 operator 모듈 대신에 람다 함수 사용.
2.11 Conditional Expressions
- 간단한 상황에 좋음.
2.11.1 정의
- Conditional Expressions (삼항 연산자)은 if 문을 더 짧은 구문으로 사용하는 방법.
2.11.2 장점
- if문보다 짧고 편리함.
2.11.3 단점
- if문보다 읽기가 어려움. 표현이 길어지면 조건을 찾기가 어려울 수 있음.
2.11.4 결론
- 간단한 상황에 좋음. 그 외의 경우에는 if문을 사용하는 것이 좋음.
2.12 Default Argument Values
- 대부분은 사용해도 됨.
2.12.1 정의
- 함수의 매개변수 목록 끝에 있는 변수에 대한 값을 지정할 수 있음.
- e.g. def foo(a, b=0): 이라는 함수의 경우 만약 foo()를 하나의 인자값으로 호출한다면 b의 값은 0으로 설정됨. 만약 두 개의 인자값이라면 b의 값은 두 번째 인자값을 가짐.
2.12.2 장점
- 기본값을 많이 가지는 함수는 있지만, 기본값을 재정의하는 경우는 거의 없음.
- 기본 인자값을 설정하면, 드문 예외에 대해 많은 함수를 정의할 필요 없이 쉽게 처리할 수 있음.
- python은 메서드/함수에 대해 overloading을 지원하지 않으므로, 기본 인자값을 사용하면 쉽게 overloading 을 사용하는 것처럼 할 수 있음.
2.12.3 단점
- 기본 인자값은 모듈이 불러올 때, 한번 계산됨. 만약 인자값이 list나 dictionary 같이 변형이 가능한 경우, object 문제를 일으킬 수 있음.
- 만약 함수가 object를 수정하는 경우 (list에 item을 추가하는 경우) 기본값이 수정됨.
2.12.4 결론
- 다음의 주의사항과 함께 사용될 수 있음.
- 함수 또는 메서드 정의할 때, 변할 수 있는 object를 기본값으로 사용하지 말 것.
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 = ()): # Empty tuple OK since tuples are immutable
...
No: def foo(a, b=[]):
...
No: def foo(a, b=time.time()): # The time the module was loaded???
...
No: def foo(a, b=FLAGS.my_thing): # sys.argv has not yet been parsed...
...
No: def foo(a, b: Mapping = {}): # Could still get passed to unchecked code
...
2.13 Properties
2.14 True/False Evaluations
- 대부분은 사용해도 됨.
2.14.1 정의
- 함수의 매개변수 목록 끝에 있는 변수에 대한 값을 지정할 수 있음.
- e.g. def foo(a, b=0): 이라는 함수의 경우 만약 foo()를 하나의 인자값으로 호출한다면 b의 값은 0으로 설정됨. 만약 두 개의 인자값이라면 b의 값은 두 번째 인자값을 가짐.
2.14.2 장점
- 기본값을 많이 가지는 함수는 있지만, 기본값을 재정의하는 경우는 거의 없음.
- 기본 인자값을 설정하면, 드문 예외에 대해 많은 함수를 정의할 필요 없이 쉽게 처리할 수 있음.
- python은 메서드/함수에 대해 overloading을 지원하지 않으므로, 기본 인자값을 사용하면 쉽게 overloading 을 사용하는 것처럼 할 수 있음.
2.14.3 단점
- 기본 인자값은 모듈이 불러올 때, 한번 계산됨. 만약 인자값이 list나 dictionary 같이 변형이 가능한 경우, object 문제를 일으킬 수 있음.
- 만약 함수가 object를 수정하는 경우 (list에 item을 추가하는 경우) 기본값이 수정됨.
2.14.4 결론
- 다음의 주의사항과 함께 사용될 수 있음.
- 함수 또는 메서드 정의할 때, 변할 수 있는 object를 기본값으로 사용하지 말 것.
2.15 Deprecated Language Features
- string 모듈 대신에 string 함수 사용.
- apply 사용하는 대신에 function call (함수 호출) 사용.
- 함수의 인자 값이 inlined lambda 일 때, filter와 map 대신에 list comprehensions와 for문 사용.
- reduce 대신에 for문 사용.
2.15.1 정의
- 현재 버전의 python은 사람들이 일반적으로 선호하는 대체 구문 제공.
2.15.2 결론
- 이러한 기능을 제공하지 않는 python 버전은 사용하지 않으므로, 새로운 스타일을 사용하지 않을 이유가 없음.
Yes: words = foo.split(':')
[x[1] for x in my_list if x[2] == 5]
map(math.sqrt, data) # Ok. No inlined lambda expression.
fn(*args, **kwargs)
No: words = string.split(foo, ':')
map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list))
apply(fn, args, kwargs)
2.16 Lexical Scoping
- 사용해도 좋음.
2.16.1 정의
- 중첩된 python 함수는 주변 함수에 정의된 변수를 참조할 수 있지만, 할당할 수 없음.
- 변수 바인딩은 렉시컬 스코핑(lexical scoping)을 통해 해결함. 즉, 정적 프로그램 텍스트를 기반으로 함.
- 블록 내에 할당된 모든 이름은 python이 해당 이름에 대한 모든 참조를 할당에 앞서 사용하더라도, 로컬 변수로 처리함. 망냑에 전역 변수가 발생하면, 그 이름은 전역 변수로 취급함.
2.16.2 장점
- 명확하고, 우아한 코드를 만듦.
- Lisp와 Scheme 개발자들에게 익숙함.
2.16.3 단점
- 혼란스러운 버그로 이어질 수 있음.
i = 4
def foo(x):
def bar():
print(i, end='')
# ...
# A bunch of code here
# ...
for i in x: # Ah, i *is* local to foo, so this is what bar sees
print(i, end='')
bar()
- foo([1,2,3]) 은 1,2,3,4 가 아니라 1,2,3,3 이 출력됨.
2.16.4 결론
- 사용해도 좋음.
2.17 Function and Method Decorators
2.18 Threading
- 내장된 타입 (built-in type)의 원자성에 의존하지 말 것.
- 딕셔너리와 같은 python의 내장된 타입은 원자 형태로 조작할 수 있지만, 그렇지 않은 경우도 있기 때문에 원자로 되어있다고 신뢰하면 안 됨. (e.g. __hash__, __eq__가 python으로 구현되는 경우)
- 원자 변수 할당에 의존해서는 안 됨. (결국, 딕셔너리에 달려있기 때문).
- 스레드 간 통신하는 데 있어 Queeu modeul의 Queue data type을 사용하라. 혹은 threading module이나, locking primitives를 사용할 것.
- lower-level locks를 사용하는 대신, condition variables와 threading.Condition을 사용할 것.
2.19 Power Features
- 해당 기능들을 피하라.
2.19.1 정의
- python은 매우 유연한 언어로서, 많은 화려한 기능을 줌.
- 사용자 정의 metaclasses, bytecode 접근, 즉각적인 컴파일, 동적 상속, object reparenting, import hacks, reflection, 시스템 내부 수정 등.
2.19.2 장점
- 강력한 언어 기능으로, 코드를 더 짧게 만듦.
2.19.3 단점
- 읽거나, 이해하거나 디버그하는데 어려움.
- 처음에는 그렇게 보이지 않지만, 코드를 다시 보면 간단하지만, 긴 코드보다 어려운 경항이 있음.
2.19.4 결론
- 코드에서 이러한 기능을 피하라.
- 이러한 기능을 내부적으로 사용하는 표준 라이브러리 모듈과 클래스는 사용할 수 있음.
- abc.ABCMeta, collections.namedtuple, dataclasses, enum
2.20 Modern Python: Python 3 and from __future__ imports
- Python3 버전이 나옴. 모든 프로젝트가 python3을 사용할 준비가 되어있는 건 아니지만, 모든 코드는 호환되도록 작성되어야 함.
2.20.1 정의
- python3는 python 언어에서 중요한 변화가 있음.
- python3에서 수정없이 사용할 수 있도록, 잘 준비하기 위해서 코드의 의도를 명확하게 만들 수 있게 하는 몇몇 간단한 것들이 있음.
2.20.2 장점
- python3를 염두해 두고 작성된 코드는 명확하고 프로젝트의 모든 의존성이 python3에서 실행하기가 더 쉬워짐.
2.20.3 단점
- 어떤 사람들은 추가된 boilderplate가 별로라고 생각함. 사용하지 않는 기능을 import 하는 것은 이례적임.
2.20.4 결론
from __future__ import
- from __future__ import 형태를 사용하는 것이 바람직함. 모든 새로운 코드는 다음 사항이 포함되어야 하며, 가능한 경우 기존 코드가 호환되도록 업데이트 해야 함.
- 이러한 import는 현재 모듈에서 사용되지 않더라도 생략하거나 제거하지 말 것. 모든 파일에 항상 향후 import가 있으므로, 나중에 이러한 기능을 사용하기 시작할 때 편집하는 동안 잊지 않도록 하는 것이 좋음.
- 다른 from __future__ import 명세도 있으니, 알맞게 사용할 것. unicode_literals는 파이썬 2.7 내 여러 곳에서 도입되는 암묵적 기본 코텍 변환 결과 때문에 명확하지 않아, 권고사항에 포함하지 않음. 대부분의 코드는 필요에 따라 b'', u'' 바이트를 명시적으로 사용하고 유니코드 문자열 literal를 사용하면 더 좋음.
six, future, and past libraries
- 프로젝트가 python 2,3 모두 지원해야 하는 경우, 리으버리를 적합하게 사용할 것을 권장. 코드를 더 깨끗하고 쉽게 만들기 위해 존재.
2.21 Type Annotated Code
- python3에서 타입의 정보를 PEP-484를 참고해서 주석으로 달 수 있음. 빌드 할 때, pytype 같은 타입 검사도구를 사용할 것.
- Type에 대한 주석은 소스 안이나 stub pyi 파일에 있을 수 있음. 가능하면 주석은 소스안에 있어야 함.
2.21.1 정의
- Type annotation (or 'type hints')는 함수나 메서드의 인자값, 반환값임.
def func(a: int) -> List[int]:
- 또한 PEP-526 처럼 변수의 type을 선언할 때 사용.
a: SomeType = some_func()
- legacy python version을 지원해야 한다면, 코드에 type 설명을 추가함.
a = some_func() # type: SomeType
2.21.2 장점
- type에 대한 주석은 코드의 가독성과 유지관리에 도움을 줌. Type 검사기는 많은 런타임 오류를 빌드 타임 오류로 바꿔주고, 강력한 기능들의 사용을 줄여줌. (2.19)
2.21.3 단점
- Type의 명세를 최신으로 유지해야 함. Type 에러가 발생하지도 모름. Type 검사기를 사용하면, 강력한 기능들을 (2.19) 활용하는 능력이 떨어질 수 있음.
2.21.4 결론
- 코드를 업데이트 할 때, python type analysis를 사용하는 것은 권장될 수 있음. 이는 프로젝트의 복잡성에 크게 좌우됨. 일단 한 번 해 볼 것.
3. Python Style Rules
3.1 Semicolons
- 세미클론을 이용해서 문장을 끝내거나, 한 줄에 2개의 구문을 작성하지 말 것.
3.2 Line length
- 최대 줄 길이는 80자.
- 예외
- 긴 import 구문
- URL, 경로이름, 주석의 긴 플래그
- 경로이름이나 URLs와 같은 공백을 포함하지 않는 긴 모듈수준의 문자상수, 즉, 여러 줄에 나누어 기록하기 불편한 경우.
- Pylint의 비활성화 주석 (e.g. #pylint: disable=invalid-name)
- 예외
- 백슬래쉬를 이용한 문장연장을 사용하지 말 것.
- 3개 이상의 컨텐스트 매니저를 요구하는 with 구문 제외.
- Python의 소/중/대 괄호 내부의 묵시적 라인결합을 사용.
- 필요하다면, 구문 양쪽에 추가로 괄호를 더할 수 있음.
Yes: foo_bar(self, width, height, color='black', design=None, x='foo',
emphasis=None, highlight=0)
if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong'):
- 만약 리터럴 문자열을 한 줄에 표현하기 어렵다면, 아래와 같이 괄호를 이용하여 묵시적 라인결합 사용.
x = ('This will build a very long long '
'long long long long long long string')
- 주석의 경우, 긴 URLs은 한줄에 표현.
Yes: # See details at
# http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
No: # See details at
# http://www.example.com/us/developer/documentation/api/content/\
# v2.0/csv_file_name_extension_full_specification.html
- 3줄 이상이 필요한 with 구문을 정의할 때는 백슬래쉬를 이용한 문장 연장 허용. 2줄인 경우 nested with 구문 사용.
Yes: 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)
No: with VeryLongFirstExpressionFunction() as spam, \
VeryLongSecondExpressionFunction() as beans:
PlaceOrder(eggs, beans, spam, beans)
Yes: with very_long_first_expression_function() as spam:
with very_long_second_expression_function() as beans:
place_order(beans, spam)
3.3 Parentheses
- 괄호는 적게 사용할 것.
- 필수는 아니지만, 튜플의 양쪽에 괄호를 사용하여도 무방.
- 묵시적 문장연장이나, 튜플을 나타내기 위한 상황을 제외하고, 리턴문이나 조건문에는 사용하지 말 것.
Yes: if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar()
# For a 1 item tuple the ()s are more visually obvious than the comma.
onesie = (foo,)
return foo
return spam, beans
return (spam, beans)
for (x, y) in dict.items(): ...
No: if (x):
bar()
if not(x):
bar()
return (foo)
3.4 Indentation
- 코드를 작성할 때, 4칸 들여쓰기를 할 것.
- 탭 사용 금지, 탭과 스페이스를 섞어서 사용하지 말 것.
- 묵시적 문장 연장의 경우,
- 동일한 문장에 포함된 요소들을 수직정렬하거나,
- 첫 열린괄호 이후로는 아무것도 없는 4칸 hanging indent를 적용해야 함. (2칸 안됨)
Yes: # Aligned with opening delimiter
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)
# Aligned with opening delimiter in a dictionary
foo = {
long_dictionary_key: value1 +
value2,
...
}
# 4-space hanging indent; nothing on first line
foo = long_function_name(
var_one, var_two, var_three,
var_four)
meal = (
spam,
beans)
# 4-space hanging indent in a dictionary
foo = {
long_dictionary_key:
long_dictionary_value,
...
}
No: # Stuff on first line forbidden
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)
# 2-space hanging indent forbidden
foo = long_function_name(
var_one, var_two, var_three,
var_four)
# No hanging indent in a dictionary
foo = {
long_dictionary_key:
long_dictionary_value,
...
}
3.4.1 Trailing commas in sequences of items?
- 여러 원소를 나열할 때, 후행 쉼표 (trailing commas)는 ), }, ] 와 같이 컨테이너를 닫는 토큰이 마지막 원소가 같은 줄에 있지 않을 때만 권장.
- 또한 후행 쉼표는 python code auto-formatter (YAPF)가 컨테이너의 원소를 한 줄에 하나씩 , 기호를 붙여 자동 정렬하도록 지시하는 힌트로도 사용됨.
Yes: golomb3 = [0, 1, 3]
Yes: golomb4 = [
0,
1,
4,
6,
]
No: golomb4 = [
0,
1,
4,
6
]
3.5 Blank Lines
- 2 blank lines: 최상위 선언문과 함수, 객체 선언 사이는 2개의 빈 줄.
- 1 blank line: class와 첫 번째 method 선언 사이는 1개의 빈 줄.
- None blank line: def 줄 이후에는 빈 줄이 없어야 함.
- 함수, 혹은 메서드 간들 사이에는 개발자의 판단하에 적절하게 한 개의 빈 줄을 사용할 것.
3.6 Whitespace
- 표준 조판 규칙을 따라 구두점 주변에 스페이스를 사용할 것.
- (, }, ] 내부에는 화이트 스페이스가 없어야 함.
Yes: spam(ham[1], {eggs: 2}, [])
No: spam( ham[ 1 ], { eggs: 2 }, [ ] )
- 콤마, 세미콜론, 콜론 앞에는 화이트 스페이스가 없어야 함.
- 문장 끝을 제외하고, 콤마, 세미콜론, 콜론 뒤에 화이트 스페이스를 사용할 것.
Yes: if x == 4:
print(x, y)
x, y = y, x
No: if x == 4 :
print(x , y)
x , y = y , x
- 매개변수 목록, 인덱싱 슬라이싱의 시작에 사용된 (, { 앞에는 화이트 스페이스를 사용하지 말 것.
Yes: spam(1)
Yes: dict['key'] = list[index]
No: spam (1)
No: dict ['key'] = list [index]
- 대입 연산자 (=), 비교 연산자 (==, <, >, !=, <>, <=, >=, in, not in, is, is not) , 불린 (and, or, not) 과 같은 바이너리 연산자는 앞, 뒤로 한 칸 띄울 것.
- 수리 연산자 (+, -, *, /, //, %, **, @) 앞, 뒤의 공백은 개발자의 판단에 따라 사용.
Yes: x == 1
No: x<1
- keyword arguments나 default parameter value를 지정하는 경우 = 기호 앞뒤로 공백을 사용하지 말 것.
- 예외) Type annotaiton (Type 지정) 이 존재할 때는 제외.
Yes: def complex(real, imag=0.0): return Magic(r=real, i=imag)
Yes: def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)
No: def complex(real, imag = 0.0): return Magic(r = real, i = imag)
No: def complex(real, imag: float=0.0): return Magic(r = real, i = imag)
- 공백을 이용하여 연속된 여러 줄을 수직정렬하지 말 것 (:, #. = 등에 적용)
Yes:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned
dictionary = {
'foo': 1,
'long_name': 2,
}
No:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned
dictionary = {
'foo' : 1,
'long_name': 2,
}
3.7 Shebang Line
- +추가) shebang (셔뱅): 해시 기호와 느낌표로 이루어진 문자 시퀀스.
- +추가) Unix shell script를 만들 때, 파일의 첫 줄에 #!/bin/sh 처럼 넣는 것.
- 대부분의 .py 파일은 #!로 시작하지 않아도 됨.
- 프로그램의 메인 파일을 #!/usr/bin/python과 PEP-394에 따른 2 또는 3을 붙여 시작하면 됨.
- 이 줄은 파이썬 파일을 import 할 때는 무시되지만, 실행 될 때는 커널이 어떤 파이썬 인터프리터를 사용해야 하는지 알려줌.
- 따라서 직접 실행될 파일에 기록하는 것이 적합함.
3.8 Comments and Docstrings
- 모듈, 함수, 메서드에 올바른 형식의 docstring과 인라인 주석을 사용.
3.8.1 Docstrings
- 파이썬은 코드를 문서화 할 때 docstring을 사용함.
- docstring은 패키지, 모듈, 클래스나 함수의 첫 번째에 선언되는 문자열.
- 해당 문자열은 pydoc이 사용하는 __doc__ 멤버 오브젝트에서 자동으로 추출될 수 있음.
- PEP257에 따라, docstring을 시작하거나 끝낼 때는 """ 사용.
- docstring은 마침표, 물음표, 느낌표로 끝나는 한 줄 (요약줄)로 시작하여야 하며,
- 공백을 두고, 내용을 담고있는 나머지 docstring이 이어져야 함.
- 또한, 내용을 담고 있는 docstring은 """와 같은 커서위치에서 시작하여야 함.
3.8.2 Modules
- 모든 파일은 license boilerplate를 가지고 있어야 함.
- 프로젝트에 알맞는 라이센스 보일러 플레이트를 선택 (e.g. Apache 2.0, BSD, LGPL, GPL)
- 파일은 docstring에서 모듈의 사용과 컨텐츠에 대한 설명으로 시작해야 함.
- a one line summary of the module or program, terminated by a period.
"""A one line summary of the module or program, terminated by a period.
Leave one blank line. The rest of this docstring should contain an
overall description of the module or program. Optionally, it may also
contain a brief description of exported classes and functions and/or usage
examples.
Typical usage example:
foo = ClassFoo()
bar = foo.FunctionBar()
"""
3.8.3 Functions and Methods
- 이 섹션에서 "function" 은 method, function, generator을 의미함.
- 아래의 조건을 만족하지 않는 이상, 함수는 반드시 docstring을 가지고 있어야 함.
- 외부에서 보이지 않음.
- 매우 짧음.
- 잘 알려져 있음.
- docstring은 직접 함수의 코드를 읽어보지 않더라도, 충분히 함수를 호출하는 코드를 작성할 수 있을만큼 정보를 제공해야 함.
- docstring은 설명조를 사용하여야 하며, 명령조를 사용하면 안 됨.
- docstring은 함수의 구현방식이 아닌, 호출 방법과 의미를 기술해야 함.
- 복잡한 코드의 경우 docstring을 사용하는 것보다, 코드 한 줄마다 주석을 첨가하는 것이 더 알맞음.
- 다른 기본 객체의 메서드를 오버라이드 (override) 하는 메서드는 """see base class.""" 처럼 개발자가 작성한 docstring에 오버라이드된 메서드가 있음을 알려주는 docstring을 전달할 수 있음.
- 이는, 같은 문서를 여러 곳에서 반복하는 것을 방지하기 위함.
- 그러나 오버라이딩된 메서드가 기존의 메서드와 확연하게 다른 동작방식을 가지고 있거나 세부적인 내용이 존재한다면 적어도 그러한 차이점들은 docstring을 통해 기록되어야 함.
- 이는, 같은 문서를 여러 곳에서 반복하는 것을 방지하기 위함.
- 함수의 몇가지 특정 부분은 별도의 특별 섹션으로 기록해야 함.
- 각 섹션은 표제로 시작하며, 콜론으로 끝맺음.
- 각 섹션은 표제를 제외하고 2칸 들여쓰기를 함.
Args:
- 매개변수를 각각 이름으로 나열. 각 이름에는 설명문이 따르며, 콜론 뒤에 공백이나, 새로운 라인으로 분리됨.
- 만약 설명문이 너무 길어 한 줄인 80자를 초과할 경우, 매개변수 이름보다 2칸 또는 4칸의 들여쓰기를 사용함.
- 만약 코드가 자료형에 대한 주석을 담고 있지 않다면, 설명문은 요구되는 자료형을 포함해서 기록해야 함.
- 함수가 *foo(가변길이의 매개변수 리스트), **bar(임의의 키워드 매개변수)를 받는다면, *foo와 **bar로 기록되어야 함.
Returns: (or Yiedlds: for generatros)
- 반환값의 자료형과 의미를 기록함. 만약 함수가 None만을 반환한다면, 이 섹션은 필요 없음.
- 만약 docstring이 returns나 yields로 시작하거나, 충분한 설명이 제공된다면 생략될 수 있음.
Raises:
- interface와 관련된 모든 예외를 설명 뒤에 나열.
- Args:에서 설명한 것과 유사하게, 예외이름 + : + 공백 또는 줄 바꿈과 hanging indent 스타일 이용.
- 명시된 API가 docstring을 위반했을 경우, 예외를 문서화하지 않음.
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Fetches rows from a Smalltable.
Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle. String keys will be UTF-8 encoded.
Args:
table_handle: An open smalltable.Table instance.
keys: A sequence of strings representing the key of each table
row to fetch. String keys will be UTF-8 encoded.
require_all_keys: Optional; If require_all_keys is True only
rows with values set for all keys will be returned.
Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
Returned keys are always bytes. If a key from the keys argument is
missing from the dictionary, then that row was not found in the
table (and require_all_keys must have been False).
Raises:
IOError: An error occurred accessing the smalltable.
"""
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Fetches rows from a Smalltable.
Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle. String keys will be UTF-8 encoded.
Args:
table_handle:
An open smalltable.Table instance.
keys:
A sequence of strings representing the key of each table row to
fetch. String keys will be UTF-8 encoded.
require_all_keys:
Optional; If require_all_keys is True only rows with values set
for all keys will be returned.
Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
Returned keys are always bytes. If a key from the keys argument is
missing from the dictionary, then that row was not found in the
table (and require_all_keys must have been False).
Raises:
IOError: An error occurred accessing the smalltable.
"""
3.8.4 Classes
- 클래스는 선언 바로 아래에 해당 클래스를 설명하는 docstring을 가지고 있어야 함.
- 만약 클래스가 public attributes를 가지고 있다면, (3.8.3의) Args: 와 같은 형식을 사용해 Attributes 섹션을 작성해야 함.
class SampleClass:
"""Summary of class here.
Longer class information....
Longer class information....
Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""
def __init__(self, likes_spam=False):
"""Inits SampleClass with blah."""
self.likes_spam = likes_spam
self.eggs = 0
def public_method(self):
"""Performs operation blah."""
3.8.5 Block and Inline Comments
- 코드의 복잡한 부분에는 주석을 다는 것을 피해야 함 (The final place to have comments is in tricky parts of the code).
- 만약, 추구 code review에서 코드를 설명하려고 한다면, 지금 주석을 달앋어야 함.
- 복잡한 동작은 시작하기 전에 몇 줄의 주석을 달아야 함.
- 잘 알려져 있지 않은 부분은 끝에 주석을 달아야 함.
# We use a weighted dictionary search to find out where i is in
# the array. We extrapolate position based on the largest num
# in the array and the array size and then do binary search to
# get the exact number.
if i & (i-1) == 0: # True if i is 0 or a power of 2.
- 가독성 향상을 위해, 이러한 주석들은 코드에서 최소 2줄 떨어져 있어야 함.
- 코드 자체를 설명하지는 말 것.
- 코드를 읽고 있는 사람이 작성자보다 파이썬을 더 잘 알고 있다고 가정할 것.
# BAD COMMENT: Now go through the b array and make sure whenever i occurs
# the next element is i+1
3.8.6 Punctuation, Spelling, and Grammar
- 스펠링과 문법, 구두점에 주의할 것. 잘 써진 주석이 읽기도 편함.
- 주석은 마치 말하는 것처럼 자연스럽게 읽을 수 있어야 하며, 영문 주석의 경우 올바른 대문자와 구두점 필요.
- 대부분의 경우, 조각난 문장보다 온전한 문장이 높은 가독성을 지님.
- 코드 끝에 붙는 짧은 주석 등의 경우, 다소 형식적이지 않아도 되지만, 전체적인 일관성을 맞춰야 함.
- 소스코드가 높은 수준의 명료성과 가독성을 가지는 것은 매우 중요함.
- 코드 리뷰어가 세미콜론이 사용되어야 하는데, 콤마를 사용했다고 지정하는 것을 중요하게 생각할 것.
- 올바른 구두점, 스펠링, 문법은 좋은 코드를 작성할 수 있도록 도와줌.
3.9 Classes
- 클래스는 object를 명시적으로 상속할 필요가 없음.
- 다만, python2와 호환되는 경우는 제외함.
Modern:
class SampleClass:
pass
class OuterClass:
class InnerClass:
pass
Ancient:
class SampleClass(object):
pass
class OuterClass(object):
class InnerClass(object):
pass
3.10 Strings
- 매개변수가 모두 문자열인 경우에도 format 메소드나 % 연산자를 사용하여 formatting 할 것.
- 개발자의 판단에 따라 +, % 선택.
Yes: 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+
No: x = '%s%s' % (a, b) # use + in this case
x = '{}{}'.format(a, b) # use + in this case
x = first + ', ' + second
x = 'name: ' + name + '; score: ' + str(n)
- 반복문에서 + , += 연산자를 사용하여 문자열을 누적하는 행위는 삼갈 것.
- 문자열은 변형이 불가하기 때문에 위와 같은 연산자를 사용하는 것은 불필요한 임시 오브젝트를 생성하게 되고, 결국 실행시간이 제곱형태의 실행시간이 소요됨.
- 위의 연산자 대신 리스트에 문자열을 넣고 반복문이 종료되면, ''.join 을 사용하는 것이 더 바람직함.
- 또는 각각의 문자열을 io.BytesIO 버퍼에 기록하는 것도 방법.
Yes: 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)
No: employee_table = '<table>'
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'
- 하나의 파일에는 따옴표를 일관되게 사용할 것. ' 또는 "를 하나를 선택하고 그것만 사용.
- 다만 역슬래쉬 사용을 피하기 위해 같은 파일이더라도 다른 따옴표를 사용하는 것은 괜찮음. gpylint가 이를 검사함.
Yes:
Python('Why are you hiding your eyes?')
Gollum("I'm scared of lint errors.")
Narrator('"Good!" thought a happy Python reviewer.')
No:
Python("Why are you hiding your eyes?")
Gollum('The lint. It burns. It burns us.')
Gollum("Always the great lint. Watching. Watching.")
- 다수의 문장을 이용할 때는 ''' 보다는 """을 사용할 것.
- 프로젝트에 따라 docstring이 아닌 여러 줄의 문자열을 ''' 을 이용하여 작성할 수 있음.
- docstring은 상황과 무관하게 """ 사용.
- 여러 줄의 문자열은 나머지 코드의 들여쓰기와 잘 호환되지 않기 때문에, 종종 묵시적인 라인결합 방식을 사용하는 것이 전체적으로 더 깔끔해 보인다는 점을 알아두기.
- string 내부의 extra 띄어쓰기를 피하기 위해
- 연결된 single-line string을 사용하거나,
- textwrap.dedent()를 사용하여, mult-line string을 사용하여 각 라인의 initial space를 제거할 수 있음.
No:
long_string = """This is pretty ugly.
Don't do this.
"""
Yes:
long_string = """This is fine if your use case can accept
extraneous leading spaces."""
Yes:
long_string = ("And this is fine if you cannot accept\n" +
"extraneous leading spaces.")
Yes:
long_string = ("And this too is fine if you cannot accept\n"
"extraneous leading spaces.")
Yes:
import textwrap
long_string = textwrap.dedent("""\
This is also fine, because textwrap.dedent()
will collapse common leading spaces in each line.""")
3.11 Files and Sockets
- 파일과 소켓은 사용이 끝나면, 명시적으로 연결을 종료 할 것.
- 파일이나 소켓과 같은 file-like object를 불필요하게 열어두는 것은 다음과 같은 단점이 있음.
- file descriptors와 같은 제한된 시스템 자원을 소모함.
- 파일을 열어둔 채로 방치하는 것은 파일의 이동이나 제거가 불가능 할 수도 있음.
- 공유되는 파일이나 소켓의 경우, 이용 종료 후에 다른 프로그램에 의해 의도치 않게 읽어지거나 쓰여질 수 있음.
- 파일이나 소켓은 객체가 소멸될 때, 자동으로 닫혀지는 것은 맞으나 객체의 수명주기를 파일의 상태에 구속하는 것은 나쁜 습관.
- 파일이나 소켓과 같은 file-like object를 불필요하게 열어두는 것은 다음과 같은 단점이 있음.
- 가장 선호되는 파일관리 방식은 with 구문.
with open("hello.txt") as hello_file:
for line in hello_file:
print(line)
- with 구문을 지원하지 않는 file-like 객체는 contestlib.closing() 사용.
import contextlib
with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
for line in front_page:
print(line)
3.12 TODO Comments
- 임시 코드, 혹은 완벽하지 않은 코드의 경우 TODO 주석 사용.
- TODO 주석은 대문자로 되어있는 TODO 문구로 시작하며, 해당 코드에 대한 가장 높은 이해도를 가진 인물의 이름, 이메일 주소 또는 다른 신원구분 문구를 괄호안에 포함해야 함.
- 이는 뒤에 무엇을 해야하는지에 대한 내용을 추가함.
- 추후 필요한 세부정보를 검색할 수 있어야 함.
# TODO(kl@gmail.com): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.
3.13 Imports formatting
- import는 개별적인 라인에 두어야 함.
- 예외) typing imports에 대한 예외가 있음.
Yes: import os
import sys
from typing import Mapping, Sequence
No: import os, sys
- import는 모듈의 주석과 docstring 바로 다음, 모듈 전역 및 상수 바로 앞 파일의 맨 위에 배치됨.
- Python future import statements
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
- Python standard library imports
import sys
- third-party module or package imports (모듈이나 패키지의 import)
import tensorflow as tf
- Code repository sub-package imports (서브 패키지의 import)
from otherproject.ai import mind
- 각각의 grouping에서 import 는 사전 순으로 정렬되어야 함.
- 예외) 각각의 모듈 전체 패키지 경로를 따랐을 경우는 (from path import ...) 예외.
import collections
import queue
import sys
from absl import app
from absl import flags
import bs4
import cryptography
import tensorflow as tf
from book.genres import scifi
from myproject.backend import huxley
from myproject.backend.hgwells import time_machine
from myproject.backend.state_machine import main_loop
from otherproject.ai import body
from otherproject.ai import mind
from otherproject.ai import soul
# Older style code may have these imports down here instead:
#from myproject.backend.hgwells import time_machine
#from myproject.backend.state_machine import main_loop
3.14 Statements
+추가) statements: 실행 가능한 (executable) 최소의 독립적인 실행 조각.
- 일반적으로 한 라인에는 오직 하나의 statement만 있어야 함.
- 예외) 테스트에 관한 statement 전체가 한 라인에 들어간다면, 테스트 결과를 같은 줄에 둘 수 있음.
- e.g. if 문에 else가 있지 않는 경우에 사용 가능.
- try/except 에서 try와 except를 같은 라인에 둘 수 없음.
Yes:
if foo: bar(foo)
No:
if foo: bar(foo)
else: baz(foo)
try: bar(foo)
except ValueError: baz(foo)
try:
bar(foo)
except ValueError: baz(foo)
3.15 Accessors (접근 제어)
3.16 Naming
- 함수 이름, 변수 이름, 파일 이름은 descriptive (설명적) 이어야 하고, 약어를 사용하지 말 것.
- 모호하거나, 익숙하지 않은 약어를 사용하지 않으며, 절대 단어에서 글자를 지워 줄이지 말 것.
- 항상 .py 파일 이름 확장자를 사용하고 - (대시)를 사용하지 말 것.
module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name.
3.16.1 Names to Avoid (피해야 할 이름)
- 아래와 같은 특별한 경우를 제외하고는 단일 글자는 피할 것.
- 특별한 경우
- counters나, iterators에서 사용할 때 (e.g. i, j, k, v 등)
- try/except 문에서 예외 식별자로 e를 사용할 때
- with 구문에서 file handle로 f를 사용할 때
- 특별한 경우
- __double_leading_and_trailing_underscore__ names (파이썬 예약어)
- 불쾌한 용어
3.16.2 Naming Conventions (네이밍 관습)
- CapWords (CamelCase) 사용.
- 다만, 모듈 이름은 lower_with_under.py 으로 함. 이는 모듈의 이름과 모듈 내부의 class 이름이 같을 경우 혼란을 피하기 위함.
3.16.3 File Naming (파일 네이밍)
- 파이썬 파일이름은 반드시 .py 확장자를 가지고 (-) 를 가지고 있으면 안 됨.
- 만약 확장자 없이 실행 파일에 접근하려면, exec, '$0.py", "S@"가 들어 있는 심볼 링크가 간단한 bash wrapper를 사용 할 것.
3.16.4 Guido의 권고에 따른 가이드라인
타입 | Public | Internal |
패키지 | lower_with_under | |
모듈 | lower_with_under | _lower_with_under |
클래스 | CapWords | _CapWords |
예외 | CapWords | |
함수 | lower_with_under() | _lower_with_under() |
글로벌/클래스 상수 | CAPS_WITH_UNDER | _CAPS_WITH_UNDER |
글로벌/클래스 변수 | lower_with_under | _lower_with_under |
인스턴스 변수 | lower_with_under | _lower_with_under |
메서드 이름 | lower_with_under() | _lower_with_under() (protected) |
함수/메서드 매개변수 | lower_with_under | |
지역 변수 | lower_with_under |
3.17 Main
- 실행 파일로 사용되도록 의도된 파일도 가져올 수 있어야 하며, 단순히 import 를 해도, 프로그램의 main 함수의 기능을 실행하는 일은 없어야 함.
- 메인은 기능적으로 main() 함수 안에 있어야 함.
- 파이썬에서 pydoc과 유닛 테스트는 모듈을 import 할 수 있어야 함.
- 메인 프로그램이 모듈을 import할 때, 실행되지 않도록 메인 프로그램을 실행시키기 전에, if __name__ == '__main__' 확인해야 함.
- absl을 사용할 때, app.run 사용. 혹은 아래와 같이 사용.
from absl import app
...
def main(argv):
# process non-flag arguments
...
if __name__ == '__main__':
app.run(main)
def main():
...
if __name__ == '__main__':
main()
3.18 Function length
- 함수의 길이가 적고, 필수기능으로만 작성된 함수를 선호할 것.
- 다만, 길이가 긴 함수가 필요할 수도 있으니, 함수 길이에 대한 제한은 없음.
- 만약 함수의 길이가 40줄을 넘어가면 프로그램의 구조에 피해가 안가면서 줄일 수 있는지 확인할 것.
- 작성한 함수를 짧고 간단하게 하여, 다른 사람들이 읽고 코드를 수정하기 쉽게 만들 것.
3.19 Type Annotatoins
+추가) python은 변수의 타입이 언제든지 바뀔 수 있는 동적 타입 언어임. 이러한 동적 타입 언어는 type 체크가 어렵다는 문제가 있고, 파이썬은 이러한 문제를 해결하기 위해 Type annotation 기능과 typing 이라는 내장 패키지를 제공함.
이들은 동적 타입인 파이썬을 정적 타입으로 바꿔주는 것은 아니고, 변수, 함수 파라미터 반환값이 어떤 타입인지 코드 상에서 명시하고, 에디터 레벨에서 경고하는 역할을 함.
3.19.1 General Rules
- PEP-484에 익숙해질 것.
- 메서드에서 self, cls 는 Type의 정보가 필요한 경우에서만 주석을 달아야 함.
- e.g. @classmethod def create(cls: Type[T]) -> T: return cls()
- 모든 변수나 반환되는 Type 이 정해지지 않았다면 Any를 사용할 것.
- 모듈에서 모든 함수에 주석을 달 필요는 없음.
- Public API에는 최소한의 주석
- 안전성과 명확성, 유연성 사이의 균형을 잘 잡아야 함.
- Type 관련 오류 (type-related errors)가 발생하기 쉬운 코드에 주석
- 이해하기 어려운 코드에 주석 달기.
- Type 의 관점에서 코드가 안정화되면 주석을 달기.
3.19.2 Line Breaking
- 기존의 indentation rules 를 따를 것.
- 주석 처리 후, 많은 함수는 '한 줄에 하나의 파라미터'가 될 것.
def my_method(self,
first_var: int,
second_var: Foo,
third_var: Optional[Bar]) -> int:
- 만약 한 줄에 입력된다면 한 줄에 입력하는 것도 괜찮음.
def my_method(self, first_var: int) -> int:
- 함수 이름, 마지막 매개 변수 및 리턴 type의 조합이 너무 길면 새 행에서 4만큼 들여쓰기.
def my_method(
self, first_var: int) -> Tuple[MyLongType1, MyLongType1]:
...
- return type이 마지막 파라미터와 같은 줄이 아닐 때, 파라미터를 4만큼 새 라인에 들여쓰고 닫는 괄호를 def와 정렬하는 방법이 선호됨.
Yes:
def my_method(
self, other_arg: Optional[MyLongType]
) -> Dict[OtherLongType, MyLongType]:
...
- pylint 를 사용하면 닫는 괄호를 새 줄로 이동하여, 여는 줄과 맞출수는 있지만 읽기 어려워 추천하지 않음.
No:
def my_method(self,
other_arg: Optional[MyLongType]
) -> Dict[OtherLongType, MyLongType]:
...
- Type은 깨지 않는 것을 선호하지만, 너무 긴 이름의 경우 한 줄에 담을 수 없는 경우, sub-type을 끊어지지 않도록 노력하고, alias(별칭) 사용을 고려할 것.
def my_method(
self,
first_var: Tuple[List[MyLongType1],
List[MyLongType2]],
second_var: List[Dict[
MyLongType3, MyLongType4]]) -> None:
...
- 최후의 수단은 다음 라인에 4칸 들여쓰기 하는 것.
Yes:
def my_function(
long_variable_name:
long_module_name.LongTypeName,
) -> None:
...
No:
def my_function(
long_variable_name: long_module_name.
LongTypeName,
) -> None:
...
3.19.3 Forward Declarations
- 아직 정의되지 않은 동일한 모듈의 클래스 이름을 사용해야 하는 경우, 클래스 이름을 '문자열'로 사용할 것.
- e.g. 클래스 선언 내에 클래스가 필요한 경우 또는 아래에 정의된 클래스를 사용하는 경우
class MyClass:
def __init__(self,
stack: List["MyClass"]) -> None:
3.19.4 Default Values
- PEP-008에 따라 Type annotation과 default value가 있는 경우에는 = 주위에 공백 사용.
Yes:
def func(a: int = 0) -> int:
...
No:
def func(a:int=0) -> int:
...
3.19.5 NoneType
3.19.6 Type Aliases
3.19.7 Ignoring Types
- #type: ignore 주석으로 type 검사를 사용하지 않도록 설정할 수 있음. (비활성화 모드)
3.19.8 Typing Variables
- 내부 변수의 타입을 유추하기 어렵거나, 유추 불가능한 경우, 다음과 같이 별도의 주석으로 설명할 수 있음.
#Type Comments:
a = SomeUndecoratedFunction() # type: Foo
#Annotated Assignments
a: Foo = SomeUndecoratedFunction()
3.19.9 Tuples vs Lists
- 리스트 타입은 한 가지 타입의 오브젝트만 포함할 수 있음.
- 튜플 타입은 반복되는 하나의 타입이나, 서로 다른 타입을 넣을 수 있음.
- 서로 다른 타입을 넣는 경우는 일반적으로 타입을 반환할 때 사용함.
a = [1, 2, 3] # type: List[int]
b = (1, 2, 3) # type: Tuple[int, ...]
c = (1, "2", 3.5) # type: Tuple[int, Text, float]
3.19.10 TypeVars
3.19.11 String types
- String 주석에 대한 적절한 type은 python2,3 버전에 따라 달라짐.
- Python3 버전일 경우 str 사용, Text도 가능하지만, 둘 중 하나를 일관성 있게 사용해야 함.
- Python2 버전일 경우 Text 사용. 가끔 str도 사용할 수 있으나, python3에 존재하지 않는 unicode는 사용하지 말 것.
No:
def py2_code(x: str) -> unicode:
...
- 이진 데이터를 처리하는 경우라면, bytes 사용.
def deals_with_binary_data(x: bytes) -> bytes:
...
- python2 에서 Text 데이터(str, unicode) 는 Text 사용.
- python3 에서 Text 데이터는 str 사용.
from typing import Text
...
def py2_compatible(x: Text) -> Text:
...
def py3_only(x: str) -> str:
...
- type이 byte 또는 text 일 수 있는 경우, 적절한 Text type과 함께 union을 사용할 것.
from typing import Text, Union
...
def py2_compatible(x: Union[bytes, Text]) -> Union[bytes, Text]:
...
def py3_only(x: Union[bytes, str]) -> Union[bytes, str]:
...
- 함수의 모든 string type이 항상 동일한 경우, AnyStr 사용.
- e.g. 반환 Type이 인자 Type과 동일한 경우.
3.19.12 Imports For Typing
- typing 모듈의 클래스는 항상 클래스 자체를 가져와야 함. typing 모듈에서 한 줄에 여러개의 특정 클래스를 가져올 수 있음.
typing import Any, Dict, Optional
- typing 에서 가져오는 이러한 방식이 로컬 네임스페이스에 항목을 추가한다는 점에서, typing 의 모든 이름은 키워드와 유사하게 취급되어야 함.
- 모듈에 있는 Type과 기존 이름이 충동하는 경우 import x as y 사용.
from typing import Any as AnyType
3.19.13 Conditional Imports
3.19.14 Circular Dependenceis
3.19.15 Generics
- 주석을 달 때, 일반 Type 지정을 선호해야 하고, 그렇지 않을 때는 Any로 가정함.
def get_names(employee_ids: List[int]) -> Dict[int, Any]:
...
# These are both interpreted as get_names(employee_ids: List[Any]) -> Dict[Any, Any]
def get_names(employee_ids: list) -> Dict:
...
def get_names(employee_ids: List) -> Dict:
...
- 파라미터의 적합한 타입이 Any일 때 명시적으로 표현되며, 많은 경우에 TypeVar가 더 적합할 수 있음을 기억해야 함.
def get_names(employee_ids: List[Any]) -> Dict[Any, Text]:
"""Returns a mapping from employee ID to employee name for given IDs."""
T = TypeVar('T')
def get_names(employee_ids: List[T]) -> Dict[T, Text]:
"""Returns a mapping from employee ID to employee name for given IDs."""
4. Parting Words
- Be Consistent
- 코드 수정 시, 이전 코드를 살펴보고, 스타일을 파악할 것.
- 스타일 가이드라인의 요점은 코딩에서 공통된 어투를 가지는 것, 그렇게 된다면 사람들은 당인싀 코드에 더 집중할 수 있음.
- 여기서는 세계적인 코딩 스타일 규칙을 제공하지만, 고유의 스타일도 중요함.
- 만약 파일에 코드를 추가했을 때, 다른 코드와 크게 달라보인다면 코드를 읽는 사람 입장에서는 리듬이 깨짐. 주의할 것.
End.