2016/06/29更新

[Python] コーディング規約(PEP8)を学んで、Pythonらしいコードを書く

このエントリーをはてなブックマークに追加            

こんにちは、@yoheiMuneです。
Pythonを良く書いていますが、独特な記法も含めてなかなか綺麗な書き方が身につきませんでした。そこで腰を据えてPEP8(Style Guide for Python Code)を学びました。学ぶとやっぱりいいですね、今まで悩んでいたこともすっきりして、綺麗に統一感あるコードが徐々に書けるようになってきました。今日は復習がてら、Pythonのコーディング規約で注意すべきところを中心に、ブログにまとめたいと思います。

画像

目次




参考資料

まずはじめに参考資料ですが、Pythonのコーディングルールを学ぶために、以下の情報を参照しています。このブログでは以下の一部しか扱わないので、他にもきになるところがありましたら、下記リンクをぜひご参照ください。

PEP 8 -- Style Guide for Python Code(英語)

pep8-ja 1.0 ドキュメン(日本語)



前提のルール

PEP8(Python用スタイルガイド)では、前提となる考え方があります。それは、

一貫性にこだわりすぎるのは、狭い心の現れである


です。以下で色々とルールを紹介しますが、「ガイドラインに従うとコードが読みづらくなる」「ガイドラインのサポート対象が古い」「ガイドラインに従うことで既存コードの一貫性を崩す」など従わない方が良い場合には、チームや個人の判断でガイドラインを意識的に無視することもOKとされています。
「ルールにこだわりすぎないように」というのが前提ルールです。



インデント

どのプログラム言語でもインデントを揃えることは重要ですが、Pythonでは特に以下を気をつけると良さそうです。


行を継続する場合は、折り返された要素を縦に揃える

関数の引数定義などのインデント方法の一つで、折り返した要素にインデントを揃えると綺麗に書くことができます。
# 開きカッコで揃える(関数呼び出しの例)
foo = 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文の場合には後続の行との違いがわからないので、以下にしてもOK
# 継続された行の条件をさらに深くインデントする
if (this_is_one_thing
        and that_is_another_thing):
    do_something()
このルールの場合、以下のようなインデントの取り方はルール違反で、可読性を下げます。
# NG:折り返された要素を縦に揃えていない
foo = long_function_name(var_one, var_two,
    var_three, var_four)


この行とそれ以外を区別するために、インデントを深くする

ひきすうの1つ目から行を変える場合には、インデントを深くすることで、読みやすくすることができます。
# インデントを深くする(関数定義の例)
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)
このルールの場合、以下はルール違反となります。
# NG:折り返したところのインデントが深くなく、他の行と区別できない
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

行を継続して波カッコ/ブランケット/カッコを閉じる

行を継続して波カッコ/ブランケット/カッコを閉じる場合は、閉じカッコの位置は以下2つのどちらかにします。
# パターン1:改行後のインデントに合わせる場合
my_list = [
    1, 2, 3,
    4, 5, 6
    ]

result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f'
    )
# パターン2:最初の行のインデントに合わせる
my_list = [
    1, 2, 3,
    4, 5, 6
]

result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f'
)



1行の長さは79文字、72文字

1行の長さは、 最大79文字以内に制限します。また、docstringやコメントのように構造制限が少ないものは、72文字以内に制限します。
ただし、チームで合意できればそれより長くてもOKです。



演算子の位置

2項演算子を前に書くか後に書くかはプログラム言語や個人の好みにもよりますが、Pythonのスタイルガイドでは前に置くことが推奨されています。
# 2項演算子は前に揃える
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)
こちらの方が演算子が一覧で見えてわかりやすいですね。



空行のルール

処理の間やメソッド間でどれだけ空行を取るかは悩みどころですが、コーディング規約では以下のルールが推奨されています。

  • トップレベルの関数やクラスは、2行ずつ空けて定義する
  • クラス内部では、1行ずつ空けてメソッドを定義する
  • 関連する関数のグループを分けるために、2行以上開けてもOK(ただし控えめに)



import文のルール

import文もいろいろな書き方ができますが、以下のルールで書くとわかりやすいですし、バグを生む可能性を減らすこともできます。


importはモジュールごとに行を分ける

sysosdatetimeモジュールをインポートすることもよくありますが、以下のようにそれぞれ別行で記述します。
# 良い例
import os
import sys

# 悪い例
import os, sys    
ただし、同じパッケージから複数のモジュールをインポートする場合には、列挙する書き方が推奨されています。
# 同パッケージから異なるモジュールをインポート
from subprocess import Popen, PIPE


絶対importを推奨する

相対インポートでも絶対インポートでもかける場合には、可読性向上のため、絶対インポートを利用します。
# 絶対importを推奨
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
ただし、絶対importを使うと不必要に冗長になる複雑なパッケージレイアウトの時は、相対インポートの利用もOKです。
# 絶対importを使うと不必要に冗長になる複雑なパッケージレイアウトの時は、相対インポートの利用もOK
from . import sibling
from .sibling import example


ワイルドカードを使ったインポートは絶対に避けたい

ワイルドカードを使って、すべてのモジュールをインポートできますが、これはバグを生みやすいため、避けるべきです。バグになる例として、

  • ワイルドカードインポートをして、知らないうちに変数名を上書きしてしまった
  • ワイルドカードインポートで、モジュールのバージョンアップした場合に、知らないうちに変数名を上書きしてしまった
このように、知らない間に名前空間を汚している場合、そのエラーを見つけるのはなかなか大変です。
# どの名前が名前空間に存在しているかわかりにくく、
# 名前の上書きをしてしまった場合に、デバッグが非常にしづらい
# 悪い例:
from somemodule import *



式や文中の空白文字

ホワイトスペースの置き方もなかなか悩みどころですが、コーディング規約では以下の通り定義されています。


カッコ/ブランケット/波カッコの前後には空白を入れない

余計な空白を置くべきではありません。余談ですが、jQueryのコーディング規約はPythonでは規約違反ですね。
# 良い
spam(ham[1], {eggs: 2})

# 悪い
spam( ham[ 1 ], { eggs: 2 } )


カンマ/セミコロン/コロンの直後に空白を入れる

カンマ/セミコロン/コロンの直後には、「基本として」空白を入れ、前には空白を入れません。
# 良い
if x == 4 print x, y; x, y = y, x

# 悪い
if x == 4 print x , y ; x , y = y , x
しかし、配列などで:をスライスの意味で使う場合には、二項演算子として振る舞うため、両側に同じ数のスペースを入れます(二項演算子のルールは後述)。また、拡張スライスでも、両側に同じスライスを置かなければなりません。ただし例外として、スライスのパラメータが省略された場合には、スペースも省略できます。
# 良い
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower:upper:step]
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]


関数呼び出しの引数リストを始める開きかっこの直前は無駄な余白を入れない

関数を呼び出す場合に、関数とかっこの間に無駄な余白を入れないようにします。
# 良い
spam(1)

# 悪い
spam (1)


インデックスやスライスの開きかっこ直前も不要

同じく、掲題の場合に不要な余白を置きません。
# 良い
dct["key"] = lst[index]

# 悪い
dct ["key"] = lst [index]


代入演算子を揃えるための、無駄な余白はダメ

これC言語とかだとよく見かけるんですが、Pythonではダメなルールです(僕は時々使っていました)。
# 良い
x = 1
y = 2
long_variable = 3

# 悪い
x             = 1
y             = 2
long_variable = 3


二項演算子は、両側に常に1つだけスペースを入れる

以下の二項演算時の場合には、両側にスペースを1つ設けます。
  • 代入演算子(=)
  • 拡張代入演算子(+=, -=)
  • 比較演算子( ==, <, >, !=, <>, <=, >=, in, not in, is, is not)
  • ブール演算子(and, or, not)
# 良い例
val1 = val2 + val3
val4 = val4 and val5


優先度の異なる演算子を複数使う場合には、余白を活用して優先度を見える化する

優先順位が違う演算子を扱う場合、優先順位が一番低い演算子の両側にスペースを入れることを検討しましょう。入れるかどうかは任意ですが、2つ以上のスペースは絶対に使わないこと。そして、二項演算子の両側には常に同じ数の空白文字を入れるようにします。
# 良い
x = x*2 - 1
hypot2 = x*x + y*x
c = (a+b) * (a-b)

# 悪い
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)


キーワード引数やデフォルトパラメータの「=」は余白なし

キーワード引数やデフォルトパラメータであることを示すために使う=の両側には、スペースは入れずに使います。このあたりは、余白の使い方の基本とは異なるので注意が必要ですね。
# 良い
def complet(real, imag=0.0):
    return magic(r=real, i=imag)

# 悪い
def complex(real, imag = 0.0):
    return magic(r = real, i = imag)


関数アノテーションの「:」と「->」の余白

関数アノテーションは、コロンに関する通常のルール(コロンの前には余計なスペースを入れない)を守りつつ、->演算子がある場合は、その両側には常にスペースを入れるようにします。
# 良い
def munge(input: AnyStr): ...
def munge() -> AnyStr: ...

# 悪い
def munge(input:AnyStr): ...
def munge()->PosInt: ...



命名規則

Pythonの命名規則は、JavaやJavaScriptで主流のものとは少し異なるようです。この辺をしっかりと理解できると、Pythonらしいコード(=パイソニックなコード)が書けるようになります。


前アンダースコア

_single_leading_underscoreのように_から始まる変数は、内部だけで使うことを示します。
例えばfrom M import *では、アンダースコアで始まる名前のオブジェクトをインポートされません。


後ろアンダースコア

single_trailing_underscore_のように後ろに_を付ける場合には、Pythonのキーワードと衝突するのを避けるために使われます。例えば、以下のような使い方です。
Tkinter.Toplevel(master, class_="ClassName")


前アンダースコア2つ

__double_leading_underscoreのように前に__とアンダースコアを2つ付ける方法は、クラスの属性に名前を付ける時に、名前のマングリング機構を呼び出します(クラスFoobar__booという名前は、_FooBar__booになります。


モジュール名

すべて小文字の短い名前を使います。読みやすくするためにアンダースコアを使っても良いです。
# モジュール名の例
my_module


パッケージ名

すべて小文字の短い名前を使います。アンダースコアの利用は推奨されていません。アンダースコアを使わなくても理解できるように命名を工夫するとパイソニックな感じになります。
# パッケージ名の例
mypackage


クラス名

CapWords形式(=最初大文字でその後はキャメルケース)を用いて、命名します。
# クラス名の例
MyClass    


例外の名前

例外はクラスであるべきで、名前はCapWords形式を採用すべきです。また、その例外が実際にエラーである場合には、末尾にErrorをつけます。
# 例外の名前例
MySomeCaseFooError


関数の名前

すべて小文字を使い、読みやすくするために必要に応じてアンダースコアも利用することができます。
# 関数名の例
my_function
ただし、すでにmixedCaseがつかわれている場合は、互換性を保つためにmixedCaseを使うこともできます。
# mixedCaseの命名ルール例
saySomething

関数の引数

インスタンスメソッドの場合は、必ず最初はselfにします。
def my_function(self, xxx):
    pass
クラスメソッドの場合は、必ずclsにします。
def my_class_function(cls, xxx) :
    pass
また、引数の変数名が予約語と被る場合は、末尾にアンダースコアをつけます。
def my_function(self, id_, dict_):
    pass


定数

全て大文字で書き、必要に応じてアンダースコアを利用することもできます。
# 定数名の例
MY_CONSTS



最後に

今日はPythonのコーディング規約についてブログを書きました。写経しながら参考サイトの内容を学んだのですが、今まで自分が使ってきた言語とは一味も二味も違ってとても面白いと感じました。また、これを学んだ後のPythonコードは、学ぶ前から比べると雲泥の差で綺麗になった気がします。コーディング規約は、Pythonに慣れてきたらぜひぜひ目を通したい内容の1つだと感じました。

最後になりますが本ブログでは、Python・Swift・Java・フロントエンド・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。