Edited at

Pythonプログラムをしっかりと書く虎の巻

Pythonを今まで適当に書いてきましたが、プログラムをしっかりと作りたいと考え、いろいろ勉強・調査しました。そこで学んだことをこの記事に書きます。


参考情報


コーディング規約

PythonにはPEP8というコーディング規約があります。せっかくあるので従うべきでしょう。PyCharmのようなIDEを使うと警告を出してくれるので効率よくPEP8に準拠したプログラムを書くことが出来ます。

私感で重要と考えるものだけ簡潔に抜粋しておきます。

オブジェクト
ルール

パッケージ(フォルダ名)
全小文字の短い名称。アンダースコア非推奨
sample

モジュール
全小文字の短い名称。読みやすいならアンダースコア可能
sapmle_module

クラス
パスカルケース
SampleClass

例外
パスカルケースで末尾は"Error"
SampleError

関数・変数
全小文字でアンダースコア可能
sample_function

関数やメソッドに渡す引数
インスタンスメソッドの第一引数名self
クラスメソッドの第一引数名はcls
引数名が予約語と衝突していた場合、アンダースコアを末尾に追加
sample_parameter

メソッド名とインスタンス変数
全小文字でアンダースコア可能
非公開のメソッドやインスタンス変数は接頭辞がアンダースコア
sample_public
_sample_private

定数
全大文字でアンダースコア可能
SAMPLE_CONSTANT


プロジェクト構造

残念ながらプロジェクト構造についてPEP8に記載がないです。いくつか参考にした情報です。


シンプルな方式

ゼロから学ぶ Pythonから抜粋。

(project)

├── (project) ............ プログラムのソースコードディレクトリ
│ ├── __init__.py
│ └── *.py
└── tests ................ 単体テストのソースコードディレクトリ
├── __init__.py
└── *.py


機械学習系

データサイエンスプロジェクトのディレクトリ構成どうするか問題から抜粋。notebook以下がメインとなるプログラム?

.

├── README.md
├── config
│ └── example.ini
├── converter
│ ├── __init__.py
│ └── converter.py
├── data
├── experiment.py
├── notebook
│ └── sample.ipynb
├── requirements.txt
├── results
└── tool
└── tool.py


Google AI Platform式

Recommended project structureから抜粋。このくらいシンプルな方が好きなのですが、ローカルでも動かすとなるとデータ・ログはどこに置く?という感じ。

image.png


docstringを使う

docstringは関数やクラスなどへのソースコード上のコメントによる説明です。あとで自分で読み返した時や、他人に共有するときなどやはり書いておくといいですよね。また、書いておくとツールを使ってドキュメントの自動生成もできるようです。

Pythonでは、主にGoogle StyleNumpy Styleがあるようです。

「Google vs NumPy」を見ると比較がわかりやすいのですが、Google Styleの方が行数を少なく書くことができます。行数が少ないソースが好きなので、私はGoogle Styleを使います。


例1 関数

def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):

"""Fetches rows from a Bigtable.

Retrieves rows pertaining to the given keys from the Table instance
represented by big_table. Silly things may happen if
other_silly_variable is not None.

Args:
big_table: An open Bigtable Table instance.
keys: A sequence of strings representing the key of each table row
to fetch.
other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.

Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:

{'Serak': ('Rigel VII', 'Preparer'),
'Zim': ('Irk', 'Invader'),
'Lrrr': ('Omicron Persei 8', 'Emperor')}

If a key from the keys argument is missing from the dictionary,
then that row was not found in the table.

Raises:
IOError: An error occurred accessing the bigtable.Table object.
"""


例2 クラス

class SampleClass(object):

"""Summary of class here.

Longer class information....
Longer class information....

Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""

def __init__(self, likes_spam=False):
"""Inits SampleClass with blah."""
self.likes_spam = likes_spam
self.eggs = 0

def public_method(self):
"""Performs operation blah."""


GitHubを使う

開発・検証したプログラムをすべてローカルに置いておくのは面倒です。GitHubは情報共有もできて、やはり非常に便利。共同開発をいない場合にも使わない手はないです。

記事「Git初心者がプログラムをGitHubに置いておく方法」のレベルで使っています。

Pythonに限定するなら、Python用".gitignore"テンプレートを使うと、少しリポジトリがすっきりします。


ログ出力

いつまでもprintだけに頼っていられません。

"logging"パッケージを使うことで、ファイルにログを簡単に残しておくことができたり、出力元ソースコードや日時を自動出力できたりとメリット多いです。

詳しくは、記事「Pythonでprintを卒業してログ出力をいい感じにする」を見てください。