LoginSignup
501

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-07-20

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

参考情報

コーディング規約

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

オブジェクト ルール
パッケージ(フォルダ名) 全小文字の短い名称。アンダースコア非推奨 sample
モジュール 全小文字の短い名称。読みやすいならアンダースコア可能 sapmle_module
クラス パスカルケース SampleClass
例外 パスカルケースで末尾は"Error" SampleError
関数・変数 全小文字でアンダースコア可能 sample_function
関数やメソッドに渡す引数 インスタンスメソッドの第一引数名self
クラスメソッドの第一引数名はcls
引数名が予約語と衝突していた場合、アンダースコアを末尾に追加
sample_parameter
メソッド名とインスタンス変数 全小文字でアンダースコア可能
非公開のメソッドやインスタンス変数は接頭辞がアンダースコア
sample_public
_sample_private
定数 全大文字でアンダースコア可能 SAMPLE_CONSTANT

命名規約(アンダーバーを変数前後につける場合)

変数の前後にアンダーバーをつける場合があり、その説明を紹介します。基本的にクラス内のPrivateな変数はアンダーバーを2つ接頭辞にするのがよさそうです。

_single_leading_underscore: "内部でだけ使う" ことを示します。 たとえば from M import * は、アンダースコアで始まる名前のオブジェクトをimportしません。

single_trailing_underscore_: Python のキーワードと衝突するのを避けるために使われる規約です。例を以下に挙げます:

__double_leading_underscore: クラスの属性に名前を付けるときに、名前のマングリング機構を呼び出します (クラス Foobar の boo という名前は _FooBarboo になります。以下も参照してください)

__double_leading_and_trailing_underscore__: ユーザーが制御する名前空間に存在する "マジック"オブジェクト または "マジック"属性です。 たとえば init, import, file が挙げられます。この手の名前を再発明するのはやめましょう。ドキュメントに書かれているものだけを使ってください。

プロジェクト構造

残念ながらプロジェクト構造について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を使います。
PyCharmではPython 統合ツールから設定すると、Styleに応じた補完などをしてくれます。

docstringのセクション一覧

よく使うセクション一覧です。

セクション 内容
Args 関数引数
Returns 関数戻り値
Raises 関数例外
Examples 関数の使用例
Notes 関数/クラスの注記
Attributes クラス属性

docstringの例

例1 関数

def fetch_bigtable_rows(big_table, keys):
    """関数に対する1行のサマリ説明

    サマリ説明から1行を空けて詳しい説明を書きます
    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: 引数1の説明
        keys: 引数2の説明

    Returns:
        戻り値の説明。複数行に渡って書くのもOK。ここでの例は戻り値の具体例まで出していてわかりやすい。
        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: 例外の説明

    Example:
        result = fetch_bigtable_rows(big_table, keys)
    """

例2 クラス

class SampleClass(object):
    """関数に対する1行のサマリ説明

    サマリ説明から1行を空けて詳しい説明を書きます
    Longer class information....

    Attributes:
        likes_spam: 属性1の説明
        eggs: 属性2の説明
    """

    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."""

型ヒント(Type Hints)を使う

型ヒント(Type Hints)を使ってアノテーションして関数の引数・戻り値に対して型を明示することができます。Pythonは動的型付け言語なので型指定をしないでいいという反面、関数の引数・戻り値がわかりにくいです。型ヒント(Type Hints)を使えば、PyCharmなどのIDEで色々とサポート(代入する値が違っているのではとの警告など)をしてくれたりします。
書くのが面倒な反面、可読性が上がるので基本的には型ヒントを使うのが望ましいかと思います。
型ヒントはPEP484PEP526で規定されていて、typingパッケージがサポートします。「typing モジュールは暫定的に標準ライブラリに追加されました」と書かれていてまだ「暫定」扱いだそうです。

型ヒントの基本

引数はコロンの後にスペースをはさんで型を書きます。
戻り値は->の後に型を書きます。

def greeting(name: str) -> str:
    return 'Hello ' + name

以下が基本的な型です。

  • str
  • int
  • float
  • bool
  • None

型ヒント(辞書型、タプル型、リスト型)

よく使う辞書型、タプル型、リスト型です。
typingパッケージからインポートして使います。
returnが複数の場合は、Tupleを使います。

from typing import Dict, Tuple, List

def test(dict_: Dict[str, int], list_: List[str]) -> Tuple[str, int]:
    print(dict_)
    print(list_)
    return 'aa', 1

型ヒント(オブジェクト)

オブジェクト名を書きます。オブジェクト名を調べるのが少し面倒です。

from logging import getLogger, Logger

def get_module_logger() -> Logger:
    logger = getLogger(__name__)
    return logger

この辺を参考にしました。とてもわかりやすかったです。当記事では、基本だけ書いたのですがもっと複雑なこともできます。

GitHubを使う

開発・検証したプログラムをすべてローカルに置いておくのは面倒です。GitHubは情報共有もできて、やはり非常に便利。共同開発をいない場合にも使わない手はないです。
記事「Git初心者がプログラムをGitHubに置いておく方法」のレベルで使っています。
Pythonに限定するなら、Python用".gitignore"テンプレートを使うと、少しリポジトリがすっきりします。

ログ出力

いつまでもprintだけに頼っていられません。
"logging"パッケージを使うことで、ファイルにログを簡単に残しておくことができたり、出力元ソースコードや日時を自動出力できたりとメリット多いです。
詳しくは、記事「Pythonでprintを卒業してログ出力をいい感じにする」を見てください。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
501