Pythonの型ヒントはなぜ「ヒント」なのか?

2024/11/08に公開

はじめに

Pythonには「型ヒント」という機能がありますが、他の静的型付け言語と異なり、
型ヒントはその名の通り「ヒント」に過ぎません。
この記事では、なぜPythonで型が「ヒント」として扱われるのかについて少し掘り下げていきます。

型ヒントはなぜ「ヒント」なのか?

型ヒントは、変数や関数の引数、戻り値に対して「どのような型が期待されるか」を示すもので、Python 3.5以降から導入されました。
例えば、次のように書くことで関数が整数を受け取り、文字列を返すことを明示できます。

def greet(age: int) -> str:
    return f"Hello, you are {age} years old!"

print(greet(20))
# Hello, you are 20 years old!

print(greet("thirty"))
# Hello, you are thirty years old!

ここでageにはint型が期待されているという情報が示されています。
しかし、この型指定はあくまで「ヒント」であり、実行時に強制されるわけではありません。
例えば、この関数にstr型の値を渡したとしても、Pythonはエラーを発生させず、そのまま実行してしまいます。

Pythonの動的型付けと型ヒント

Pythonは「動的型付け言語」であるため、型ヒントは実行時の強制力を持ちません。
動的型付けとは、変数の型が実行時に決まることで、開発者がコード中で型を自由に変更できるという柔軟性を持ちます。
型ヒントは、この柔軟性を維持しつつ、コードの保守性や可読性を高めるための機能として位置付けられています。

そのため、型ヒントはPythonにおいて「実行時にチェックされる型」ではなく、
あくまで開発者やエディタがコードの意図を読み取りやすくするための補助的な情報です。

mypyなどの型チェッカはPythonを動的型付けに保ったまま、静的型付けのメリットを与えます。

型の「機械的な表現」と「意味論的な表現」

ロバストPythonという本を読んでいたら、データ型に関して「機械的な表現」「意味論的な表現」という概念があることを知りました。
https://www.oreilly.co.jp/books/9784814400171/

機械的な表現:
データ型がPython自体に対して振る舞いや制約を伝える役割を持ちます。
つまり、Pythonの内部でオブジェクトがどのように扱われるかを決定します。

意味論的な表現:
データ型が開発者に対して振る舞いや制約を伝える役割を持ちます。
コードを読む他の開発者に、変数や関数がどのように使われるべきかを示します。

機械的な表現について理解するためにメモリから型情報を観察

パッと機械論的な表現と言われても理解できないので、
実際に、Pythonオブジェクトがメモリ上でどのように型情報を持っているかを確認してみます。
以下のコードでは、オブジェクトのメモリ位置を直接読み取り、型ポインタやデータがどのように格納されているかを調べています。

from binascii import hexlify
from ctypes import string_at
from sys import getsizeof

a = 10
print(hexlify(string_at(id(a), getsizeof(a))))

b = 100
print(hexlify(string_at(id(b), getsizeof(b))))

c = "NCDC"
print(hexlify(string_at(id(c), getsizeof(c))))

d = "むーさん"
print(hexlify(string_at(id(d), getsizeof(d))))

このコードを実行すると、各オブジェクトのメモリ位置が16進数で表示されます。

b'ffffffff00000000d01a69030100000008000000000000000a000000'
b'ffffffff00000000d01a690301000000080000000000000064000000'
b'ffffffff00000000e0c169030100000004000000000000004e43444300'
b'ffffffff00000000e0c16903010000000d00000000000000e38182e38184e38186'

どうやら9~16バイト目までが型ポインタを表す情報らしく[1]
整数オブジェクト a = 10 の場合、型ポインタは d01a690301000000 となっており、
b = 100 の場合も同じです。

文字列オブジェクト c = "NCDC" の場合、型ポインタはe0c1690301000000 となっており、
d = "むーさん" の場合も同じです。

このように、オブジェクトは自分の型情報を持っており、Pythonインタープリタはこれを利用して適切な処理を行います。

なぜこの理解が重要か

しかし、この型情報は実行時にのみ利用され、静的なコード解析やコンパイル時の型チェックには使われません。

ここで型ヒントが「意味論的な表現」として登場します。。
型ヒントを用いることで、開発者間での意図の共有や、静的解析ツールによるコード品質の向上が図れます。

まとめ

Pythonの型ヒントが「ヒント」であるのは、言語の特性である動的型付けを尊重しながら、
開発者間の意思疎通やコード品質の向上を図るための手段として位置づけられているからです。

型ヒントは開発者や静的解析ツールに対してコードの意図や期待される型を伝える「意味論的な表現」として機能しますが、実行時にはPythonインタープリタがオブジェクトの持つ型情報「機械的な表現」を利用して動的に処理を行います。

脚注
  1. ※この辺はAIに聞いただけで、大元の情報源から根拠は確かめられていません。
    以下のようなドキュメントを深掘っていけばわかりそうですが、自分には理解困難でした。。。
    https://docs.python.org/3/c-api/structures.html ↩︎

NCDCエンジニアブログ

Discussion