こんな人にオススメ
pythonについて調べていると「PEP8」っていう規約が出てくるんだけど、あれって何?規約だから守らないといけないの?
あと、守るんならどうすればいい?
ということで、今回はpythonにおけるコーディング規約である
PEP8
と、コードの文法チェックツールであるflake8
について紹介する。後半では執筆者が個人的によく使うかなと思ったflake8
の項目について紹介する。PEP8
が決まり事で、それをチェックする道具がflake8
という認識。
実はこれらのスタイル・ツールは執筆者が配属されていた研究室では一切気にされておらず、いざ従うと今までのコードを全て改修する必要があった。一方で、大学院を修了して新しいPCとしてM1 Macbook Proを迎え入れたのだが、この時のpython構築でこれらを気にすることで少しでもコーディングをキレイに、良くしようと思った。
研究室のことを少しだけ書いている、執筆者の半生については以下参照。
-
-
【M天パ(めがてんぱ)自己紹介】変わって楽しいの繰り返し
続きを見る
これからpythonを始める方、少しかじった方、結構習得したけどこんなこと考えたことなかったという方も、今回の記事をきっかけにコード整形をしてみてはいかがだろうか。
PEP8
はじめにPEP
について説明する。その後PEP8
についての説明をする。
PEP
そもそもPEP
とは「Python Enhancement Proposal」の略で、直訳すると「pythonの拡張提案」とかになる。pythonの設計書的なもので、公式サイトがめちゃくちゃ長く最後までスクロールするのがめちゃくちゃ大変。
自分も含めた初心者はPEP
全体については深く考えなくていいのかなぁ...
PEP8
深く考えなくてもいいのかなと言った矢先だが、PEP8
については簡単で守ったほうが後々が楽。PEP8
はPEP
の8番目で「Style Guide for Python Code」という名称で、「コードは書いている時間よりも読まれている時間の方が長いので、スタイルを合わせて読みやすくしよう」ということ。
flake8
次にflake8について説明する。
flake8
とは
flake8
とはPEP8
でのコードをチェックしてくれるツールのこと。PEP8
はただのルールで、実際に自分のコードではルールに則っているのかについては各自で確かめる必要がある。そこでいちいち目で追っていたのでは時間も力も使う。なら機械にさせればいいじゃない。
E1
:インデントに関するエラー
ルール自体は「Flake8 Rules」に載っているが、なんせ数が多い。ということで執筆者の独断と偏見で以下のように抽出した。コードはFlake8 Rulesから引用している。なお、→
はタブを示す。
Indentation contains mixed spaces and tabs (E101)
インデントにタブとスペースが混在してはいけない。
# anti def get_name(self): if self.first_name and self.last_name: ••••→ → return self.first_name + ' ' + self.last_name else: return self.last_name # best def get_name(self): if self.first_name and self.last_name: ••••••••return self.first_name + ' ' + self.last_name else: return self.last_name
Indentation is not a multiple of four (E111)
インデントで使用するスペースの数が4の倍数である方が良い。
# anti class User(object): def __init__(self, name): self.name = name # best class User(object): def __init__(self, name): self.name = name
Expected an indented block (comment) (E115)
インデントされたコードにコメントを書く際に、コメントがインデントされてない。
# anti def start(self): if True: # try: # self.master.start() # except MasterExit: # self.shutdown() # finally: # sys.exit() self.master.start() # best def start(self): if True: # try: # self.master.start() # except MasterExit: # self.shutdown() # finally: # sys.exit() self.master.start()
Continuation line missing indentation or outdented (E122)
コード1行を改行する際に、残りのコード(継続行)がインデントされていない、もしくはされすぎている。
# anti print("Python", ( "Rules")) # best print("Python", ( "Rules"))
Closing bracket does not match indentation of opening bracket's line (E123)
閉じカッコは開きカッコと同じインデントレベルにする。
# anti result = function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', ) # best result = function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', )
Continuation line with same indent as next logical line (E125)
継続行のインデントレベルが次の行のインデントレベルと同じ時は、継続行のインデントレベルを上げる。
# anti if user is not None and user.is_admin or \\ user.name == 'Grant': blah = 'yeahnah' # best if user is not None and user.is_admin or \\ user.name == 'Grant': blah = 'yeahnah'
E2
: スペースに関するエラー
Whitespace after '(' (E201)
開きカッコの後にはスペースを入れない。
# anti with open( 'file.dat') as f: contents = f.read() # best with open('file.dat') as f: contents = f.read()
Whitespace before ')' (E202)
閉じカッコの前にはスペースを入れない。
# anti with open('file.dat' ) as f: contents = f.read() # best with open('file.dat') as f: contents = f.read()
Whitespace before ':' (E203)
コロンの前にスペース入れない。
# anti with open('file.dat') as f : contents = f.read() # best with open('file.dat') as f: contents = f.read()
Whitespace before '(' (E211)
開きカッコの前後にスペースを入れない。
# anti with open ('file.dat') as f: contents = f.read() # best with open('file.dat') as f: contents = f.read()
Multiple spaces before operator (E221)
演算子の前にスペースは1つだけ。
# anti num = 10 doubled = num * 2 # best num = 10 doubled = num * 2
Multiple spaces after operator (E222)
演算子の後にはスペースは1つだけ。
# anti num = 10 doubled = num * 2 # best num = 10 doubled = num * 2
Tab before operator (E223)
演算子の前にはスペースを1つだけ入れる。
# anti if x→in [1, 2, 3]: print(x) # best if x in [1, 2, 3]: print(x)
Tab after operator (E224)
演算子の後にはスペースを1つだけ入れる。
# anti if x in→[1, 2, 3]: print(x) # best if x in [1, 2, 3]: print(x)
Missing whitespace around operator (E225)
全ての演算子の前後にスペースが1つ必要。
# anti if age>15: print('Can drive') #best if age > 15: print('Can drive')
Missing whitespace around arithmetic operator (E226)
算術演算子+
, -
, /
, *
の前後にはスペースを1つ入れる。
# anti age = 10+15 # best age = 10 + 15
Missing whitespace around modulo operator (E228)
%
の前後にはスペースが1つ必要。
# anti remainder = 10%2 #best remainder = 10%2
Missing whitespace after ',', ';', or ':' (E231)
,
;
, :
の後にはスペースを入れる必要がある。
# anti my_tuple = 1,2,3 # best my_tuple = 1, 2, 3
Unexpected spaces around keyword / parameter equals (E251)
関数定義時の=の前後にはスペース入れない。
# anti def func(key1 = 'val1', key2 = 'val2'): return key1, key2 # best def func(key1='val1', key2='val2'): return key1, key2
At least two spaces before inline comment (E261)
インラインコメントの前には少なくとも2つのスペースが必要。
# anti def print_name(self): print(self.name) # This comment needs an extra space # best def print_name(self): print(self.name) # Comment is correct now
Inline comment should start with '# ' (E262)
インラインコメント#
の後にはスペースが1つ必要。
# anti def print_name(self): print(self.name) #This comment needs a space # best def print_name(self): print(self.name) # Comment is correct now
Block comment should start with '# ' (E265)
ブロックコメントの前には1つのスペースが必要
# anti #This comment needs a space def print_name(self): print(self.name) #best # Comment is correct now def print_name(self): print(self.name)
Too many leading '#' for block comment (E266)
ブロックコメントの先頭の#
は1つだけ。
# anti ## Prints hello print('hello') #best # Prints hello print('hello')
Tab after keyword (E273)
キーワードの後にはスペースが1つ必要。
# anti def→func(): pass # best def func(): pass
Tab before keyword (E274)
キーワードの前にはスペースが一つ必要。
# anti def func(): if 1→in [1, 2, 3]: print('yep!') # best def func(): if 1 in [1, 2, 3]: print('yep!')
Missing whitespace after keyword (E275)
キーワードの後に1つのスペースが必要。
# anti from collections import(namedtuple, defaultdict) # best from collections import (namedtuple, defaultdict)
E3
: 空白行に関するエラー
Expected 1 blank line, found 0 (E301)
クラスのメソッド間には1行の空白行が必要。
# anti class MyClass(object): def func1(): pass def func2(): pass #best class MyClass(object): def func1(): pass def func2(): pass
Expected 2 blank lines, found 0 (E302)
関数とクラスの間には2行の空白行が必要。
# anti def func1(): pass def func2(): pass #best def func1(): pass def func2(): pass
E4
: import
に関するエラー
Multiple imports on one line (E401)
1行で複数のimport
が行われている。
# anti import collections, os, sys #best import collections import os import sys
Module level import not at top of file (E402)
モジュールレベルのimport
がファイルの先頭以外で行われている。
# anti import locale locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') import sys #best import locale import sys locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
E5
: 1行の文字数に関するエラー
Line too long (82 > 79 characters) (E501)
1行の最大文字数は79文字。最大文字数を100もしくは120に変更することは一般的。
E7
: ステートメントに関するエラー
Statement ends with a semicolon (E703)
文章の最後にセミコロンはいらない。
# anti print('Hello world!'); # best print('Hello world!')
Multiple statements on one line (def) (E704)
関数定義は一行で書く。
# anti def f(): pass # best def f(): pass
Comparison to true should be 'if cond is true:' or 'if cond:' (E712)
True
と比較する際に透過演算子==
を使用しない。
# anti x = True if x == True: print('True!') # best x = True if x is True: print('True!') # or simply: x = True if x: print('True!')
Do not compare types, use 'isinstance()' (E721)
型の比較にはisinstance
を使用する。
# anti if type(user) == User: print(user.name) # best if isinstance(user, User): print(user.name)
Do not assign a lambda expression, use a def (E731)
ラムダ式は変数に割り当てない。
# anti root = lambda folder_name: os.path.join(BASE_DIR, folder_name) # best def root(folder_name): return os.path.join(BASE_DIR, folder_name)
Do not use variables named 'I', 'O', or 'l' (E741)
l
, O
, I
という変数名は紛らわしいので使用しない。
#anti O = 100.0 total = O * 1.08 # best order = 100.0 total = order * 1.08
Do not define classes named 'I', 'O', or 'l' (E742)
l
, O
, I
というクラス名は紛らわしいので使用しない。
Do not define functions named 'I', 'O', or 'l' (E743)
l
, O
, I
という関数名は紛らわしいので使用しない。
W1
: インデントに関する警告
Indentation contains tabs (W191)
タブでインデントしない。
# anti def get_name(self): if self.first_name and self.last_name: → → return self.first_name + ' ' + self.last_name else: return self.last_name #best def get_name(self): if self.first_name and self.last_name: return self.first_name + ' ' + self.last_name else: return self.last_name
W2
: スペースに関する警告
Trailing whitespace (W291)
行の最後にスペースがあってはいけない。
# anti def first_func(): # The line below has two spaces after its final character pass••
No newline at end of file (W292)
ファイルは改行を入れて終わる。
# anti import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) #best import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # This is a new line that ends the file.
Blank line contains whitespace (W293)
空白行にはタブやスペース入れない。
# anti def first_func(): pass •••• # This line contains four spaces def second_func(): pass # best def first_func(): pass def second_func(): pass
W3
: 空白行に関する警告
Blank line at end of file (W391)
ファイルの最後の改行は1回だけ。
# anti class MyClass(object): pass # best class MyClass(object): pass
W5
: 改行に関する警告
Line break occurred after a binary operator (W504)
二項演算子の前に改行を入れる(W503は逆を言っているが、W503は現在非推奨)。
# anti income = (gross_wages + taxable_interest) #best income = (gross_wages + taxable_interest)
W6
: 廃止された構文について
Invalid escape sequence 'x' (W605)
バックスラッシュ\\
だけ使用しない。
# anti regex = '\\.png$' # best regex = r'\\.png$'
pyflakes
関連
Module imported but unused (F401)
使わないモジュールはimport
しない。
# anti from collections import namedtuple my_tuple = ('Grant', 'McConnaughey', 25) # best my_tuple = ('Grant', 'McConnaughey', 25)
Import module from line n shadowed by loop variable (F402)
ループ変数にモジュール名を指定しない。
# anti from os import path for path in ['file1.py', 'file2.py']: print(path) # best from os import path for file_name in ['file1.py', 'file2.py']: print(file_name)
'from module import *' used; unable to detect undefined names (F403)
from module import *
だとオブジェクトがどこでimport
されたのかが分かりにくいので使用しない。モジュール、クラス、関数は明示的に定義する。
# anti from users.models import * from auth.models import * user = User.objects.get(name='Grant') # best from users.models import * from auth.models import * user = User.objects.get(name='Grant')
Name may be undefined, or defined from star imports: module (F405)
*
インポートをしない。代わりに明示的にするか変数を置く。
#anti from mymodule import * def print_name(): print(name) # name could be defined in mymodule # best from mymodule import name def print_name(): print(name) # or define name. from mymodule import * def print_name(name): print(name)
Redefinition of unused name from line n (F811)
モジュールは1回のみのimport
。
# anti import json import os import json # best import json import os
List comprehension redefines name from line n (F812)
リスト内包表記での変数名とその他の変数名は別にする。
# anti i = 1 squares = [i ** 2 for i in range(10)] # best i = 1 squares = [num ** 2 for num in range(10)]
Local variable name is assigned to but never used (F841)
関数内のローカル変数が定義したなら使用する。
# anti def full_name(self): # name is defined but not used name = self.first_name + ' ' + self.last_name return self.first_name
執筆者の実際の設定
最後に実際に執筆者がどのように運用しているのかについて紹介する。
無効にしているものもある
執筆者は普段pythonコードは「Visual Studio Code」というエディタを使用している。このエディタの設定でPEP8
の規約違反をチェック、そして自動修正を行うことができるように設定している。
しかし、最初にも書いたがM1 Macbookを迎え入れるまでろくに規約を考えていなかったので、コードを自動修正されると自分が理解できなくなる。したがって、一部の規約に関しては無効にしているものもある。執筆者がVisual Studio CodeのJSON設定で実際に使用している項目を示す。
"python.formatting.autopep8Args": [ "--ignore=E402, E401, E501, F403, F405, W503", ], "python.linting.flake8Args": [ "--ignore=E402, E401, F403, F405, W503", ],
まず、"python.formatting.autopep8Args"
は規約違反の部分を自動修正、"python.linting.flake8Args"
は規約違反のコード部分を赤の波線で示してくれる。--ignore
の後に上述の記号+番号を入れることで、autopep8
での自動修正やflake8
の赤波線を消すことができる。
執筆者がautopep8
、flake8
で無効にしている項目とその理由は以下。
E402
(import
をファイル先頭以外してる):テンプレートを他のディレクトリから呼ぶ際に無視してしまうからE401
(import
を一行にしない):以前は1行に凝縮していた。解除してもいいと思っているE501
(79文字制限):自動修正されると昔のコードがめちゃくちゃになるので検出のみF403
(from module import *
は使用しない):便利なんだもん...F405
(*
インポートは使用しない):便利なんだもん...W503
(W504
の逆で、演算子の後に改行を入れる):W504
と逆の意見だから
Visual Studio CodeにPEP8やflake8を認識させたりするのは他のサイトを自分で調べるか、もしくはいつの日か本ブログでも書くかもしれないのでそれまで待っていただくかしてもらいたい。
ルールを守って楽しくデュ...コーディング
今回はとても長くなってしまい申し訳ない。しかし、本記事で取り上げた内容はまだ一部で、まだまだ多くのルールが存在する。その全てに従うのが理想だろうが本記事で紹介した内容だけでも守るとより読みやすく、統一されたコードになるのではないでしょうか。
最後に参考にさせていただいたサイトについて下記に記します。とても参考になりました。