ころがる狸

ころがる狸のデータ解析ブログ

【pythonテクニック】説明文(docstring)でコードを読みやすく!

こんにちは。今日(2020/4/29)も天気がいいですね。窓をあけながら記事を書いています。皆さん、プログラムを書くときにdocstringはきちんと書いていますか?そもそもdocstringという単語を聞いたことはあるでしょうか。githubで有名ライブラリのコードを読んでいると、トリプルクオート(""")で囲まれた説明文が入っているのを目にしたことがあるかもしれません。これを一般にdocstringと呼びます。私自身はdocstringを書くようになって格段にコードのメンテナンスがしやすくなったと感じているため、pythonの重要テクニックとして今回はdocstringとは何か、どのように書くべきかについて記事にしてみました。

docstringとはなにか(PEP257を読みながら)

docstringとはなんぞや、ということがpythonプログラミングの質を向上させるための資料集PEP(Python Enhancement Proposals)の中のPEP257に記載されています。

A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the __doc__ special attribute of that object.

(翻訳)docstringとはモジュール、関数、クラス、メソッドの最初のステートメントとして現れる文字列リテラルであり、__doc__という特殊属性で呼び出すことができます。

・・・とのことですが分かりますか?もっと直接的に言うと、docstringとはクラスやメソッドの説明文のことです。それらがどのような機能を持つのか、どのような引数を取るのか、データ型は何か、デフォルト値は何か、クラスならどのような属性を持つか、戻り値は何かといった情報を英語や日本語で記述します。これにより、いちいちコードを解読しなくてもその説明文(docsgring)を読めば理解ができるようになるためプログラムの可読性が向上します。なお、ここに書いているようにprint(オブジェクト名.__doc__)とするとdocstringの文章を表示できるので今度試してみてくださいね。
さらにPEP257にはこう書いています。

All modules should normally have docstrings, and all functions and classes exported by a module should also have docstrings.

(翻訳)すべてのモジュールは通常docstringを持つべきであり、またモジュールから呼び出されるすべての関数やクラスもまたdocstringを持つべきです。

・・・実際、有名ライブラリは普通docstringを内部に含んでいますし、docstringの有る無しではメンテナンスのしやすさも格段に違います。自分が書いたコードでも時間が経てば理解が難しくなりますし、それが他人の書いたものであればなおさらです。これを理解しやすいものとし、コード解読の手間を省くためにも極力docstringはきちんと記述するようにしましょう。
www.python.org

実際に見てみよう(numpy風の docstring)

さぁ、百聞は一見に如かず。docstirngの具体的な使い方を見てみましょう。以下は本ブログでも紹介してる新型コロナウィルス感染者数をプロットする際に用いたコードの一部ですが、これだけ読んでも何を目的としたメソッドなのか判別が付きません。しかしdocstringが付いたことでこのメソッドの使用目的や引数、そして戻り値が何なのかがはっきりと示されました(""" """で囲まれた部分がdocstringです)。

def getPatients(df):
    """
    DadaFrameからソート済み日時と日当りの新型コロナ感染者数を取得。
    
    Parameters
    ----------
    df : DataFrame
        新型コロナ感染者の発生日時を記録したdate列を含むpandasのDataFrame
        
    Returns
    -------
    d : list[datetime]
        datetime型の日時を格納したリスト
    c : list[int]
        日当りの新型コロナ感染者数を格納したリスト
        
    """
    l = []
    for d in set(df.date):
        l.append([d, df.date.value_counts()[d]])
    
    d = list(map(lambda x:datetime.strptime(x[0], '%Y/%m/%d'), l))
    c = list(map(lambda x:x[1], l))
    
    tmp = [[i, j] for i, j in zip(d, c)]
    tmp.sort()
    for i, items in enumerate(tmp):
        d[i], c[i] = items
    
    return d, c

docstringのスタイル

stackoverflowなどで調べるといくつかの書き方のスタイルが存在するようですが、よく目にするのはNumPy styleとGoogle styleのdocstringかと思います。どちらを使っても構わないのですが、1つのコードの中に複数のスタイルが混在しないよう片方に統一して使いましょう。なお、上記の例ではNumPy styleのdocstringを使っています。
sphinxcontrib-napoleon.readthedocs.io

numpydoc.readthedocs.io
詳細な説明は公式ドキュメントにゆずりますが、上の例で使った書き方についてのみ説明します。引数と戻り値はそれぞれParameters, Returnsで宣言した後にその下に記載します。引数名を書き、コロン(:)を挟んでその引数の型やデフォルト値を記載します。その下の行に具体的な説明文を挿入します。引数や戻り値の型が配列の場合、内部に別の型を格納できます。それを明示的に示したい場合には、例えばlist[int], list[ndarray]のようにします。これによりどのようなオブジェクトが返されるのかより分かりやすくなります。特にpythonではC++とは異なり明示的な型宣言がありません。コードを書く上では便利ですが、読み返したときそれがどのようなオブジェクトなのか判断に時間がかかるというデメリットを感じます。そのためdocstringでは明示的に型を示すべきだと私は考えています。
なぜか私が参加している社内のプロジェクトではdocstringを書く文化がなかったため、上記のようなことは自分で調べて学ぶ必要がありました。できれば誰かからすぐに教えて欲しかったのですが・・・docstring自体は簡単な話ですが、知らないことはそもそも調べることすらできないので、知識を得るというのは難しいですね。