読者です 読者をやめる 読者になる 読者になる

SideCI Blog

自動コードレビューサービスSideCIを提供している株式会社アクトキャットのコーポレートブログです。



Pythonのスタイルガイドとそれを守るための各種Lint・解析ツール5種まとめ!

Python Other

f:id:sideci:20151125183257p:plain

今回はpythonのスタイルガイド、コーディング規約に関連する周辺ツールについてまとめてみました。pep8, pyflakes, flake8, haking, Pylingの5種類のツールについてまとめてあります。

いつもの記事より少し長いため、お急ぎの方はpep8とflake8の章を読むことをおすすめします。

目次

  • pep8の使い方
    • PEP 8とは
    • PEP 8の思想
    • pep8を動かしてみよう
    • サマリの出力
    • 深く見てみる
    • pep8のエラーコードの意味とオプション
    • pep8のカスタマイズ
      • ユーザごとの設定ファイル
      • プロジェクトごとの設定ファイル
  • pyflakesの使い方
    • pyflakesとは
    • pyflakesをインストールしてみよう
    • pyflakesを動かしてみよう
    • pyflakesの設計思想について
  • flake8の使い方
    • flake8とは
    • flake8をインストールしてみよう
    • flake8を動かしてみよう
    • flake8のエラーコードの意味とオプション
  • hackingの使い方
    • hackingとは
    • hackingをインストールしてみよう
    • hackingのエラー検知
    • hackingのエラー概要
    • hackingのカスタマイズについて
  • Pylintの使い方
    • Pylintとは
    • Pylintをインストールしてみよう
    • Pylintを動かしてみよう
    • Pylintのエラーコードの意味とオプション

pep8の使い方について

PEP 8とは

pep8の使い方について説明する前に、まずPEP 8について説明します。

pythonにはPEPという文書群があります。

これはpythonコミュニティに対して情報や新機能・プロセス・環境設定などを説明する文書の集合体です。

その設計書のうちの8番目が、PEP 8 - Style Guide for Python Codeです。

Style Guideというタイトルの通り、pythonのコードスタイルについて書かれています。

ちなみに、「Cのコードスタイル」について書かれているのがPEP 7(PEP 0007 – Style Guide for C Code _ Python.org)、「Docstringの規約」について書かれているのがPEP 257(PEP 0257 – Docstring Conventions _ Python.org)です。

PEP 8の思想

PEP 8の中の一節、A Foolish Consistency is the Hobgoblin of Little Mindsに、コードの一貫性についての考え方について書かれています。この部分は、PEP 8の考え方について理解するのに良いかもしれません。以下に抜粋します。

  • コードは書かれる時間よりもずっと多くの時間読まれる。

  • PEP 20 – The Zen of Pythonも言っている通り、「読みやすさ」は大切なことだ。

  • このスタイルガイドは一貫性のために存在する。

  • 最も大事なことは、一貫性を壊すときはどんな時かを知ること。例えばこのPEPを守るがために後方互換性を壊すようなことはしてはならない。

このガイドラインであるPEP 8のコードスタイルをチェックするツールがpep8です。

このチェッカーは、pythonで実装されています。

pep8を動かしてみよう

さて、前置きが長くなりましたが、pep8を動かしてみましょう。

以下、特に断りのない限り、各種pythonツールは下記の環境で動作確認しています。

  • Mac OSX Yosemite(10.10.3)
  • Python 2.7.9
  • pip 7.1.2

まずはpep8をインストールしてみましょう。

READMEのとおりに、以下のようなコマンドでインストールできるでしょう。

$ pip install pep8

テストのために、以下のようなファイル(parse.py)を用意します。

scrapy/scrapyから拝借し、警告が起きるように一部改変しました。

from __future__ import print_function
import logging
class Command(ScrapyCommand):


    @property
    def max_evel(self): 
        levels  = self.items.keys() + self.requests.keys()
        if levels: return max(levels)
        else: return 0

    def add_items(self, lvl, new_items):
        old_items = self.items.get(lvl, [])
        self.items[lvl] = old_items + new_items

このファイルに対し、pep8を実行してみます。

基本的な使い方としては $ pep8 [ファイル名またはディレクトリ名] です

$ pep8 parse.py
scrapy/commands/parse.py:3:1: E302 expected 2 blank lines, found 0
scrapy/commands/parse.py:6:5: E303 too many blank lines (2)
scrapy/commands/parse.py:7:24: W291 trailing whitespace
scrapy/commands/parse.py:8:15: E221 multiple spaces before operator
scrapy/commands/parse.py:9:18: E701 multiple statements on one line (colon)
scrapy/commands/parse.py:10:13: E701 multiple statements on one line (colon)
scrapy/commands/parse.py:15:1: W391 blank line at end of file

今回の場合、7個のエラーが出力されました。

上記とのとおり、エラーが発生したファイル名、位置、エラーコードとその内容がそれぞれ出力されます。

英語で表示されますが、内容は簡潔でわかりやすいと思います。

サマリの出力

サマリを表示するには--statistics -qq とします。

$ pep8 --statistics -qq ./parse.py
1       E221 multiple spaces before operator
1       E302 expected 2 blank lines, found 0
1       E303 too many blank lines (2)
2       E701 multiple statements on one line (colon)
1       W291 trailing whitespace
1       W391 blank line at end of file

プロジェクトのルートディレクトリで $ pep8 --statistics -qq . を実行すれば、そのプロジェクトのPEP 8違反箇所のサマリを出力することができるでしょう。

深く見てみる

問題のある箇所を^を使ってわかりやすく表示することができます。$ --show-sourceオプションです。

$ pep8 --show-source ./parse.py

./parse.py:3:1: E302 expected 2 blank lines, found 0
class Command(ScrapyCommand):
^
./parse.py:6:5: E303 too many blank lines (2)
    @property
    ^
./parse.py:7:24: W291 trailing whitespace
    def max_evel(self):
                       ^
./parse.py:8:15: E221 multiple spaces before operator
        levels  = self.items.keys() + self.requests.keys()
              ^
./parse.py:9:18: E701 multiple statements on one line (colon)
        if levels: return max(levels)
                 ^
./parse.py:10:13: E701 multiple statements on one line (colon)
        else: return 0
            ^
./parse.py:15:1: W391 blank line at end of file

^

さらに、直し方の説明を見ることも出来ます。

--show-pep8 オプションです。

$ pep8 --show-pep8 ./parse.py
./parse.py:3:1: E302 expected 2 blank lines, found 0
    Separate top-level function and class definitions with two blank lines.

    Method definitions inside a class are separated by a single blank line.

    Extra blank lines may be used (sparingly) to separate groups of related
    functions.  Blank lines may be omitted between a bunch of related
    one-liners (e.g. a set of dummy implementations).

    Use blank lines in functions, sparingly, to indicate logical sections.

    Okay: def a():\n    pass\n\n\ndef b():\n    pass
    Okay: def a():\n    pass\n\n\n# Foo\n# Bar\n\ndef b():\n    pass

    E301: class Foo:\n    b = 0\n    def bar():\n        pass
    E302: def a():\n    pass\n\ndef b(n):\n    pass
    E303: def a():\n    pass\n\n\n\ndef b(n):\n    pass
    E303: def a():\n\n\n\n    pass
    E304: @decorator\n\ndef a():\n    pass
....(以下、警告の数だけ説明が出ます)

たくさん出てきてしまいますが、1つ1つドキュメントをチェックせずに治していくことができるので、このオプションも便利そうです。

pep8のエラーコード(Exxx)の意味とオプション

おおまかな分類としては以下のとおりです(ソースコード内から抜粋)。

  • エラーと警告
    • E で始まるもの … errors
    • W で始まるもの … warnings
  • 100系 … indentation
  • 200系 … whitespace
  • 300系 … blank lines
  • 400系 … imports
  • 500系 … line length
  • 600系 … deprecation
  • 700系 … statements
  • 900系 … syntax errors

それぞれの意味の詳細については、こちらのドキュメント、およびソースコード内にのこちらが参考になると思います。

特定のエラーコードをチェック対象から外す

--ignoreオプションでエラーコードを値として与えることで、特定のエラーを無視することが出来ます。

例えば以下のように、カンマ区切りで、チェックをスキップしたいエラーコードを列挙して実行します(E41と書くとE410〜E419は無視されます)。

$ pep8 --ignore=E226,E302,E41 [ファイル名またはディレクトリ名]

pep8のカスタマイズ

ここまで、pep8の走らせ方と、エラーと警告の種類について見てきました。

つぎに、pep8にオプションで渡していた内容を、設定ファイルに書く方法について見ていきましょう。

ユーザごとの設定ファイル

デフォルトのユーザの設定ファイルの場所は'~/.config/pep8'です。

設定ファイルは、オプションと同じように、以下の様な形で書くことが出来ます。

[pep8]
ignore = E226,E302,E41
max-line-length = 160

ignoreはカンマ区切りで、チェックをスキップするエラーコードを列挙します(E41と書くとE410〜E419は無視されます。コマンドラインオプションと同じです。)。

max-line-lengthは1行の中で許容される最大文字数を指定できます(デフォルトは79で、80文字以上になるとE501エラーが出ます。

設定ファイルの場所は --config=設定ファイルの場所で指定することも出来ます。

プロジェクトごとの設定ファイル

以上のような設定ファイルを、各プロジェクト内の所定の場所に置くことで、プロジェクト内で共有することが出来ます。

setup.cfgファイルまたはtox.iniファイルが存在する場合は、そのファイルに記載された内容が読み込まれます。

ちなみに.pep8ファイルも読み込まれるそうですが、このファイル名を利用するのは非推奨(deprecated)だそうです。

pyflakesの使い方

pyflakesとは

pep8の利用方法を理解したところで、次にpyflakesについて見ていきます。

pyflakesもpep8同様に、pythonのソースコードのエラーチェッカーです。

pyflakesは、pep8とは違い、スタイルについては一切関知せず、論理的なエラーのみを検出します。

pyflakesをインストールしてみよう

以下の様なコマンドでインストールできます。

$ pip install pyflakes

pyflakesを動かしてみよう

基本的な使い方としては、以下のとおりです。

$ pyflakes [ファイル名またはディレクトリ名]

先ほどのparse.pyを対象として動かしてみましょう。

$ pyflakes ./parse.py
./parse.py:2: 'logging' imported but unused
./parse.py:3: undefined name 'ScrapyCommand'

新たに「インポートされたが利用されていないライブラリ」や「未定義の名前」などを検出しました。

pep8でチェックした内容とは違う内容が出てきました。「スタイルについては感知せず、論理的なエラーのみを検出」していることがわかります。

pyflakesの設計思想について

pyflakesの設計思想について、pyflakesのREADMEに説明があります。特徴的なので、簡単に紹介します。

  • スタイルについては言及しない。
  • 偽陽性を出さないように、並々ならぬ努力をしている。
  • 個々のファイルを個別にチェックしているため、PylintやPyCheckerより速く、検出できることが比較的限定されている。
  • スタイルのチェックをしたかったり、プロジェクト毎の設定を可能にするには、flake8をチェックすると良い。

また、オプションが非常に少なく(--help--versionしか無い)、この手のツールが提供するような、「警告を抑制したりカスタマイズする仕組み」がありません。

これらのことも、次に紹介するflake8が解決します。pyflakesは、"論理的なエラーのみを検出する"という1つのことをうまくやっているツールと考えることが出来そうです。

flake8の使い方

flake8とは

flake8は、簡単に言うと、「pep8のチェック、pyflakesのチェック、及び循環的複雑度をチェックできるラッパー」です。

他の機能としては、# flake8: noqa で特定の行の警告を抑制することができる(pyflakes単体だと出来ない)ことや、pep8のように設定ファイルで発生する警告をカスタマイズできる機能などがあります。

flake8をインストールしてみよう

以下の様なコマンドでインストールできます。

$ pip install flake8

flake8を動かしてみよう

使い方及びオプションはpep8と一緒で $ flake8 [ファイル名またはディレクトリ名]のように使います。

$ flake8 ./parse.py
./parse.py:2:1: F401 'logging' imported but unused
./parse.py:3:1: E302 expected 2 blank lines, found 0
./parse.py:3:15: F821 undefined name 'ScrapyCommand'
./parse.py:6:5: E303 too many blank lines (2)
./parse.py:7:24: W291 trailing whitespace
./parse.py:8:15: E221 multiple spaces before operator
./parse.py:9:18: E701 multiple statements on one line (colon)
./parse.py:10:13: E701 multiple statements on one line (colon)
./parse.py:15:1: W391 blank line at end of file

pep8の結果(エラーコードがExxxとWxxxのもの)とpyflakesの結果(エラーコードがFxxxというふうに出てくる)が一緒に出力されていることが確認できます。

これは、pyflakesのエラー(Fxxx)も、pep8同様に、ignoreやselectで設定ファイルで調整できることを意味しています。カスタマイズがやりやすいですね。

flake8のエラーコード(Exxx)の意味とオプション

flake8のエラーコードは、pep8で利用されているE***系とW***系に加えて、F***系とC9**系があります。

  • E***/W***: pep8 のエラー及び警告。
  • F***: PyFlakes の検知。
  • C9**: McCabeによる循環的複雑度の検知。

エラーコードの詳細についてはこちらのドキュメントに説明があります。

hackingの使い方

flake8は拡張性に優れています。次に、プロジェクト独自のルールをflake8に落とし込んだhackingを紹介します。

hackingとは

hackingは、OpenStack社のOpenStack Style Guidlinesに基づいて作られたflake8プラグインです。

ソースコードはgithub上にあり、ライセンスはApache License 2.0です。

OpenStack Style Guidlinesの発祥は、こちらのhackingのページによると、 Google Python Style Guide に基づいており、その後OpenStackの独自ルールが追加されたものであるということです。

OpenStack Style Guidlinesによると、hackingには、「いくつかの目的」があり、それは以下の様なものであるということです。

  • レビューがつまらない指摘(docstring guidelinesなど)の泥沼に嵌らないように
  • 大人数の環境の違いによる読みづらさの排除(unix vs windows newlinesなど)
  • 危険なパターンの排除(shadowing built-in or reserved wordsなど)

hackingをインストールしてみよう

$ pip install hacking

flake8が無い状態で実行すると、flake8も一緒にインストールされると思います。

hackingのエラー検知

hackingはこんなことも検知してくれます。H101を発生させてみます。

サンプルコードに、# TODO いつか直すというコメントを追加してみます

    @property
+    # TODO いつか直す
    def max_evel(self): 
        levels  = self.items.keys() + self.requests.keys()
        if levels: return max(levels)

hackingがインストールされている状態でflake8を実行すると

$ flake8 scrapy/commands/parse.py
scrapy/commands/parse.py:2:1: F401 'logging' imported but unused
scrapy/commands/parse.py:3:1: E302 expected 2 blank lines, found 0
scrapy/commands/parse.py:3:15: F821 undefined name 'ScrapyCommand'
scrapy/commands/parse.py:6:5: E303 too many blank lines (2)
scrapy/commands/parse.py:7:7: H101  Use TODO(NAME)
scrapy/commands/parse.py:8:24: W291 trailing whitespace
scrapy/commands/parse.py:9:15: E221 multiple spaces before operator
scrapy/commands/parse.py:10:18: E701 multiple statements on one line (colon)
scrapy/commands/parse.py:11:13: E701 multiple statements on one line (colon)
scrapy/commands/parse.py:17:1: W391 blank line at end of file

scrapy/commands/parse.py:7:7: H101 Use TODO(NAME) が新たに発生しました。

これは、ガイドラインのここにかかれているルールで、

[H101] Include your name with TODOs as in # TODO(yourname). This makes it easier to find out who the author of the comment was.

というルールのためです(細かいですね^^)。

このように、hackingをインストールすると、flake8を実行した時にhacking独自のルール(エラーコード:Hxxxの形です)が追加されます。

hackingのエラー概要

hackingのエラーコードについてのドキュメントを探したのですが、見つかりませんでした(エラーコードについてご存知の方は、教えてください)。

ソースコードを読む限り、以下のように分類できると思います。

H1xx

ライセンスやコメントなどについて。

[H101] Include your name with TODOs as in ``# TODO(yourname)``. This makes
it easier to find out who the author of the comment was.

[H102 H103] Newly contributed Source Code should be licensed under the
Apache 2.0 license. All source files should have the following header::

[H104] Files with no code shouldn't contain any license header nor comments,
and must be left completely empty.

[H105] Don't use author tags. We use version control instead.

[H106] Don't put vim configuration in source files (off by default).

H2xx

テストなどについて。

[H201] Do not write ``except:``, use ``except Exception:`` at the very least.
[H202] Testing for ``Exception`` being raised is almost always a ...

H23x

Python3.xとの互換性。

[H231] ``except``. Instead of::
[H232] Python 3.x has become more strict regarding octal string
[H233] The ``print`` operator can be avoided by using::
[H234] ``assertEquals()`` logs a DeprecationWarning in Python 3.x, use
[H235] ``assert_()`` is deprecated in Python 3.x, use ``assertTrue()`` instead.
[H236] Use ``six.add_metaclass`` instead of ``__metaclass__``.
[H237] Don't use modules that were removed in Python 3. Removed module list:
[H238] Old style classes are deprecated and no longer available in Python 3

H3xx

importのスタイルについて。

[H301] Do not import more than one module per line (*)
[H303] Do not use wildcard ``*`` import (*)
[H304] Do not make relative imports
[H306] Alphabetically order your imports by the full module path.

H4xx

Docstringsについて。

[H401] Docstrings should not start with a space.
[H403] Multi line docstrings should end on a new line.
[H404] Multi line docstrings should start without a leading new line.
[H405] Multi line docstrings should start with a one line summary followed

H5xx

スタイルの統一などについて。

[H501] Do not use ``locals()`` or ``self.__dict__`` for formatting strings,
[H702] If you have a variable to place within the string, first
[H703] If you have multiple variables to place in the string, use keyword
[H903] Use only UNIX style newlines (``\n``), not Windows style (``\r\n``)

hackingのカスタマイズについて

flake8のプラグインですので、pep8と同様にエラーコードを指定してのignoreなどが可能です。

例えば、上述したとおりH1**系のエラーは、「Apache License2.0の明示」や、「TODOコメントの書き方」など、会社単位もしくはプロジェクト単位で異なるものが多いと考えられるので、まずはignoreしてみても良いかもしれません。

Pylintの使い方

ここまで個々のファイルを個別にチェックできるpep8やpyflake系のツールを見てきましたが、

最後に、複数ファイルにまたがったモジュールやパッケージをチェックできるツールの1つである、Pylintをご紹介します。

Pylintとは

Pylintもまた、pythonのコードチェッカーの1つです。

最初に紹介したシンプルなチェックツールであるpep8も歴史がある(2006-)のですが、Pylintも歴史が古いようです(最初のリリース0.0版が2003年)。

pep8と比較すると、多彩なチェック項目と、オプションの多さが特徴です。

2015年11月現在も頻繁にコミットがされているようです。ソースコードはこちら(https://bitbucket.org/logilab/pylint)です。

Pylintをインストールしてみよう

他のツールと同様、pipでインストールすることが出来ます。

$ pip install pylint

Pylintを動かしてみよう

使い方としては $ pylint [オプション] [モジュールまたはパッケージ] になります。

前述のhackingで利用したparse.pyをサンプルとして動かしてみましょう。結果は、以下のようになりました。

$ pylint scrapy/commands/parse.py
No config file found, using default configuration
************* Module scrapy.commands.parse
C:  7, 0: Trailing whitespace (trailing-whitespace)
C:  8, 0: Exactly one space required before assignment
        levels  = self.items.keys() + self.requests.keys()
                ^ (bad-whitespace)
C:  1, 0: Missing module docstring (missing-docstring)
C:  3, 0: Missing class docstring (missing-docstring)
W:  3, 0: Class has no __init__ method (no-init)
E:  3,14: Undefined variable 'ScrapyCommand' (undefined-variable)
C:  7, 4: Missing method docstring (missing-docstring)
E:  8,18: Instance of 'Command' has no 'items' member (no-member)
E:  8,38: Instance of 'Command' has no 'requests' member (no-member)
C:  9,19: More than one statement on a single line (multiple-statements)
C: 12, 4: Missing method docstring (missing-docstring)
E: 13,20: Instance of 'Command' has no 'items' member (no-member)
E: 14, 8: Instance of 'Command' has no 'items' member (no-member)
W:  2, 0: Unused import logging (unused-import)
...(以下レポートが出ますが長いので割愛します)

hackingでは計9個の警告が出ていましたが、今回は多少の増減があり、計12個の警告が出ました。

特徴的なものとして、

E:  8,18: Instance of 'Command' has no 'items' member (no-member)

のような警告が新たに検出されました。

これは、チェック時にitemsというメンバが見つからなかったという警告(エラーコードはE1101)です。

ただし、公式wikiのE1101のページを見ると、動的にメンバを追加した時には偽陽性を出すことがある、ということが書かれています。

なので、意図的にそのような書き方をしている際には注意が必要です。

Pylintのエラーコードの意味とオプション

Pylintは、多くのエラーコードおよびオプションがありますので、全ては紹介しきれません。

公式ドキュメントのこのページで一覧化されていますので、カスタマイズする際はこちらを参考にすると良いと思います。

今回は、チェックを並列化するオプションと設定ファイルの生成方法のみご紹介します。

チェックの並列化による高速化(-jオプション)

Pylintの特徴として、個々のファイルを個別にチェックするのでなく、パッケージやモジュール単位で評価するという特徴があるため、pyflakesやpep8に比べてどうしてもチェックに時間がかかってしまいます。

-j(--jobs)オプションで、並列化することが出来るようです。

    -j <n-processes>, --jobs=<n-processes>
                        Use multiple processes to speed up Pylint. [current:1]

設定ファイルの生成方法(–generate-rcfileオプション)

–generate-rcfileオプションで、標準出力に設定ファイル(pylintrc)の雛形を出力することが出来ます。

特にオプションで設定ファイルの場所を渡さないかぎり、カレントディレクトリのpylintrcが最優先で読み込まれます。

プロジェクト毎に共有ルールを決める場合は、プロジェクトのルートディレクトリにpylintrcを置くと良いかもしれません。

最後に

今回はpythonのスタイルガイド、コーディング規約に関連する周辺ツールについてまとめてみました。

pep8やflake8に関するツールを見てきましたが、いずれにしても実際にプロジェクトに合ったカスタマイズをすることで開発がやりやすくなると思います。

flake8がpep8をwrappingしていたり、hackingがflake8のプラグインとして作られているなど、最も普及しているようです。ぜひまずはflake8をお試し下さい。

また、SideCIでもflake8をお使いいただけます。