skydum

個人的な作業記録とか備忘録代わりのメモ

pythonでDocstringを記載し、型定義を行ったサンプル

pythonでDocstringを記載し、型定義を行ったサンプル

開発したソースにはDocstringと型定義をするようにしているが、面倒なのか書いている人が少ない気がする。
型定義を書くと便利だと言うために型定義を体験できるサンプルを作ってみた。

動作環境

Docstringとは

Pythonにおけるdocstringとは、関数やクラス、メソッドなどのドキュメンテーションに用いられる文字列のことで、対象の関数やクラスの使い方や仕様、戻り値の型などを記述するために用いられます。

また、型定義にはtypingモジュールを使用します。型定義を書くことで、静的コード解析ツールによるエラー検知や、コード補完の精度向上が期待できます。

以下は、Python3.10.6で動作する、docstringと型定義を用いたサンプルコードです。get_todos_none_type関数はdocstringや型定義がないため、戻り値の詳細が不明なことが確認できます。

サンプルのソースの説明

  1. get_todos_none_type

    • Docstringも型定義もなし → JSONが戻ってくるんだなと分かるが詳細不明
  2. get_todos_type_requests_response

    • Docstringあり、ResuestsのResponseオブジェクトが戻ってくると分かるので、vscodeでコード補完が効くが中身のデータは詳細不明
  3. get_todos_type_dict

    • Docstringに戻ってくる値のサンプルあり、型定義にDict[str, Union[str, int]]とあるのでdict型としてコード補完が効く
  4. get_todos_typeddict

    • Docstringに戻ってくる値のサンプルあり、型定義にTypedDictの形式を指定しているためdict型としてのコード補完とキー名についてもコード補完が効く(コード補完も効いてコードが書きやすいが、TypedDictの定義がめんどくさい…)

サンプルソース

  • {JSON} Placeholderにアクセスして1件分のデータを取得する
  • requestsを利用しているのでpip install requestsをしてください
from typing import Dict, TypedDict, Union

import requests


def _get_json(id):
    URL = "https://jsonplaceholder.typicode.com/todos/1"

    res = requests.get(URL)
    return res


# 何も記載なし
def get_todos_none_type(id):
    res = _get_json(id)
    return res.json()


# docstringと型定義あり オブジェクト
def get_todos_type_requests_response(id: int) -> requests.Response:
    """JSONPlaceholderからidに指定されたTodoを1件分取得して返す

    Args:
        id (int): Todoを取得するID

    Returns:
        requests.Response: 取得したデータをResponse形式で返す
    """
    res = _get_json(id)
    return res


# dosctringと型定義あり Dictで型定義(key:str, value:str)で詳細はdocstringに記載
def get_todos_type_dict(id: int) -> Dict[str, Union[str, int]]:
    """JSONPlaceholderからidに指定されたTodoを1件分取得して返す

    Args:
    id (int): Todoを取得するID

    Returns:
        Dict[str, Union[str, int]]: Todoの1件分のデータを返す

    Examples:
        INPUT: id: 1

        OUTPUT:
            {
                "userId": 1,
                "id": 1,
                "title": "delectus aut autem",
                "completed": false
            }
    """
    res = _get_json(id)
    return res.json()


# Dict[str, Union[str, int]]としても中身が良くわからないので、厳密に定義する場合の書き方
class Todo(TypedDict):
    userId: int
    id: int
    title: str
    completed: bool


# dosctringと型定義あり TypeDictで指定
def get_todos_typeddict(id: int) -> Todo:
    """JSONPlaceholderからidに指定されたTodoを1件分取得して返す

    Args:
    id (int): Todoを取得するID

    Returns:
        Todo: Dict[Todo]形式のデータを返す

    Examples:
        INPUT: 1

        OUTPUT:
            {
                "userId": 1,
                "id": 1,
                "title": "delectus aut autem",
                "completed": false
            }
    """
    res = _get_json(id)
    return res.json()


def main():
    TODO_INT_ID = 1
    TOTO_STR_ID = "1"

    # 型指定なし、レスポンスの型なし
    # パラメータに型がないので静的コードチェックでエラーにならない
    res_none = get_todos_none_type(TODO_INT_ID)
    # resの型が不明な為コードの補完が働かない
    print(res_none)

    # 型指定あり、レスポンスの型(object)
    # 静的コードチェックでSTR型なのでNGになるが、pythonの実行自体はエラーとならない
    res_response = get_todos_type_requests_response(TOTO_STR_ID)  # ← pylanceがエラーを出す

    # INT型なのでOK
    res_response = get_todos_type_requests_response(TODO_INT_ID)

    # resに型があるのでコード補完が利用できる
    print(res_response.json())

    # 型指定あり、レスポンスの型(Dict)
    res_dict = get_todos_type_dict(TODO_INT_ID)

    # resにTypedDictの型指定があるのでdictのメソッドの補完とキー名の補完が可能
    print(res_dict["userId"])

    res_typedict = get_todos_typeddict(TODO_INT_ID)
    print(res_typedict["completed"])


if __name__ == "__main__":
    main()