0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

一人アドベントカレンダー!主にPythonに関して投稿します。Advent Calendar 2024

Day 15

Python開発: mypyによる型チェックでコードをもっと堅牢に

Last updated at Posted at 2024-12-14

はじめに

Pythonはそのシンプルさと柔軟性から、多くの場面で利用される人気の高いプログラミング言語です。
しかし、動的型付けであるため、型に関連するバグが実行時まで発見されないというリスクがあります。
これが静的型付け言語を好む開発者にとって、Pythonを敬遠する理由のひとつであったりします。

ただし、Python 3.5以降に導入された型ヒントと、それを活用する型チェックツールmypyを組み合わせることで、型安全性を大きく向上させることができます。
本記事では、静的型チェックの利便性と強力さを提供するmypyについて詳しく解説します。

mypyとは?

mypyはPythonコードの静的型チェックを行うツールです。
Python 3.5以降で導入された型ヒントを活用して、コード内の型の整合性をチェックします。
これにより、潜在的なバグを事前に発見しやすくなり、コードの安全性と保守性を向上させることができます。

mypyを導入するメリット

  1. バグの早期発見
    型チェックを行うことで、コード中の潜在的なバグを実行前に防ぐことができます。
    特に大規模プロジェクトや複数人での開発では、異なる部分のコードが予期しない型で相互作用する可能性が高まります。
    mypyを使えば、これらの問題を早期に検出できるため、修正コストを抑えることが可能です。
  2. コードの可読性向上
    mypyのオプション(例えば--strict)を使うと型定義を強制できるため、すべての関数やメソッドに型ヒントを記述する文化が定着します。
    型ヒントは他の開発者がコードを理解する助けになるため、チームでの開発効率が向上します。
  3. 型チェック処理を省く
    型チェックをコード内で明示的に行う処理を省くことができます。
    従来、関数内部でisinstanceやカスタム例外処理を使って型を検証することが多く見られましたが、mypyを導入すればこれらのチェックは不要になり、コードの記述量が減少し可読性も向上します。
    例えば以下のコードが不要になります:

従来の型チェックコード

def add_numbers(a, b):
    if not isinstance(a, int) or not isinstance(b, int):
        raise ValueError("エラーコメント")
    return a + b

mypy導入後のコード

mypyで引数がint型であることを保証しているため、型チェックを省くことができる

def add_numbers(a: int, b: int) -> int:
    return a + b

mypyの使用方法

mypyのインストールと基本的な使い方を以下に示します。

1. インストール

まず、mypyをインストールします。

pip install mypy

2. 型ヒントを追加する

コードに型ヒントを記述します。

sample.py
def greet(name: str) -> str:
    return f"Hello, {name}!"
  • name: str は引数nameが文字列であることを示します。
  • -> str は戻り値が文字列であることを示します。

3. mypyで型チェックを実行する

以下のコマンドで静的型チェックを実行します:

mypy sample.py

型の不一致がある場合、エラーが表示されます。
例えば、以下のコードをチェックした場合

def greet(name: str) -> str:
    return 123  # 戻り値がint型

実行結果:

sample.py:2: error: Incompatible return value type (got "int", expected "str")
Found 1 error in 1 file (checked 1 source file)

エラーメッセージは「戻り値の型が期待していたstrではなく、intである」と指摘しています。このように、mypyはコードの問題箇所を的確に教えてくれます。

mypyの便利なオプション

mypyには便利なオプションが豊富に用意されています。
今回はその中でも便利そうなオプションをピックアップして紹介します。

--strict

推奨されるすべての型チェックオプションを有効にします。
オプションを渡していないデフォルトのmypyでは、型を明示的に設定していない関数やメソッドに対する警告曖昧な型に対する警告が表示されませんが、--strictを使用することで、型注釈の不足や曖昧な型に関する警告が有効になります。

これにより、厳格な型チェックが適用され、コードの型安全性が向上します。
一方で、既存のコードベースに適用すると警告が増えるため、段階的な導入がおすすめです。

オプションを渡していないデフォルトのmypyの場合

以下のコードは、型を明示していない関数や、曖昧な型(listなど)が含まれています。

sample.py
def add_numbers(a, b):  # 型を明示していない
    return a + b

def print_list(data: list) -> None:  # 型を明示していない
    print(data)

このコードに対してmypy sample.pyを実行しても、特に警告やエラーは表示されません。

% mypy sample.py
Success: no issues found in 1 source file

--strictオプションを渡した場合

一方、mypy --strict sample.pyを実行すると、以下のようにエラーが表示されます。

% mypy --strict sample.py
sample.py:1: error: Function is missing a type annotation  [no-untyped-def]
sample.py:4: error: Missing type parameters for generic type "list"  [type-arg]
Found 2 errors in 1 file (checked 1 source file)

このように、--strictを使用することで、型注釈が不足している関数や、曖昧な型について警告が表示されるようになります。

エラー解決後のコード

以下のように型を明示すると、--strictを有効にしても警告がなくなります。

def add_numbers(a: int, b: int) -> int:  # 引数と返り値に型を明示
    return a + b

def print_list(data: list[str]) -> None:  # listの中身の型を明示
    print(data)

--disallow-any-expr

明示的に指定されたAny型の利用を禁止します。

このオプションは2024/12/15現在の最新バージョン(mypyバージョン1.13.0)では--strictモードには含まれていないため、利用時には別途オプションを渡す必要があります。

以下はサンプルコードと実行した例です。

sample.py
from typing import Any

def greet(name: Any):
    print(f"Hello, {name}")

このコードをmypy --disallow-any-expr sample.pyで実行すると、次のような警告が表示されます

% mypy --disallow-any-expr sample.py 
sample.py:9: error: Expression has type "Any"  [misc]
Found 1 error in 1 file (checked 1 source file)

--warn-unreachable

到達不能コードを警告するオプションです。

このオプションは2024/12/15現在の最新バージョン(mypyバージョン1.13.0)では--strictモードには含まれていないため、利用時には別途オプションを渡す必要があります。
mypyのissueにEnable --warn-unreachable in --strict in Mypy 2.0が起票されているため、今後--strictに含まれてくる可能性があります。

以下はサンプルコードと実行した例です。

sample.py
def check_number(x: int) -> str:
    if isinstance(x, str):  # 到達不能: 型ヒントで x は int 型と指定
        return "String"
    return "Integer"

このコードをmypy --warn-unreachable sample.pyで実行すると、次のような警告が表示されます。

% mypy --warn-unreachable sample.py 
sample.py:2: error: Subclass of "int" and "str" cannot exist: would have incompatible method signatures  [unreachable]
sample.py:3: error: Statement is unreachable  [unreachable]
Found 2 errors in 1 file (checked 1 source file)

おまけ: mypy.iniでオプションを記載する

mypyはコマンドラインで指定するオプションを設定ファイルで記載することができます。
この設定ファイルはプロジェクトのルートディレクトリに配置することで、毎回コマンドラインでオプションを指定する手間を省くことができます。
以下はサンプルです。

mypy.ini
[mypy]
# すべての型を厳密にチェック
strict = True

# 到達不能コードを警告
warn_unreachable = True

# 明示的に指定されたAny型の利用を禁止
disallow_any_expr = True

まとめ

今回の記事ではコマンドを手動で実行して型チェックを行いましたが、pre-commitフックなどと組み合わせることで自動的に型チェックを行うことも可能です。
mypyの型チェックを活用して、Pythonコードをより堅牢で保守しやすいものにしましょう!
ご拝読いただきありがとうございました。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?