Open11
PEP8を読む
PEPとは
PEP(Python Enhancement Proposal)は,pythonの言語の仕様を変更する際に行われる議論の記録のこと.
例えば,「相対インポートの追加」や「python2からpython3への変更」などでそれぞれPEPがあるらしい.
議論の記録なので,なぜその案を採用するに至ったのかや却下された案も示してある.
中でも,とりわけ有名なpythonのコーディング規約に関するPEPとして,PEP8がある.
はじめに
- CPythonのC言語の規約は,PEP7を参照すべし
- 時代遅れになったら変更しますよ
- プロジェクト内での規約と齟齬がある場合は,プロジェクトを優先してね
一貫性にこだわりすぎるな
- コードは書くより読まれる機会が圧倒的に多いので,きれいに書くのは当然
- 一貫性の重要順は,(本コーディング規約)<(プロジェクト内)<(モジュール・関数内)と認識すべき
- 迷ったときには,他の例を見て,最終的には自分で判断せい
- 後方互換性を失うようなことは特にやめてね
- ある規約を無視していいときの理由が複数挙げられていますが,省略...
レイアウトに関して
インデント
- インデントごとにスペース4つ使う
- "{}, (), []"こいつらは,勝手に改行しても怒られないので,これを利用する
# インデントをそろえる
foo = long_function_name(var_one, var_two,
var_three, var_four)
# インデントを深く(のちのコードと区別するため)
def 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)
- if文の条件が長くなる場合,明確な立場は示さないが以下のような書き方が可能.
# そのまま(下のコードと区別がつきにくく微妙かも)
if (this_is_one_thing and
that_is_another_thing):
do_something()
# コメントを追加
if (this_is_one_thing and
that_is_another_thing):
# 両方trueのときの処理
do_something()
# 深いインデントをしておく
if (this_is_one_thing
and that_is_another_thing):
do_something()
- "{}, [], ()"の閉じ方は以下のいずれか
# 要素に合わせる
my_list = [
1, 2, 3,
4, 5, 6,
]
# 端っこにおく
my_list = [
1, 2, 3,
4, 5, 6,
]
タブ or スペース?
- (スペース)>(タブ)で,タブを使用するのは,すでに使用されているコードと一貫性を保つ時だけ
- まちっがてもタブとスペースを混ぜるな
1行の長さ
- docstringやコメントなどは72文字までで,他は最大79文字まで
- たいていのツールのデフォルトの折り返しでは見た目が悪くなるので,この文字数がいい
- どうしても長くしたいなら,制御部分は「99文字までは認めるが,docstringやコメントの72文字は譲らない
- 途中で改行が必要な場合は,"{}, [], ()"のこいつらを使うことでこそっと(暗黙的に)改行できる
- withやassertなどの勝手に改行しても認識してくれない派閥のやつは,仕方なくバックスラッシュを使う(この際,インデントには注意しておくこと)
with open('パス1') as file_1, \
open('パス2') as file_2:
file_2.write(file_1.read())
改行は,2項演算の前 or 後?
- かつては,演算子の後で改行するスタイルが推奨されてきたが,読みにくい
- 演算子の前で改行すると,演算子のインデントを統一しやすい.
- プロジェクト内で統一されていればどちらでもいいが,後者(演算子の前で改行)を推奨
空行
- トップレベル関数やクラスは2行ずつ空ける
- クラス内部では,1行ずつ空けてメソッドを定義する
- 関数内の関連を示すために,2行を使用することも許容
- ctrl + Lの話があったがよくわからなかった(後で調べる)
エンコード
- 常にutf-8を使用すべき
- utf-8以外のエンコードを使用するのは,テスト目的に限る
- ASCII以外の文字は,場所や人名に使用すべき
- 他のデータを使用するにしても,ノイズの多いUnicodeなどは避けるべき
import
- 行は分けるべきだが,fromのときはok
# これはだめ
import os, sys
# これはok
from sys import path, argv
- docstring[1]の直後のファイルの先頭に置くべき
- えらい順(標準ライブラリ→外部ライブラリ→ローカルのもの)
- 基本は絶対importを使用するべきだが,冗長になりすぎる場合は,相対importも可
- fromで絶対importしてもいいが,名前の衝突が起きるのであれば直接べた書きに変更する
# これでは衝突している
from subpackA import MyClass
from subpackB import MyClass
# 以下のようにする
import subpackA
import subpackB
- ワイルドカード(*)のimportはどの名前が使用されたかがわからないので,通常はさけるべき(許容されるケースもあるが自分には関係なさそうなのでいったん無視)
二重のアンダースコアの位置
- (docstring) → (
from __future__
)→ (他のimport文の前)の順
"""
this is docstring
"""
from __future__ import ....
__all__ = ['a', 'b', 'c']
import sys
[1]:関数やモジュールの仕様を書いたトリプルクウォートで囲まれた文字列
-
1 ↩︎
文字列
- pythonでは,「'」と「"」は同じでどちらが良いということもないので,自分で統一して使え
- 「'」か「"」が文字列に含まれていたら,もう一方を使うべき
- トリプルクォートは,docstringのルールと合わせるため「"」のみを使うべき
式と文中のスペース
- 余計なスペースはあんまいれんな
- [], {}, ()のはじめとおわり
# ok
spam(ham[1], {eggs: 2})
# ng
spam( ham[1], { eggs: 2 } )
- 末尾のカンマとその後のかっこの間
# ok
foo = (0,)
# ng
bar = (0, )
- カンマ,セミコロン,コロンの直前
- スライスでは注意(演算子として扱われるので,両側の数と同じスペースをとり,数字が省略された場合は,スペースも省略)
# ok
ham[1:9], ham[1:9:3], ham[1::3], ham[1:9:]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower+offset : upper+offset]
ham[lower + offset : upper + offset]
# ng
ham[1: 9], ham[1 :9], ham[1:9 :3] # スペースが等間隔でない
ham[lower : : upper] #スペースの省略がない
ham[ : upper] # スペースの省略がない
- 関数呼び出し時の,引数を入れる()の直前
# ng
spam (1)
- インデックスの[]の直前
- 演算子の前後にはスペース1つだけ
その他
- 行末に余計なスペースを入れない(わからないのでバグの温床になりかねない)(バックスラッシュの後にスペースを入れると挙動がかわるので,プロジェクト内でコミット前に拒否するように設定してあったりする)
- 代入(=)やその拡張(+=, -=),比較,ブール演算子(and, or, not)の前後は1つスペース入れる
- 複数の演算子を扱う場合は,優先順位が一番低い演算子の両側にスペースを入れることを考える(任意)
# ok
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b) #ここでは,*がもっとも優先順位が低い
- 関数アノテーションは,コロンルール(直前に余計なスペースをいれない)を守り,アロー演算子(->)はその両端にスペース
# ok
def munge(input: AnyStr):
def munge() -> PosInt:
- アノテーションされていない関数のkwargsやデフォルトを示す=の両側にスペースをいれない
# ok
def complex(real, imag=0.0):
return magic(r=real, i=imag)
- アノテーションでデフォルト値と組み合わせるときは,=の両端にスペースをいれる
# ok
def munge(sep: AnyStr = None):
def munge(input: AnyStr, sep: AnyStr = None, limit=1000):...
- 一般に一行に複数文を書くことは非推奨(短文とif/for/whileを同じ行に置くことがokな場合もあるが,複数文はやめるべき)
# ng
if foo == 'blah': do_blah_thing()
末尾にカンマをつけるべき場合
- 通常は任意だが,以下の2つの場合は便利
- 要素が一つのタプルのとき(ないと混乱する)
# ok
FILES = ('setup.cfg', )
# ng
FILES = 'setup.cfg',
- [], {}, ()の要素が増えていくことが分かっている場合
#ng
intialize(FILES, error=True,)
コメント
- コードと矛盾する内容にするくらいなら書くな
- 完全な文で,文頭は大文字,文末はピリオドで複数の文を書く
- 変数名の大文字小文字は変更してはいけない
- 最後の文以外は,ピリオドの後に二つスペースを入れるべき
- 他国の人に読まれないという確信がなければ,英語で書くべき
ブロックコメント
- 適用したいレベルのコメントと同じレベルのインデントで,#とスペース1つで始め,その後に続くコードのいくつか or すべてに適用される
インラインコメント
- コードと同じ行に各コメントのことで,あんま使いすぎない方がいい
- 少なくともコードとの間に2つのスペースを置くべき
- 自明なことは邪魔なのでいちいち書かない
docstrings
- PEP 257にまとめられているので読んでね
- すべてのオープンなモジュール,クラス,メソッドのdocstringを書くべき
- defの後の行に書き,複数行の場合は,**「””””だけの行」**で閉じることに注意
命名規則
- 新しいモジュールやパッケージはこの規約に従って書くべき
- 既存のライブラリが異なる書き方をしている場合は,一貫させることが望ましい
最重要原則
- ユーザ側から見えるAPIは,実装よりも使い方を反映した名前にすべき
実践される命名方法
- いろいろな方法があり,
- lowercase
- loser_case_with_underscores
- UPPERCASE
- UPPER_CASE_WITH_UNDERSCPRES
- CamelCase(or CapWords):この場合,HttpServerErrorではなく,HTTPServerErrorとした方が良いらしい(個人的には,UpperCamelCaseと呼んでいた)
- mixedCase (こちらは,lowerCamelCaseと呼んでいた)
- Camel_Case_With_Underscoresこれは使わんほうがいいらしい
- 他にも,
os.start()
にst_mode, st_size, ...
のようにstというprefixがつくことがあるが,この部分を扱うのに慣れたプログラマが扱いやすいようにされていたり?(誤解釈かも)するらしい
アンダースコアらへん
- 先頭のアンダースコア1つ:内部だけで使うことを示す.
from M import *
としたときに呼び出されない. - 後ろのアンダースコア1つ:pythonの予約語との衝突を避けるために使用
tkinter.Toplevel(master, class_='ClassName') #classは予約語のため
- 先頭のアンダースコア2つ:マングリング機構と呼ばれ,呼び出し方が特殊.(FooBarクラスの__booは,_FooBar__booと呼び出す)
- アンダースコアを2つ:マジック属性と呼ばれる.すでにあるものだけを使い,新たにつくろうなどと思い上がらないこと
プログラミングに関する推奨事項
- 他の実装(PyPy, Jython, CPythonなど)で不利にならないような書き方を心がけるべき
-
a += b
やa = a + b
のような効率的な実装は,CPythonでは速いが,リファレンスカウントがないpython実装には存在しないため,使用しない - ライブラリのパフォーマンスに敏感な部分は
''.join()
を使うべきで,これは多くのpython実装において,文字列の結合が線形時間で終わることを保証するため
-
- Noneは,
is
やis not
を使用するべき-
if x is not None
とif x
に注意で,前者はNoneのみがFalseとなるが,後者はブール型の判定に依存してしまうため,NoneでなくてもFalse判定になる可能性をはらむ -
not ... is ...
はわかりにくいので使わず,```is not``を使え
-
関数アノテーション
- PEP484で導入されたため,これを参照するのが一番
- 標準ライブラリ以外では,PEP484のルールの範囲内で実験することが推奨されており,大規模な外部ライブラリに型アノテーションを追加することで,この作業が簡単であったかやコードの理解度向上につながるかを確認するのもよい
- 以下のコメントをファイルの先頭に書くことで,型チェックのプログラムを無視できる
# type: ignore
- linterや型チェックを行うプログラムはpythonインタプリタとは別のツールであり,使用するかどうかも任意
- 型チェックが嫌なら無視してもいいが,外部ライブラリの使用するユ)ザが型チェックを行いたいと思うかもしれないので,スタブファイル(.pyファイルに対応する型チェッカーが読み取る.pyiファイル)の使用を推奨
- 拡張比較で並び替えを実装する場合(本来定義されていないオブジェクト同士の演算など)は,すべての演算(eq, ne, ...)を実装するべき(ぬけがないように,
functools.total_ordering()
デコレータを使用することで,存在しない比較メソッドは自動生成してくれる) - PEP207によると,pythonは反射律を想定しているが,念のため6つの演算すべてを実装するのがベストとされている(
x < y
とy > x
は本来等価であるはずだが,これを想定していないような場面に対処するためと思われる) - ラムダ式は文字に代入するのではなく,常にdef文を使え(この理由も後々)
# ok
def f(x): return 2*x
# ng
f = lambda x: 2*x
-
BaseException
ではなく,Exception
から例外を派生させろ(まだ関係なさそうなので後々) - 例外チェインを適切に使え(後々)
- 例外は特定の例外を指定しろ(できるだけ生のexcept:は使用しない)()
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
- コードの特定の部分で使用されるときは,後始末できるようにwithを使用するべき
- return分は一貫しておくべき.つまり,returnをある分岐で書いたならば,関数の中ではあらゆる分岐でreturnを書くべき.そうでないなら全く書かないようにするべき.
def foo(x):
if x >= 0:
return math.sqrt(x)
else:
return None #ここも書くべき点に注意
- 文字列に所望の接頭辞や接尾辞があるかを確認するには,スライスではなく
''.startswith()
や''.endswith()
を使え(ミスが起こりにくく,かつ見やすくなるため)
# ok
if foo.startswith('bar'):
# ng
if foo[:3] == 'bar':
- オブジェクトの型の比較は,型の直接比較でなく
.isinstance(obj, int)
を使え
# ok
if isinstance(obj, int):
# ng
if type(obj) is type(1):
- シーケンス文字列(リスト,タプル,文字列)は空シーケンスがFalseを利用
# ok
if not seq:
if seq:
# ng
if len(seq):
if not len(seq):
- 行末が空白文字の文字列を書かない(視覚的にもわかりにくいし,エディタによっては勝手に消してしまうこともあるため)
- ブール型は
==
で比較しない
# ok
if greeting:
# ng
if greeting == True:
if greetin is True:
-
try ... finally
で,finallyの外に出るための制御構文(braak, return, continueなど)を書くな(finallyから伝播する例外を無視してしまうため)
# ng
def foo():
try:
1 / 0
finally:
return 42
変数アノテーション
- PEP 526で導入され,使い方は関数アノテーションと似ており,スペースに対するルールなども上記と同様
code: int
class Point:
coords: Tuple[int, int]
label: str = '<unknown>'