PEP8
Python의 대표적 코딩 컨벤션인 PEP8을 정리해봤다. 코딩 컨벤션엔 이유가 있어야 하는데, 대부분 컨벤션이 이유까지 설명하지는 않는다. 그래서 PEP8 컨벤션의 이유를 나름대로 생각해서 적어봤다.
PEP8
코드 레이아웃
들여쓰기
- 들여쓰기 수준마다 4개의 공백을 사용하라.
- A) 2개의 공백을 사용하면 행간 가독성이 떨어진다. 8개의 공백을 사용하면 코딩할 수 있는 가로 공간을 낭비한다.
- 여러 행을 연결하려면 괄호(대/중/소) 내 요소들의 암시적 연결(파이썬에서 괄호 안의 요소들은 백슬래쉬같은 명시적 연결 표현이 없어도 연결된 것으로 간주됨)을 사용하거나 걸쳐진 들여쓰기(hanging indent)를 사용하여 감싸고 있는 요소들을 세로로 정렬해야 한다. 걸쳐진 들여쓰기에서 첫번째 행에는 인수가 없어야 하며 구분을 명확하게 하기 위해 두번째 행부터는 들여쓰기를 해야 한다.
# 좋음: # 구분자 쉼표를 사용하여 세로 정렬 foo = long_function_name(var_one, var_two, var_three, var_four) # 걸쳐진 들여쓰기와 그 아래의 코드를 구분하기 위해 4개의 공백을 추가 def long_function_name( var_one, var_two, var_three, var_four): print(var_one) # 걸쳐진 들여쓰기 foo = long_function_name( var_one, var_two, var_three, var_four)# 나쁨: # 두번째 행에 세로 정렬되지 않은 인수가 바로 나타나서 그 아래 코드와 혼동됨 foo = long_function_name(var_one, var_two, var_three, var_four) print(var_one) # 걸쳐진 들여쓰기와 그 아래 코드가 구분되지 않음 def long_function_name( var_one, var_two, var_three, var_four): print(var_one)- A) 세로로 정렬하지 않으면 매개변수들의 가독성이 떨어진다. 들여쓰기를 하지 않으면 그 아래 코드들과 혼동된다.
- Q) 괄호 내 암시적 연결을 사용하면 함수 이름의 가독성이 높아지지만 코드가 가로로 늘어져 가독성이 떨어질 수 있다. 걸쳐진 들여쓰기를 사용하면 함수 이름의 가독성은 떨어지지만, 코드의 가로길이가 짧아지고 매개변수들의 가독성이 높아진다.
- 연결된 행에서 4개의 공백 룰은 옵션이다.
# 걸쳐진 들여쓰기에서는 4개가 아닌 공백을 사용할 수 있다. foo = long_function_name( var_one, var_two, var_three, var_four)- Q) 4개로 통일하는 것이 가독성 측면에서 좋을 것 같다.
- 조건문이 길어져 여러 행으로 나눠야 하는 경우, 키워드(if) + 1개의 공백 + 시작 괄호 + 개행 후 4개의 공백의 구조가 유용하다. 하지만 조건문 아래의 코드들도 4개의 공백을 갖기 때문에 가독성이 떨어질 수 있다. PEP에서는 이에 대해 명시적인 제안을 하지 않는다. 아래와 같이 몇가지 옵션이 있지만, 이에 국한하지 않는다.
# 그냥 4개의 공백을 사용 if (this_is_one_thing and that_is_another_thing): do_something() # 주석을 넣어서 주석의 색깔로 구분할 수도 있음 if (this_is_one_thing and that_is_another_thing): # Since both conditions are true, we can frobnicate. do_something() # 추가 공백으로 구분할 수도 있음 if (this_is_one_thing and that_is_another_thing): do_something()- Q) 조건문의 가독성은 중요하므로, 구분은 필요해 보인다. 위 예시의 세번째 것이 적절해 보인다.
- 조건문 내, 이항 연산자(and, or, …)의 위치도 논의 대상이다.
- Q) 조건의 대상이 중요한가, 이항 연산자가 중요한가의 문제인 것 같다. 개인적으로는 이항 연산자를 잘못써서 발생하는 오류가 더 많은 것 같다. 이항 연산자가 컬러링으로 구분되기는 하지만 맨앞에 정렬되어 있으면 좋을 것 같다. 따라서 위 예시의 3번째 것이 적절해 보인다.
- Q) 조건문 내에서 중첩된 괄호를 사용하는 방식도 논의해야 한다.
- 여러 행로 연결된 구성체의 닫는 괄호는 구성체 마지막 행, 공백이 아닌 최초 문자에 맞춰 세로 정렬하거나
my_list = [ 1, 2, 3, 4, 5, 6, ] result = some_function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', )- A) 구성체 요소를 추가/수정할 경우, 변경점이 행 단위로 구분되어 깔끔하다. 오타를 줄일 수 있다.
- 구성체 요소들의 마지막 행에서 개행한 후 맨 앞에 둔다.
my_list = [ 1, 2, 3, 4, 5, 6, ] result = some_function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', )- Q) 4개의 공백 들여쓰기 구조가 일시적으로 깨지기 때문에 가독성이 떨어질 것 같다.
탭이냐 공백이냐
- 탭 보다는 공백을 추천한다.
- A) 탭의 크기는 에디터 세팅에 따라 달라질 수 있기때문에(VI 8개, PyCharm 4개) 의도한 가독성이 깨질 수 있다.
- 들여쓰기 시 탭을 사용했던 레거시 코드가 있는 경우에만 일관성 유지 차원에서 탭을 사용한다.
- Q) 공백만 사용하는 코드로 변환하는 것이 좋을 것 같다.
- 파이썬3에서는 탭과 공백의 혼용을 불허한다.
- 탭과 공백을 혼용한 파이썬2 코드는 공백만 사용하는 코드로 변환해야 한다.
- 파이썬2의 커맨드라인 인터프리터를 사용할 때, -t 옵션을 주면 탭과 공백이 혼용된 부분을 경고로 알려준다. -tt 옵션을 주면 에러로 알려준다. 옵션 사용을 강력 권장한다.
행의 최대 가로 길이
- 파이썬 표준 라이브러리는 행의 최대 가로 길이를 79 캐릭터로 제한한다.
- Q) 예전 화면의 최대 가로 길이(80 캐릭터)에 맞춘 것 같은데, 요새는 화면이 크니 조금 더 늘려도 좋을 것 같다. 대략 100 캐릭터 정도이면 어떨까? 참고로 PyCharm 기본 설정은 120 캐릭터이다. 그러나 두 파일을 가로로 나란히 띄워놓고 코드를 비교하는 경우도 많으므로, 최대한 짧게 설정하는 것도 고민해봐야 한다.
- 파이썬 표준 라이브러리는 주석이나 DocString의 최대 가로 길이를 72 캐릭터로 제한한다.
- A) 코드와 시각적으로 분리되는 것이 좋다.
- 코드 편집기의 창 너비의 크기를 제한하면 코드를 나란히 띄워 검토할 수 있다.
- A) 의존 객체의 정의나 구현을 살펴보는 용도로 자주 사용한다.
- 코드 편집기가 자동 행 바꿈 기능을 지원하더라도 자동으로 잘린 코드는 읽기 어렵고 이해하기도 어렵다. 이것을 피하기 위해 코드 편집기의 창 너비를 80 캐릭터로 제한하는 것이다. 웹기반 툴들은 자동 행 바꿈 기능을 아예 지원하지 않기도 한다.
- 팀의 성격에 따라 내부 논의해서 가로 길이를 99 캐릭터까지 확장할 수 있다. 하지만 주석이나 DocString은 여전히 72 캐릭터로 제한한다.
- 파이썬 표준 라이브러리는 79 캐릭터(주석/DocString은 72캐릭터) 전통을 준수한다.
- 긴 행을 감싸는 가장 좋은 방법은 행의 구성 요소들을 암시적으로 연결해주는 괄호 같은 것을 사용하는 것이다. 백슬래시 같은 명시적 표현을 사용하기 이전에 괄호를 사용하자.
- A) 백슬래시를 사용하면 코드의 로직과 상관 없는 표현이 포함되므로 수정 용이성/가독성 면에서 좋지 않다.
- with, assert 문과 같이 구성요소들을 암시적으로 연결할 수 없는 경우에만 백슬래시를 쓴다.
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())- Q) 위 예시처럼 길어진다면 함수로 분리시키는 것이 좋을 것 같다.
이항 연산자(and, or, …)의 앞/뒤 어디에서 행을 바꿔야할까?
- 이항 연산자 뒤에서 행을 바꾸는 것이 오랜 관행이었으나 두 가지 관점에서 가독성이 떨어진다. 연산자가 세로 정렬이 되어 있지 않기 때문에 연산이 한 눈에 들어오지 않고, 연산자를 읽고 연산 대상을 확인하려면 아래 행으로 눈을 돌려야하기 때문에 코드를 읽는 흐름이 끊어진다.
# 나쁨: # 연산자가 연산 대상과 떨어져 있다. income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest) - 이런 문제 때문에 수학자와 수학 출판업계는 반대 규칙을 따른다. Donald Knuth는 그의 Computers and Typesetting 시리즈에서 전통적인 룰을 설명한다. “글로 쓴 공식에서는 항상 이항 연산 및 관계들 이후에 행이 바뀌지만, 기호로 표기된 공식에서는 항상 이항 연산 전에 행이 바뀐다.” 수학의 전통을 따르면 더 읽기 쉬운 코드가 된다.
# 좋음: # 연산자와 연산 대상이 쉽게 매칭됨 income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)A) 연산의 대상과 연산자가 한번에 읽혀야 연산을 이해하기 쉽다.
- 기존 코딩 스타일과 일관성을 유지하기 위해, 이항 연산자 앞/뒤 행 바꿈이 모두 허용되나, 새로운 코드에는 Knuth의 스타일을 권고한다.
빈 행
- 최상위 수준 함수와 클래스 정의는 두 개의 빈 행으로 구분한다.
- Q) 최상위 수준의 함수와 클래스 정의를 모듈의 맨 앞에 두면, 굳이 두 개의 빈 행은 필요 없을 것 같다. Robert C. Martin의 언급에 따라 함수와 클래스를 출현하는 순서대로 나열한다면 두 개의 빈 행으로 구분하는 것이 좋을 것 같다.
- 클래스의 메서드 정의는 한 개의 빈행으로 구분한다.
- A) 메서드 사이는 한 줄 띄우는 것이 좋다.
- 연관된 함수 그룹을 서로 구분하기 위해 빈 행을 추가할 수 있다.
- A) 코드 이외의 방식(주석 등으로 구분)하는 것은 수정에 취약하므로 지양하는 것이 좋을 것 같다.
- 함수 내에서 논리적 단위를 드러내기 위해 빈 행을 사용한다.
- Q) 애매한 경우가 있겠지만, 논리적 단위는 새로운 함수로 묶는 것이 맞다고 본다.
- 파이썬은 컨트롤-L(^L) 폼 피드 문자를 공백으로 취급한다. 많은 툴이 이 문자를 페이지 구분자로 취급하기 때문에, 파일 내에서 서로 연관성이 있는 단락을 페이지로 구분하는데 이 문자를 사용할 수 있다. 특정 편집기나 웹 기반의 코드 뷰어는 컨트롤-L을 폼 피드 문자로 인식하지 않을 수도 있으며, 이 경우 폼 피드를 대신하는 다른 문자가 컨트롤-L 문자의 위치에 표시될 것이다.
- Q) 출력 등을 위한 페이지 구분자에 대한 내용인데, 사용할 일이 없을 것 같다. 제대로 표현하지 못하는 경우도 있다고 하니, 아예 사용하지 않는 것이 낫겠다.
소스 파일 인코딩
- 파이썬 Core 코드는 반드시 UTF-8(파이썬2 에서는 ASCII)으로 배포해야한다. ASCII(파이썬2) 또는 UTF-8(파이썬3)을 사용하는 파일에는 인코딩 선언이 없어야 한다.
- A) 파이썬 코드를 해석하는 인터프리터에 혼동을 줄 수 있으니 따라야 한다.
- 표준 라이브러리에서는 DocString 및 주석에 작성자의 이름을 비 ASCII문자로 표현해야할 경우, 또는 테스트 목적일 경우에만 ASCII/UTF-8이외의 인코딩을 허용하며, 그렇지 않다면 리터럴에 \x, \u, \U, \N등의 이스케이프를 사용하는 것을 권고한다.[참고]
- Q) 특이한 인코딩을 사용하는 것은 여러모로 불편하니, 독음을 영어로 쓰는 것이 좋을 것 같다.
- Python3.0 이상의 표준 라이브러리는 반드시 ASCII 사용(PEP3131). 주석과 DocString에도 ASCII 사용. 예외는 테스트 목적 또는 저자의 이름 표현할 때. 그러나 이때에도 ISO/IEC 8859-1를 벗어난다면, ASCII를 활용해 음독으로 표현해야 함. 전세계 대상의 오픈소스를 만든다면, 따르는 것이 좋다.
- A) 간단히만 적는다면 영어 주석이나 음독도 가독성이 떨어지지 않는다. 권고를 따르는 것이 좋을 것 같다.
임포트
- 임포트는 행 바꿈 한다.
# 좋음: import os import sys# 나쁨: import sys, os- A) 공통점이 없는 것들은 묶지 않는 것이 상식. 묶으면 수정에 취약하다.
import a, b, c, d로 선언하다가 b를 삭제해야 한다면, 변경된 소스를 볼 때 변경점이 한눈에 들어오지 않는다. 행으로 구분하면 한눈에 들어온다.
- A) 공통점이 없는 것들은 묶지 않는 것이 상식. 묶으면 수정에 취약하다.
- 모듈에서 다수의 함수/클래스를 import하는 경우에는 행 바꿈하지 않는다.
# 좋음: from subprocess import Popen, PIPE- A) 모듈의 일부를 import 하는 경우에는 모듈이 하나로 명시되고, 변경도 모듈 단위로 될 가능성이 크므로 한 줄로 쓰는 것이 낫다. 오히려 행을 구분하면
from 모듈을 중복해서 써야한다.
- A) 모듈의 일부를 import 하는 경우에는 모듈이 하나로 명시되고, 변경도 모듈 단위로 될 가능성이 크므로 한 줄로 쓰는 것이 낫다. 오히려 행을 구분하면
- import는 파일의 맨 윗부분, 모듈 주석과 DocString 밑, 모듈 전역 및 상수 위에 넣는다.
- A) 코드는 위 > 아래, 큰 개념 > 작은 개념으로 읽는 것이 자연스럽다.
- import는 Standard 라이브러리들(os, sys, collections, …) > 관련된 써드파티 라이브러리들(numpy, pandas, …) > 자체 작성한 라이브러리 순서로 하고, 각 라이브러리 그룹들 간에는 행을 구분한다.
- A) 큰 개념 > 작은 개념으로 읽히므로, 읽기 자연스러운 순서다. Pycharm과 같은 에디터에서는 중복 import를 회색으로 컬러링 해 주는데, 위 순서대로 한다면 중복 import를 확인할 수 있다.
- 절대 경로로 import 한다. 읽기 쉽고, system import 설정이 잘못된 경우에도 오류가 발생할 확률이 적다.(최소한 오류 메세지라도 정확하게 뿌려준다.)
# 좋음: import mypkg.sibling from mypkg import sibling from mypkg.sibling import example- A) 상대 경로를 사용하기 시작하면, import 경로를 한 눈에 파악하기 어렵고 파악하는데 시간을 들여야한다.[참고]
- 명시적(.을 사용해서 현재 경로를 명시적으로 표현)으로 상대 경로를 import하는 것도 허용은 된다. 특히 절대 경로를 사용하기엔 너무 복잡한 디렉토리 구조를 갖는 패키지를 import할 때 사용할 수 있다.
from . import sibling from .sibling import example- Q) 보통 추상화 레벨에 따라 패키지의 depth를 만들기 때문에, 좀 길더라도 이 depth를 모두 표현한 절대 경로를 사용하는 것이 가독성을 높일 것 같다.
- 표준 라이브러리는 복잡한 패키지 레이아웃을 가지면 안되고, 무조건 절대 경로로 import
- A) 표준 라이브러리를 작성할 일이 있을지 모르겠지만, 많은 사람이 사용할 것이므로 정의된 규칙을 따라야한다.
- 암시적으로 상대 경로를 사용하는 것은 금지. Python3에서는 제거되었음
- A) 암시적으로 import하면 패키지가 현재 소스 디렉토리에 있는 것인지 아니면 sys.path에 있는 것인지 알 수가 없다.참고
- 클래스를 담고 있는 모듈에서 클래스를 import할 때에는 일반적으로 아래와 같이
from을 사용한다.from myclass import MyClass from foo.bar.yourclass import YourClass- A) 로컬 클래스명과 충돌이 나지 않는다면, 그리고 클래스의 네이밍이 너무 추상적이지 않다면 코드량 줄이기/가독성 면에서 당연하다.
- 로컬 클래스명과 충돌이 발생한다면 아래와 같이 명시적으로 쓰고,
myclass.MyClass,foo.bar.yourclass.YourClass와 같이 사용한다.import myclass import foo.bar.yourclass- A) 충돌이 나면 어쩔 수 없으니, 당연한 얘기
- 와일드카드로 패키지를 import하는 것(
from <module> import *처럼)은 현재 네임 스페이스를 불명확하게 하는 것(패키지 내에 어떤 이름의 모듈들이 있을지 모르는데 모두 import하는 것)이므로, 문제를 발생시킬 수 있다.- A) 불필요한 것이 포함되면 확장/수정에 모두 불리하다. 상식적인 얘기
- 내부(Internal) 인터페이스를 import하여 public API로 재배포하고자 하는데, 어떤 내부 인터페이스를 사용하여 재배포할지 결정되지 않은 경우에는 와일드카드 import를 사용할 수 있다.
- Q) API 배포할 때마다 import 부분을 수정해야하는 수고가 있겠지만, 그래도 import 되는 부분이 어떻게 변화되어 가는지 추적한다는 관점에서 와일드카드 import는 지양하는 것이 낫지 않나 싶다.
모듈 수준의 DUNDER(Double UNDERscore) 이름
__future__, __all__, __author__, __version__등과 같은 모듈 수준 dunders(즉, 두 개의 선행 밑줄과 두 개의 후행 밑줄이있는 이름)은 모듈의 DocString 뒤, 모든 import 문의 앞에 위치해야한다. Python에서__future__는 Docstring을 제외하고는 다른 어떤 코드들 보다 앞에 위치해야 한다."""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- A)
__future__, __all__, __author__, __version__은 모듈 전체의 내용을 표현하고 있으므로 큰 개념 > 작은 개념 순서의 관점에서 앞에 위치시키는 것이 맞다. 오픈소스 패키지를 만들지 않는 한,__future__를 사용할 일은 없을 것 같지만, 여튼__future__의 위치는 강제사항.[참고]
- A)
문자열 따옴표
- Python에서 홑따옴표와 겹따옴표는 완전히 같다. 아무거나 골라서 일관성 있게 사용하면 된다. 다만 문자열 내에 홑따옴표와 겹따옴표가 포함되는 경우에는 백슬래시를 사용하지 않는 방향을 권장한다.
- Q) 문자열 표현에는 일반적으로 겹따옴표가 더 친숙하고(C/C++), 경험적으로 문자열 내에 따옴표를 사용해야 할 경우에는 대부분 홑따옴표로 커버가 가능한 것 같다.
- 3개의 따옴표로 문자열을 묶을 때[참고]에는 항상 겹따옴표를 사용하여 DocString 규칙PEP257을 따른다.
- A) DocString 표현과 혼동을 피하기 위해 통일하자는 것.
표현과 구문([참고])에서의 공백
거슬리는 것들(Pet Peeves)
- 불필요한 공백은 없애자
- 괄호(대/중/소)바로 안
# 좋음: spam(ham[1], {eggs: 2})# 나쁨: spam( ham[ 1 ], { eggs: 2 } )- A) 괄호 자체로 구분이 되는데, 굳이 가로 공백을 낭비할 필요가 없다. 공백이 많아지면 구분자로서의 공백의 의미가 떨어진다.
- 후행 쉼표(Trailing Comma)와 바로 이어지는 닫는 괄호 사이
# 좋음: foo = (0,) # 나쁨: bar = (0, )- A) 계속되고 있다는 것을 쉼표로 충분히 표현했기 때문에 굳이 공백을 추가해 공간을 낭비할 필요가 없다.
- 쉼표, 세미콜론, 콜론 바로 앞
# 좋음: if x == 4: print x, y; x, y = y, x# 나쁨: if x == 4 : print x , y ; x , y = y , x- A) 구분자의 의미를 갖는 공백과 혼동되어 가독성이 떨어진다.
- 콜론을 슬라이스 또는 확장된 슬라이스로 사용할 경우 (우선순위가 가장 낮은)이항 연산자와 비슷하므로 콜론 앞/뒤 형식을 동일하게(공백을 넣던 빼던) 맞춰야 한다.
# 좋음: 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 : : upper] ham[ : upper]- A) 이항 연산자의 의미가 되므로, 연산자의 앞/뒤 형식을 맞춰야 같은 비중을 가진다는 의미로 읽힌다.
- 예외로 파라미터가 생략되면 공백도 생략한다.
- A) 상식적이고, 구분자로써의 공백의 의미에도 부합한다.
- 함수 호출 시, 인수 목록이 시작되는 괄호 바로 앞
# 좋음: spam(1)# 나쁨: spam (1)- A) 함수의 의미는
함수+인수로 파악이 되므로 붙이는 것이 가독성을 높인다.
- A) 함수의 의미는
- 인덱싱 또는 슬라이싱을 시작하는 괄호 바로 앞
# 좋음: dct['key'] = lst[index]# 나쁨: dct ['key'] = lst [index]- A) 인덱싱 및 슬라이싱은 기존 대상의 일부를 표현하는 것이므로 의미적으로 붙이는 것이 가독성을 높인다.
- 세로 정렬을 위해 대입 연산자 근처에 여러 개의 공백을 넣는 경우
# 좋음: x = 1 y = 2 long_variable = 3# 나쁨: x = 1 y = 2 long_variable = 3- A) 세로 정렬을 위해 공백을 넣으면, 대입 대상과 피대상이 분리되어 가독성이 떨어지고, 오타의 가능성도 커진다. 대입 대상의 이름이 변경되거나, 길이가 더 긴 새로운 대입 대상이 추가되면 기존 코드를 모두 재정렬 해야한다.
- 괄호(대/중/소)바로 안
기타 권고들
- 표현과 구문 끝에 이어지는 공백은 제거한다. 그러한 공백은 보이지 않기 때문에 혼동을 줄 수 있다. 예를들어 행 바꿈이 된 코드들이 연속된 코드임을 나타내기위해 백슬래시를 사용하는데, 백슬래시 뒤에 공백을 넣으면 제대로 동작하지 않는다. 몇몇 에디터는 자동으로 이러한 공백을 제거하며, CPython 같은 프로젝트에서는 이러한 코드를 커밋하지 못하게 한다.
- A) 라인 끝에 보이지 않는 공백 때문에 문제가 발생할 수 있다는 얘기
- 다음과 같은 이항 연산자의 앞/뒤에는 한개의 공백을 넣는다.
- 대입(=), Augmented 대입(+=, -=, …), 비교(==, <, >, !=, <>, <=, >=, in, not in, is, is not), Booleans(and, or, not)
- A) 강력한 레거시다. 연산자와 피연산자를 시각적으로 구분하기 쉽다. 에디터에서 컬러링도 해주지만 확실히 공백을 넣는 것이 더 쉽게 읽힌다.
- 대입(=), Augmented 대입(+=, -=, …), 비교(==, <, >, !=, <>, <=, >=, in, not in, is, is not), Booleans(and, or, not)
- 우선 순위가 다른 연산자를 사용하는 경우 우선 순위가 가장 낮은 연산자 주위에 공백을 추가하는 것이 좋다. 그러나 두 개 이상의 공백을 사용하지 말고 항상 이항 연산자의 양쪽에 동일한 수의 공백을 넣어라.
# 좋음: 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)- Q) 짧으면 괜찮지만, 길어지면 공백 보다는 괄호를 넣는 것이 나을 것 같다. 우선순위를 파악하기 위해서 뇌를 사용하면 시간이 아깝다.
- 함수 주석은 콜론에 대한 일반 규칙(슬라이싱으로 콜론을 사용하는 경우를 제외하곤 콜론 뒤에 한개의 공백)을 사용해야하며, 주석이 존재하는 경우 항상-> 화살표 주위에 공백이 있어야한다.
# 좋음: def munge(input: AnyStr): ... def munge() -> PosInt: ...# 나쁨: def munge(input:AnyStr): ... def munge()->PosInt: ...- A) 코드와 주석은 구분해야 읽기 좋다.
- 키워드 인수(
magic(r=real, i=imag)와 같은)를 표시하거나, 주석이 없는 함수 매개 변수의 기본값(def complex(real, imag=0.0)같은)을 표시하는 데 = 기호를 사용할 때, = 기호 주위에 공백을 사용하지 않는다.# 좋음: def complex(real, imag=0.0): return magic(r=real, i=imag)# 나쁨: def complex(real, imag = 0.0): return magic(r = real, i = imag)- A) = 기호가 연산자의 의미로 사용된 것이 아닌데다가, 인수들은 쉼표+공백으로 구분되는데, 인수들 사이에 추가적으로 공백이 들어가면 인수 구분 가독성이 떨어진다.
- 그러나 인수 주석과 기본값을 같이 표현할 때에는 = 기호 주위에 공백을 사용한다.
# 좋음: 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): ...- A) 인수 주석과 디폴트값을 구분해서 읽어야 하기 때문에 사이에 공백을 넣는다. 공백을 넣지 않으면 인수 주석에 대입을 하는 것처럼 읽힌다.
- 같은 행에 여러 문(Statement)을 쓰는 것은 일반적으로 권장하지 않는다.
# 좋음: if foo == 'blah': do_blah_thing() do_one() do_two() do_three()# 나쁨: if foo == 'blah': do_blah_thing() do_one(); do_two(); do_three()- A) 같은 행에 여러 문을 쓰는 것은 가독성, 변경 용이성, 변경점 추적 등 모든면에서 나쁘다.
- 때로는 같은 줄에 작은 본문으로 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() 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()- A) 한 줄로 간략히 적는 것이 컴팩트해서 좋아보이지만, 여러 코드들과 섞여 있을 때, 한 눈에 읽히지 않는 것은 사실이다. 세미콜론으로 연결한 다중 절은 가독성, 변경점 추적에 최악이므로 지양하는 것이 맞다.
후행 쉼표를 사용할 때
- 후행 쉼표는 옵션이지만, 요소 1개를 가지는 튜플을 만들 때에는 필수다. 괄호로 감싸서 명확하게 표현하는 것을 권장한다.
# 좋음: FILES = ('setup.cfg',)# 나쁨: FILES = 'setup.cfg',- A) 문의 마지막에 쉼표만 있으면 잘 보이지 않는다. 괄호를 함께 두면 명확하게 보이고, 계속된다는 의미를 확실히 읽을 수 있다.
- 항목이나 인수가 계속 확장된다면, (특별한 기능을 하는 것은 아니지만)후행 쉼표를 사용하면 좋다. 각 항목이나 인수는 쉼표를 붙여 행마다 한 개씩 나타내고, 닫는 괄호는 행을 바꿔 넣는다.(위에서 설명한 싱글톤 튜플의 경우엔 제외)
# 좋음: FILES = [ 'setup.cfg', 'tox.ini', ] initialize(FILES, error=True, )# 나쁨: FILES = ['setup.cfg', 'tox.ini',] initialize(FILES, error=True,)- A) 후행 쉼표 자체가 항목 추가/변경을 고려한 표현이므로, 위와 같은 룰이 항목 추가/변경에 용이할 것 같다.
주석
- 코드와 모순되는 주석은 없는게 더 낫다.
- 주석은 완전한 문장이어야 하고, 소문자로 된 식별자로 시작하지 않는 이상 첫 글자는 대문자여야 한다.
- A) 문장은 대문자로 시작한다.
- 블록 주석은 완전한 문장으로 구성된 하나 이상의 단락으로 구성되며, 각 문장은 마침표로 끝난다.
- 여러 문장으로 된 주석에서 마지막 문장을 제외하고, 문장 끝 마침표 뒤에 2개의 공백을 넣어야 한다.
- A) 한 문장으로 끝내는 것이 좋을 것 같고, 문장 간의 구분은 2개의 공백으로 확실하게 하는 것이 좋을 것 같다. 블록 주석의 예시를 보면 공백 1개보다 2개를 넣을 때 가독성이 더 좋다.
- 이해하기 쉽게 써라.
- 외국인이 당신의 코드를 읽지 않으리라 120% 확신하지 않는 한, 주석은 영어로 작성한다.
블록 주석
- 코드와 같은 레벨로 들여쓰기 한다.
- 주석 내에서 텍스트를 들여써야 하는 경우가 아니라면 주석의 각 행은
# + 공백1개으로 시작한다. - 블록 주석내의 단락은
#만 사용한 행으로 구분한다.# This is the start point of block comments. The first paragraph is # written here. # # The second paragraph is written here. for i in range(0,100): print(i)- A) 상식적인 내용.
#뒤에 공백을 붙여야 주석 내용이 구분되어 보인다.
- A) 상식적인 내용.
인라인 주석
- 쓰지말것
- A) 코드 수정에 매우 불편
- 쓴다면 코드 뒤에 최소 2개의 공백을 넣고
# + 공백1개로 사용한다.# 나쁨 x = x + 1 # x를 1만큼 증가시킴# 의미를 나타낸다면, 가끔 의미가 있을 수도 있음 x = x + 1 # 가장자리 계산을 위한 보정- Q) 사용을 금지하는 것이 좋을 것 같다. 아래 예시에서 의미가 있다고 한 것은 인라인이어서 의미가 있는 것이 아니라, 코드의 의미를 설명하는 주석이기 때문에 의미가 있다는 것. 사실 이것도 정체모를 x라는 변수 명을 바꾸고 상수 1에도 이름을 지어주는 것이 더 낫다고 생각한다.
독 스트링(Documentation Strings, DocStrings)
- 좋은 독 스트링을 작성하는 규칙은 PEP257에 있다.
- 모든 공개(public) 모듈, 함수, 클래스, 메소드들에 대해 독 스트링을 작성한다. 비공개 메소드에 대해서는 작성할 필요 없지만, 어떤 역할을 하는 메소드인지 주석을 def 줄 바로 다음에 작성해야 한다.
- Q) 자동 문서화를 위한 것이지만, 과연 코드와 동기화가 잘 될 것인가. 개발을 마친 후에 작성하는 것이 낫다고 보고, 개발 시에는 주석이 아닌 코드로 최대한 표현하려고 하는 것이 나을 것 같다.
- 여러 행으로 구성된 독스트링을 끝내는
"""는 마지막 행에 단독으로 두어야 한다. 그러나 한줄로 된 독스트링의 경우에는 붙여서 쓴다."""Return a foobang Optional plotz says to frobnicate the bizbaz first. """"""Return an ex-parrot."""- A) 에디터나 IDE에서 컬러링해 주기는 하지만, 그래도 명시적으로 행을 구분하는 것이 가독성에 좋을 것 같다.
- 모든 공개(public) 모듈, 함수, 클래스, 메소드들에 대해 독 스트링을 작성한다. 비공개 메소드에 대해서는 작성할 필요 없지만, 어떤 역할을 하는 메소드인지 주석을 def 줄 바로 다음에 작성해야 한다.
네이밍 규칙
- 기존 Python 라이브러리들의 네이밍 규칙은 엉망이지만, 새로운 모듈과 패키지에는 다음의 네이밍 규칙을 권장한다.
우선 원칙
- 사용자에게 공개되는 API의 이름들은 구현보다는 사용법을 반영해야 한다.
- A) 사용자 입장에서 읽고 이해하기 쉬어야한다는 당연한 얘기. 하지만 의외로 인터페이스부터 작성하는 개발자가 별로 없다. 상세 구현에 매몰되는 경우를 많이 봤다.
네이밍 스타일
- 많은 네이밍 스타일이 있는데, 이를 알아보는 것은 각 네이밍 스타일들의 목적과는 별개로 주로 어떤 네이밍 스타일들이 사용되는지 파악하는데 도움이 된다. 다음은 일반적인 네이밍 스타일들이다.
- b : 단일 소문자)
- B : 단일 대문자)
- lowercase : 소문자만
- lower_case_with_underscores : 소문자만 언더바로 연결
- UPPERCASE : 대문자만
- UPPER_CASE_WITH_UNDERSCORES : 대문자만 언더바로 연결
- CapitalizedWords(CapWords/CamelCase/StudlyCaps) : 대문자로 시작된 단어로 연결
- 약어가 포함될 경우, 약어는 대문자로만 표시하는 것이 낫다. HTTPServerError가 HttpServerError보다 낫다.
- Q) 약어와 약어가 연결된 경우에는 애매하다.
- 약어가 포함될 경우, 약어는 대문자로만 표시하는 것이 낫다. HTTPServerError가 HttpServerError보다 낫다.
- mixedCase : 대/소문자를 섞음(시작 문자가 소문자이므로 대문자로 시작된 단어로 연결하는 경우와 다름)
- Capitalized_Words_With_Underscores(구리다!) : 대문자로 시작된 단어를 언더바로 연결
- 짧은 접두어를 사용하여 그룹을 표현하는 경우도 있다. 예를들어 os.stat() 함수는 st_mode, st_size, st_mtime과 같은 이름을 갖는 튜플을 반환한다.(POSIX 시스템 호출 구조체의 필드와 매칭시켜 개발자에게 친숙함을 제공)
- Q) 위와 같은 예외상황을 제외하고 접두어는 사용하지 않는 것이 좋다. 이유는 아래에
- X11 라이브러리는 모든 공용 public 함수에 X 접두어를 사용한다. 파이썬에서는 보통
object_name.method_name, module_name.function_name으로 사용되기 때문에 이런 네이밍은 불필요하다.- A) 네임 스페이스는 설명과 같이 패키지,모듈,클래스 이름으로 구분하는 것이 이해하기 좋다.
- 선/후행 언더바는 다음과 같이 특수한 목적을 나타낸다. (일반적으로 대/소문자 규칙과 결합된다.)
- 선행 언더바 1개(
_single_leading_underscore)는 내부적으로만 사용됨을 나타낸다.import *를 할 경우, 1개의 선행 언더바로 시작된 객체들은 임포트되지 않는다. - 후행 언더바 1개(
single_leading_underscore_)는 Python의 키워드와 충돌을 피하기 위해 사용한다.tkinter.Toplevel(master, class_='ClassName') - 선행 언더바 2개(
__double_leading_underscore)로 클래스의 속성을 지정할 때, 네임 맹글링을 만든다.(FooBar 클래스에서__boo속성은__boo가 아닌,_FooBar__boo로 맹글링 된다.)- A) 선행 언더바 2개를 private 메소드의 이름을 지을 때 사용하는 경우가 있지만, 본질은 맹글링이다.[참고]
- 선행, 후행 언더바 2개(
__double_leading_and_trailing_underscore__)는 유저가 컨트롤하는 네임스페이스에 있는 매직 객체/속성 :__init__, __import__, __file__와 같은 이름들이며 이런 이름들은 사용 방식이 있으니 문서를 참고해서 그에 따라서만 사용한다.
- 선행 언더바 1개(
네이밍 규칙
- 피해야 하는 이름
- 소문자 엘(l), 대문자 오(O), 대문자 아이(I)를 단일 문자 변수로 사용하지 말 것
- A) 읽을 때, 숫자1, 숫자0, 대문자I 등과 혼동될 수 있다.
- 소문자 엘(l), 대문자 오(O), 대문자 아이(I)를 단일 문자 변수로 사용하지 말 것
- ASCII 호환
- 패키지와 모듈 이름
- 모듈 이름은 짧고, 모두 소문자여야 한다. 가독성을 위해 언더바를 삽입할 수 있다. 패키지 이름 역시 짧아야 하고, 언더바는 지양한다.
- Q) ‘짧다’의 정량적인 기준이 필요한데.. 대략 언더바를 최대 1개까지 사용하는 것은 어떨까?
- C/C++로 작성된 확장 모듈이 상위 레벨의 인터페이스를 제공하는 파이썬 모듈을 가지고 있다면, 그 C/C++ 모듈은 선행 언더바 1개를 가진다.(예:
_socket)
- 모듈 이름은 짧고, 모두 소문자여야 한다. 가독성을 위해 언더바를 삽입할 수 있다. 패키지 이름 역시 짧아야 하고, 언더바는 지양한다.
- 클래스 이름
- 타입 변수 이름
- 타입 변수 이름은 PEP484에 소개되어 있고, 보통 짧은 CapWords로 표현한다.(T, AnyStr, Num, …) 공변/반공변성을 갖는 타입에는
_co또는_contra접미사를 붙이는 것을 권고한다.from typing import TypeVar VT_co = TypeVar('VT_co', covariant=True) KT_contra = TypeVar('KT_contra', contravariant=True)- A) 파이썬에서 타입 변수를 명시할 수 있도록 한 것은 가독성을 높이기 위함이므로 최대한 명확히 적는 것이 좋을 것 같다.[참고]
- 타입 변수 이름은 PEP484에 소개되어 있고, 보통 짧은 CapWords로 표현한다.(T, AnyStr, Num, …) 공변/반공변성을 갖는 타입에는
- 예외 이름
- 예외는 반드시 클래스이어야 하기 때문에, 클래스 이름 규칙이 적용된다. 하지만
Error접미사를 반드시 붙여야 한다.(예외가 실제 에러라면).
- 예외는 반드시 클래스이어야 하기 때문에, 클래스 이름 규칙이 적용된다. 하지만
- 전역 변수 이름
- (전역 변수는 하나의 모듈 내에서면 사용되길 희망한다.)함수의 규칙과 거의 같다.
from M import *를 통해 사용하도록 설계된 모듈은__all__메커니즘을 사용하여 전역 내보내기를 방지하거나 이러한 전역 변수들의 이름에 언더바 접두어를 붙여 비공개임을 나타낸다.- Q) 전역 변수는 최대한 사용하지 않는 것이 좋다. 클래스 정적 변수로 커버 가능하다.
- 함수와 변수 이름
- 변수/함수 이름은 소문자. 가독성을 높이기 위해 언더바를 삽입할 수 있다.
- mixedCase는 이전 스타일(threading.py)과 호환성을 유지하기 위해서만 사용한다.
- Q) 확실히 언더바를 넣으면 가독성이 좋아진다. 하지만, 코드의 가로 길이가 증가하고, 어떤 기준으로 언더바를 넣느냐가 불명확하기 때문에 통일성을 해칠 수 있다.
- 함수와 메소드 인수
- 인스턴스 메소드의 첫번째 인수는 항상
self로 한다. - 클래스 메소드의 첫번째 인수는 항상
cls로 한다. - 함수 인수의 이름이 예약된 키워드와 충돌하는 경우, 동의어를 사용해 이를 해결하는 것이 가장 좋고, 적절한 동의어가 없는 경우에는 후행 언더바를 사용하는 것을 권장한다.
- A) 적절한 동의를 찾는 것이 통일성/가독성 면에서 좋다.
- 인스턴스 메소드의 첫번째 인수는 항상
- 메소드 이름 및 인스턴스 변수
- 함수 이름은 소문자. 가독성을 높이기 위해 언더바를 삽입할 수 있다.
- 비공개 메소드 및 인스턴스 변수에만 선행 언더바를 사용한다.
- A) 가독성을 높이기 위해 언더바 사용은 좋은 것 같다.
- 하위 클래스의 메소드 이름 및 인스턴스 변수와 충돌된다면, 선행 언더바 2개를 사용, 맹글링을 활용한다.
- Q) 하위 클래스와 메소드 이름이 중복될 일이 없어야 할 것 같다. 오버라이딩하지도 않는데 이름이 같다면 네이밍에 문제가 있다고 판단된다.
- 파이썬은 다음과 같이 맹글링 한다.
Foo클래스에__a라는 속성이 있으면Foo.__a에 접근할 수 없다.__a는_Foo__a로 맹글링되기 때문이다. 굳이 접근하려면Foo._Foo__a로 접근해야 한다. 일반적으로 맹글링은 하위클래스와 충돌을 방지하기 위해서만 사용해야 한다. 이렇게 선행 언더바 2개를 사용하는 것에는 몇가지 논란이 있다.
- 상수
- 상수는 일반적으로 모듈 수준에서 정의되며 단어 사이를 구분하는 언더바와 함께 모두 대문자로 표현된다. 예로는 MAX_OVERFLOW 및 TOTAL이 있다.
- A) 상수를 바로 구분할 수 있다.
- 상수는 일반적으로 모듈 수준에서 정의되며 단어 사이를 구분하는 언더바와 함께 모두 대문자로 표현된다. 예로는 MAX_OVERFLOW 및 TOTAL이 있다.
- 상속 설계
- 클래스의 메소드와 인스턴스 변수들의 공개/비공개를 항상 고민해야한다. 잘 모르겠으면 비공개로 두자. 공개를 나중에 비공개로 변경하려면 비용이 많이 든다.
- A) 공개된 이후에는 더 이상 나만의 것이 아니다.
- 공개 속성은 이전 버전과 호환되고, 클래스와 관계 없는 클라이언트가 사용하는 것을 가정한 속성이다. 비공개 속성은 제3자가 사용하지 않는 속성이다. 비공개 속성은 제거되거나 변경될 수 있다.
- 여기서는 private란 용어를 사용하지 않고 비공개(non-public)란 용어를 사용한다. 왜냐하면 파이썬에서는 특별한 작업을 하지 않는 한, 어떠한 속성도 실제 private은 아니기 때문이다.
- 속성의 또다른 카테고리는 하위 클래스 API의 일부가 되는 것들이다.(보통 다른 언어에서는 protected라고 하는). 어떤 클래스들은 동작을 확장하거나 수정하기 위해 상속받도록 설계 되었다. 이러한 클래스를 디자인 할 때, 어떤 속성이 공개인지, 하위 클래스 API의 일부인지, 기본 클래스에서만 사용할 수 있는지에 대해 명시적으로 결정해야 한다.
- 이러한 관점에서 다음 가이드라인을 참고하라.
- 공개 속성은 선행 언더바를 사용하지 않는다.
- 예약된 키워드와 충돌한다면, 한개의 후행 언더바를 넣는다. 약어나 오염된(자기 맘대로 줄여서 정의한) 철자보다 낫다. 하지만 이러한 규칙에도 불구하고
cls라는 약어는 클래스로 알려진 변수 또는 인수, 특히 클래스 메소드의 첫 번째 인수에 사용된다.- A)
cls와 같은 시스템 약어는 잘 익혀두자.
- A)
- 단순한 공용 데이터 속성의 경우엔 복잡한 get/set 메소드 없이 속성 이름만 노출하는 것이 젤 좋다. 굳이 필요하다면 파이선에서는 property를 사용해서 쉽게 구현할 수 있다.
- A) property를 사용하면
__get__, __set__, __delete__를 구현함으로써 setter/getter/deleter를 사용할 수 있다.[참고]
- A) property를 사용하면
- 속성은 new-style 클래스(파이썬3 에서는 new-style 클래스만 지원)에서만 작동한다.[참고] 캐싱[참고]과 같은 부작용은 괜찮지만, 기능 동작 자체에 부작용이 없도록 주의하자. 계산 비용이 많이 드는 작업에 속성을 사용하지 말것. 보통 속성 참조는 값싼 동작이라고 가정하기 때문에 호출하는 쪽에서 당황할 수도 있다.
- 클래스가 상속될 수 있고, 상속받은 클래스에서 특정 속성에 접근하지 못하도록 하고 싶다면, 선행 언더바2개를 사용한 네이밍을 고려하자. 속성 이름이 맹글링이 되며, 이름 충돌도 막아준다.
- Q) 애초에 상/하위 클래스의 추상화 레벨이 다른데, 같은 이름의 속성을 가진다면 무언가 네이밍이 잘못된 것으로 봐야한다.
- 이름 맹글링에는 단순한 클래스 이름이 사용되기 때문에(언더바 + 단순한 클래스 이름), 하위 클래스가 만약 이와 동일한 클래스 이름과 속성을 선택한다면, 여전히 충돌 가능성이 있다.
- 이름 맹글링은 특정 상황에서(디버깅,
__getattr__()[참고] 등) 편의성이 떨어질 수 있다. 그럼에도 불구하고 이름 맹글링 알고리즘은 문서화가 잘 되어 있으며(쉽게 해석할 수 있고), 수동으로 수행시키기 쉽다. - 모두가 이름 맹글링을 좋아하는 것은 아니다. 미래의 사용자가 잠재적으로 클래스를 사용할 경우, 충돌을 피하는 것이 얼마나 필요한지 잘 따져보아야 한다.
- Q) 앞에서 언급한 바와 마찬가지로 최대한 지양하는 것이 좋을 것 같다. 가독성/통일성이 떨어지므로
- 클래스의 메소드와 인스턴스 변수들의 공개/비공개를 항상 고민해야한다. 잘 모르겠으면 비공개로 두자. 공개를 나중에 비공개로 변경하려면 비용이 많이 든다.
공개/내부 인터페이스
- 호환성 보장은 공개 인터페이스만. 따라서 공개/내부 인터페이스를 명확히 구분해야 한다.
- 내부 인터페이스는 명시적으로 내부 인터페이스임을 선언할 것(1개의 선행 언더바 등). 선언되지 않은 모든 문서화된 인터페이스는 공개 인터페이스로 간주.
__all__속성을 활용할것.__all__을 빈 리스트로 둔다면 해당 모듈은 공개 API가 없다는 뜻.__all__을 적절히 설정하더라도 내부 인터페이스는 1개의 선행 언더바로 네이밍할 것.- 포함된 네임스페이스(패키지, 모듈, 클래스)가 내부로 간주되면, 인터페이스들도 모두 내부로 간주.
- 임포트된 이름들은 항상 상세 구현으로 간주해야 한다. 그 이름 외에 모듈들은 절대로 간접적으로 접근해서는 안된다. 서브 모듈의 기능을 드러낸
os.path나 패키지의__init__모듈처럼 모듈 API에 포함되었다고 명시적으로 문서화되어있지 않는 한 말이다.
프로그래밍 권고
- CPython외에 다양한 파이썬 구현들(PyPy, Jython, IronPython, Cython, Psyco 등[참고])에 악영향을 주는 코드를 작성하지 말자.
- 예를 들어, CPython에서는 in-place 문자열 연결(하나의 레퍼런스에 문자열을 계속 연결) 구현이
a += b또는a = a + b로 최적화되어 있는데, 이는 CPython에서도 일부 타입에만 적용되고, 레퍼런스 카운팅을 사용하지 않는 구현에는 아예 존재하지도 않는다[참고]. 성능에 민감한 부분에는''.join()을 사용해야 한다. 이는 다양한 파이썬 구현에서도 O(N) 시간 복잡도를 보장한다.- A) 다양한 파이썬 구현들을 사용할 일이 별로 없을 것 같으나,
''.join()등은 습관과 연결되므로 참고해두면 좋을 것 같다.
- A) 다양한 파이썬 구현들을 사용할 일이 별로 없을 것 같으나,
- 예를 들어, CPython에서는 in-place 문자열 연결(하나의 레퍼런스에 문자열을 계속 연결) 구현이
- None과 같은 싱글톤[참고]을 비교할 때는
is또는is not구문을 사용해야 하며==기호를 사용하지 말자.- A)
==은 레퍼런스가 아닌 값을 비교하기 때문에 싱글톤을 비교할 때 오류가 발생할 수 있다.
- A)
if x is not None을 표현하려고if x를 쓸 때 주의하자. 디폴트 값이 None인 변수/인수의 세팅된 값을 확인할 때, 그 값이 Boolean으로 False가 되는 타입(컨테이너 등)일 수도 있다.- A) x의 디폴트 값이 None인데, x에 요소 개수가 0인 컨테이너가 assign된다면,
if x는 None과 False를 동시에 검사하는 모호한 표현이 된다[참고].
- A) x의 디폴트 값이 None인데, x에 요소 개수가 0인 컨테이너가 assign된다면,
not ... is를 사용하지 말고is not을 사용하자. 차이는 없지만 가독성이 높다.# 좋음: if foo is not None:# 나쁨: if not foo is None:- 풍부한 비교(rich comparison) 연산자를 오버로딩할 때에는, 6개의 연산자(
__eq__, __ne__, __lt__, __le__, __gt__, __ge__)[참고]를 모두 구현하자.- 관련된 노력을 최소화하기 위해
functools.total_ordering()데코레이터는 누락된 비교 메서드를 생성하는 도구를 제공한다. - PEP207은 파이썬이 가정하고 있는 반사(reflexivity) 규칙을 나타낸다. 따라서 인터프리터는 임의로
y > x를x < y,y >= x를x <= y로 바꾸고x == y및x != y의 x, y인수를 바꿀 수 있다.sort()및min()은<연산자를 사용하고max()함수는>연산자를 사용하지만, 혼동이 발생하지 않도록 6가지 연산자를 모두 구현하는 것이 가장 좋다.- A) 객체 비교에 어떠한 연산자가 사용될지 모르기때문에 다 구현해두는 것이 정석이다.
- 관련된 노력을 최소화하기 위해
- 람다 표현식을 식별자에 직접 할당하는 대신에 항상 def문을 사용하도록 하자.
# 좋음: def f(x): return 2*x# 나쁨: f = lambda x: 2*x- 첫 번째 형식은 함수 객체의 이름이 익명 ‘lambda’가 아닌, 구체적인 ‘f’임을 의미한다. 이것은 일반적으로 traceback 및 문자열 표현에 더 유용하다. 할당문을 사용하면, 람다식의 유일한 이점(더 큰 표현의 내부에 람다식이 간결하게 포함될 수 있는 이점)이 사라진다.
BaseException이 아닌,Exception에서 상속받아 예외를 만들자.BaseException을 직접 상속하는 예외들은 예약되어 있다.[참고]- A)
BaseException을 상속받은 예외들은KeyboardInterrupt,SystemExit,GeneratorExit등인데, 이는 저수준의 예외(프로그램 종료나 쓰레드 예외 등)를 모두 캐치해야하기 때문이다. 따라서 일반적인 로컬 코드에서는BaseException을 상속받아 저수준 예외를 핸들링 할 경우가 거의 없다.
- A)
- 예외가 “발생”하는 곳이 아니라 “예외 처리가 필요할 것 같은”” 곳에 기반해 예외의 계층 구조를 설계하자. “문제가 발생했습니다”라고만 설명하는 것이 아니라 프로그래밍적으로 “무엇이 잘못 되었는가?”라는 질문에 대답하는 것을 목표로 한다.(예외의 계층 구조에 대해 학습할 수 있는 PEP3151의 예를 참고하자)
- A) 예외를 단순히 흐름 제어를 위한 정보로 취급할 것이 아니라, 문제를 파악하고 해결할 수 있는 도구로 생각해야 한다.
- 예외가 에러일 경우 이름에 “Error” 접미사를 붙여야 하지만, 그 외에는 클래스 네이밍 규칙을 적용하자. 로컬을 벗어난(non-local) 흐름 제어[참고] 또는 기타 신호의 목적으로 사용하는, 에러가 아닌 예외의 이름에는 접미사가 필요없다.
- A) 에러 예외와 에러가 아닌 예외를 구분하는 것이 코드 흐름을 이해하는 데 좋다.[참고]
- 예외 연결(chaining)을 적절하게 사용하자. 파이썬3에서
raise X from Y는 Y의 traceback을 유지하면서, 명시적으로 예외 Y를 예외 X로 대체한다.- A) 예외 traceback 정보는 디버깅을 위해 중요하다.
- 의도적으로 내부 예외를 대체하는 경우(파이썬2에서
raise X또는 파이썬3.3 이상에서Rise X사용) 예외의 세부 정보가 새 예외로 전송되는지 확인(KeyError를AttributeError로 변환 할 때, attribute 이름이 유지되는지 확인하거나, 새 예외 메세지에 기존의 예외 텍스트가 포함되는지 확인하는 등)한다.- A) 예외 traceback 정보는 디버깅을 위해 중요하다.
- 파이썬2에서 예외를 발생시킬 때 구 형식
raise ValueError, 'message'대신 파이썬3 형식인raise ValueError('message')를 사용하자. 괄호를 사용하면 예외의 인수가 많거나 문자열 형식을 포함할 때, 따로 백슬래시같은 줄 연속 문자를 사용할 필요가 없다.- A) 파이썬2는 사장되었다. 파이썬3 문법에 따르자.
- 예외를 캐치할 때, 그냥
exception:로 쓰지 말고 구체적인 예외를 밝히자.# 좋음 try: import platform_specific_module except ImportError: platform_specific_module = Noneexcept:로 예외를 캐치하면, SystemExit 및 KeyboardInterrupt 예외도 캐치하기 때문에 Control-C로 프로그램이 중단되지 않을 수 있고 잡다한 문제가 발생할 수 있다. 만약 에러 시그널을 모두 캐치하고 싶다면,except Exception:을 사용하자. 참고로except:는except BaseException:과 동일하다.- 경험적으로
except:는 아래 두 경우에만 사용하는 것이 좋다.- 예외 핸들러가 traceback을 로깅하거나 출력해야하는 경우; 최소한 사용자는 오류가 발생했음을 알게된다.
- 클린업(자원 해제 등) 코드가 수행되어야 하는데
raise때문에 클린업이 안되고 예외가 상위로 전파되어버리는 경우. 하지만 이 때에는 try…final을 사용하는 것이 더 낫다.- A) 구체적으로 예외를 명시하면, 핸들링하고자 하는 예외의 범위를 명확하게 설정하는데 도움을 준다.
- 캐치된 예외를 이름에 바인딩할 때, 파이썬2.6에 추가된 이름 바인딩 문법을 권장한다.
try: process_data() except Exception as exc: raise DataProcessingFailedError(str(exc))- 이것은 파이썬3에서 지원되는 유일한 문법이며, 쉼표 기반 구 문법의 모호함을 방지한다.
- 운영 체제의 에러를 캐치할 때에는 errno 값을 검사하는 것보다 파이썬3.3에 도입된 명시적 예외 계층을 활용하는 것이 좋다.
- A) errno와 같은 전역 상태값은 관리하기 어렵고 확인하는 것도 자주 까먹는다. 그리고 멀티쓰레드 환경에서도 문제가 있다.
- 또한 모든 try/except 절에서 try 절을 필요한 최소한의 코드로 제한하라. 다시 말하지만 이는 숨겨진 버그를 방지한다.
# 좋음: try: value = collection[key] except KeyError: return key_not_found(key) else: return handle_value(value)# 나쁨: try: # Too broad! return handle_value(collection[key]) except KeyError: # Will also catch KeyError raised by handle_value() return key_not_found(key)- A) 위 예시에서 try 절 안에 코드(함수 호출)가 많기 때문에 KeyError가 아닌 다른 예외가 발생해 캐치하지 못할 수도 있다.
- 특정 코드 범위에 한정된 로컬 리소스는 with 문을 사용하여 신속하고 안정적으로 정리되도록 한다. try/finally 문도 허용된다.
- A) with 문과 같은 컨텍스트 매니저는 리소스 관리를 자동으로 해준다.
- 리소스 획득/해제 외 다른 작업을 하려면, 컨텍스트 매니저[참고]는 항상 별도의 함수 또는 메소드를 통해 호출되어야 한다.
# 좋음: with conn.begin_transaction(): do_stuff_in_transaction(conn)# 나쁨: with conn: do_stuff_in_transaction(conn)- 위 예시 중 후자는
__enter__및__exit__메서드가 하는 작업을 명시적으로 표현하지 못한다. 단지 연결이 닫힌다는 것만 알 수 있다.
- 위 예시 중 후자는
- return 문에서 일관성을 유지하라. 함수에서 모든 return 문은 표현[참고]식을 반환하거나 어떤 것도 반환하지 않아야 한다. return 문이 표현식을 반환하는 경우 값이 반환되지 않는 case는 모두 return None으로 명시 적으로 나타내야하며, 명시적인 return 문이 함수 끝에 있어야 한다.(함수 끝에 도달할 수 있다면).
# 좋음: def foo(x): if x >= 0: return math.sqrt(x) else: return None def bar(x): if x < 0: return None return math.sqrt(x)# 나쁨: def foo(x): if x >= 0: return math.sqrt(x) def bar(x): if x < 0: return return math.sqrt(x)- A) 모든 case에 대해 return을 정확히 명시해야 함수가 의도대로 동작한다.
- string 모듈 대신 string 메소드를 사용하라.string 메소드는 항상 훨씬 빠르고, unicode 문자열을 지원하는 API와 이름이 동일하다. 단, 파이썬 2.0 이상에서만 지원된다.
- 접두사/접미사를 확인하기 위해 슬라이싱하지 말고
''.startswith()와''.endswith()를 사용하라. 명확하고 에러가 덜 발생한다.# 좋음: if foo.startswith('bar'):# 나쁨: if foo[:3] == 'bar':- A) 가독성도 떨어지고 오류의 소지도 크다.
- 객체의 타입 비교는 직접하지 말고 항상 isinstance()를 사용하라
# 좋음: if isinstance(obj, int):# 나쁨: if type(obj) is type(1): - 객체가 문자열인지 확인하려면, 문자열이 유니코드이어야 한다는 것을 늘 명심하라. 파이썬2에서 str과 unicode는 basestring이라는 공통 기본 클래스를 가진다. 따라서
if isinstance(obj, basestring): - 파이썬 3에서는 unicode와 basestring이 더이상 존재하지 않는다. 오직 str만 존재하며, byte 객체는 더이상 문자열의 종류가 아니다.(정수 시퀀스다)
- 시퀀스들(strings, lists, tuples)에 대해, 비어있는 시퀀스는 False라는 사실을 이용하라
# 좋음: if not seq: if seq:# 나쁨: if len(seq): if not len(seq):- A) len()은 중복이며, None의 경우에는 is/is not으로 비교해야 한다.
-
문자열 리터럴을 쓸 때 후행공백을 사용하지 말라. 구분도 안될 뿐더러 어떤 에디터에서는 잘라내버린다.(최근에는 reindent.py를 돌리면 잘라준다)
- Boolean 값을 ==를 사용해서 True/False 비교를 하지 말라
# 좋음: if greeting:# Wrong: if greeting == True:# 더 나쁨: if greeting is True:- A)
- try…finally 내에 있는 흐름 제어문(return/break/continue)이 try..finally 외부로 점프한다면 사용을 하지마라. 왜냐하면 그런 제어문들은 암시적으로 취소되고 finally를 통해 전파되면서 예외를 발생시키기 때문이다.
# Wrong: def foo(): try: 1 / 0 finally: return 42
함수 주석
- PEP484에 따라 함수 주석의 스타일은 계속 업데이트되고 있다.
- 앞으로의 호환을 위해, 파이썬3의 함수 주석은 PEP484 문법을 따라야 한다.(앞에서 관련된 내용을 조금 언급했었다)
- 이전에 PEP8에서 권고했던 주석 스타일을 실험하는 것은 이제 권고하지 않는다.
- 그러나 stdlib 밖, PEP484 내에서의 실험은 권고 된다. 예를 들어 PEP484를 적용한 큰 규모의 써드파티 라이브러리나 어플리케이션을 체크하는 것, 얼마나 주석을 추가하기 쉬웠는지 리뷰하는 것, 그리고 그들의 존재를 관찰하는 것은 코드에 대한 이해를 높인다.
- 파이썬 표준라이브러리는 그런 주석을 적용하는데 보수적이어야 하지만, 새로운 코드가 추가되거나 대규모 리팩토링을 한다면 적용할 수 있다.
- 함수주석을 다르게 사용하려면 다음 형식의 주석을 넣는 것이 좋다.
# type: ignore- 파일의 맨 앞부분에 넣는다. 이는 타입체커에 모든 주석을 무시한다고 알려준다. 타입체커를 끄는 더 자세한 방법은 PEP484에 있다)
- 린터와 마찬가지로 타입체커는 선택사항이며 별도의 도구다. 기본적으로 파이썬 인터프리터는 타입 검사를 하지 않는다. 주석을 기반으로 동작이 변경되면 안된다.
- 타입체커를 사용하지 않는 사용자는 신경쓰지말라. 하지만 써드파티 라이브러리를 사용하기 전에 타입 체커를 돌리면 유용할 수 있다. 이를 위해 PEP484는 스텁파일(해당 .py파일에 우선하여 타입체커가 읽는 .pyi파일) 사용을 추천한다. 스텁파일은 라이브러리와 함께, 또는 독립적으로(라이브러리 작성자의 승인 하에) typeshed 리포지토리를 통해 배포될 수 있다.
- 이전 버전과 호환되어야하는 코드의 경우, 타입 주석은 주석(Comments)형태로 추가 될 수 있다. PEP484 관련 섹션을 참고하라.
변수 어노테이션
- PEP526은 변수 어노테이션을 소개한다. 함수 어노테이션과 비슷하다.
- 모듈 수준의 변수들,클래스,인스턴스 변수들,로컬 변수들의 어노테이션은 콜론 뒤에 1개의 공백을 가져야 한다. 콜론 전에는 공백이 없어야 한다. 대입이 오른쪽에 있으면 = 기호 양쪽에는 하나의 공백이 있어야 한다.
# 좋음: code: int class Point: coords: Tuple[int, int] label: str = '<unknown>'# 나쁨: code:int # No space after colon code : int # Space before colon class Test: result: int=0 # No spaces around equality sign - PEP526은 파이썬 3.6에 허용되지만, 변수 어노테이션 문법은 모든 파이선 버전의 스텁파일에서 선호되는 문법이다.(상세 내용은 PEP484를 보라)