nikkie-ftnextの日記

イベントレポートや読書メモを発信

Pythonの型チェックでもprintデバッグのようなことができたのか! reveal_type() を知りました

はじめに

プログラミングしてる花咲夜、むふふ。nikkieです。

Pythonで型を書いてきて少なくとも2年、初めて reveal_type()を知りました

目次

reveal_type()、一見に如かず

mypyのドキュメントの中の「Displaying the type of an expression」にある例です。
https://mypy.readthedocs.io/en/stable/common_issues.html#displaying-the-type-of-an-expression

You can use reveal_type(expr) to ask mypy to display the inferred static type of an expression.

reveal_type((1, "hello"))

型チェックを実行します。

% mypy --version
mypy 1.11.0 (compiled: yes)

% mypy example.py
example.py:1: note: Revealed type is "tuple[Literal[1]?, Literal['hello']?]"
Success: no issues found in 1 source file
  • 型チェックは通った
    • reveal_type()は組み込み関数ではないが、型チェッカはそれを怒らない
  • reveal_type()の部分、noteが出力されている

このスクリプトPythonインタプリタで実行するとreveal_type()のためにエラーが送出されます。

% python -V
Python 3.11.8

% python example.py
Traceback (most recent call last):
  File "/.../example.py", line 1, in <module>
    reveal_type((1, "hello"))
    ^^^^^^^^^^^
NameError: name 'reveal_type' is not defined

mypy以外の型チェッカもサポート

pyrightでもmypyと同様に使えるようです。

% pyright --version
pyright 1.1.372

% pyright example.py
/.../example.py
  /.../example.py:1:13 - information: Type of "(1, "hello")" is "tuple[Literal[1], Literal['hello']]"
0 errors, 0 warnings, 1 information

PylanceはVS Code上で表示してくれて、これはなかなか便利!
(青い波の下線にマウスカーソルを当てて表示しています)

"(1, "hello")" の型は "tuple[Literal[1], Literal['hello']]" です

Python 3.11からtyping.reveal_type()

https://docs.python.org/ja/3/library/typing.html#typing.reveal_type

Ask a static type checker to reveal the inferred type of an expression.

導入理由(python.jpより)

導入理由はpython.jpが分かりやすかったです。
https://www.python.jp/news/wnpython311/typing5.html#typing.reveal_type()

reveal_type() は mypy などの型チェッカでのみ実行可能な関数で、reveal_type() が残ったPythonスクリプトを実行すると、エラーとなってしまいます。このため、開発中に型を確認しながらテストを実行する場合には reveal_type() を入れたり消したりしなければならず、とても面倒でした。

typing.reveal_type()を使えば、削除しなくてもPythonインタプリタはエラーを送出しません

from typing import reveal_type

reveal_type((1, "hello"))
% mypy example.py
example.py:3: note: Revealed type is "tuple[Literal[1]?, Literal['hello']?]"
Success: no issues found in 1 source file

% python example.py
Runtime type is 'tuple'

typing.reveal_type()の実装を見る

What's New in Python 3.11も見てみます。
https://docs.python.org/ja/3.11/whatsnew/3.11.html#typing

issueにもありますが、実装はprint文のようです

# https://github.com/python/cpython/blob/v3.11.9/Lib/typing.py#L3440-L3456
def reveal_type(obj: T, /) -> T:
    print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
    return obj

意見:reveal_type()は残したくない

typing.reveal_type()であれば消さなくてもエラーが送出されないことが分かりましたが、私としては削除したほうがいいのではないかと考えます。
実体はprint()関数で、プログラム実行中には不要な出力な気がします。
printデバッグも解決したら削除すると思いますし、型のprintデバッグの面があるので削除したいかなと思います。

迷ったポイントとして、Pylanceが型を示してくれるのが他の開発者とのコミュニケーションで便利かもと思いました。
しかしながら、以下の点から削除したいと考えています。

  • 開発チーム規模によっては全員がVS Code + Pylanceを前提にできないことがある
  • コードベースが大きくなったら実行時のprintがじゃまになるのでは
  • 他の開発者に型を伝える役目は、コード中のコメントが負った方がよい気がする

終わりに

型ヒントを書き始めたときに知りたかったreveal_type()でした。

  • import不要でreveal_type()と書くと、型チェッカが型を表示してくれる
  • Python 3.11からtyping.reveal_type()が導入された

typing.reveal_type()はありますが、本質はprintデバッグだと思うので、デバッグが終わったら削除しましょう!