「型ヒント (Type Hints)」は Python 3.5 以降で導入された機能です。変数や関数の引数・戻り値に対して、期待されるデータ型を注釈 (アノテーション) として記述することができます。これは JavaScript と TypeScript の関係によく似ています。TypeScript の最大の特徴は、変数や関数の引数や返り値などの値にデータ型を指定できることです。これを「静的型付け」といいます。この機能により、コンパイルの段階でエラー (データ型の不一致など) を検出することが可能になります。
Python 自体は動的型付け言語なので、実行時に型チェックが行われることはありません。型が違っていてもそのまま実行されます。しかし、型ヒントを記述することでコードの可読性を高め、VS Code や PyCharm などの IDE (統合開発環境) の自動補完が強力になったり、mypy などの静的解析ツールを使うことで「実行前にバグを発見できたりする」といった「静的型付け」のメリットを享受することができます。
本ページでは Python の型ヒントの基本的な使い方を簡単に説明します。
型ヒントの基本的な書き方はとても簡単です。次の例を見てください。
# 変数の型ヒント
変数名: 型 = 値
# 関数の型ヒント
def 関数名(引数名: 型, ...) -> 型:
... 略 ...
変数の場合、名前の後ろに : を付けて、その後ろに型を定義します。関数の場合、引数は変数と同じで、返り値の型はカッコの後ろに -> を付けて、その後ろに型を定義します。
Python の組み込み型は、そのまま型ヒントに使用することができます。よく使われる基本的な型を示します。
簡単な例を示しましょう。
リスト : 型ヒント (sampleh01.py)
def square(num: int) -> int:
return num * num
# 本来は int を渡すべきところに、誤って str(文字列)を渡している
result = square('5')
print(result)
関数 square の引数 num は型ヒント : int が記述されているので、num は int を受け取る引数であることがわかります。そして、返り値の型ヒント -> int が記述されているので、int が返されることがわかります。ここで、square('5') と呼び出すと、文字列を渡すことになるので実行時にエラーが発生します。このような場合でも、静的な型チェックを行うツール mypy を使うと、エラーを発見することができます。
mypy は次のコマンドでインストールすることができます。
(.venv) $ pip install mypy
M.Hiroi の環境では mypy ver 2.3.1 がインストールされました (2026 年 9 月時点)。あとは mypy にファイル名を指定して実行するだけです。
$ mypy sampleh01.py sampleh01.py:5: error: Argument 1 to "square" has incompatible type "str"; expected "int" [arg-type] Found 1 error in 1 file (checked 1 source file)
エラーが見つからない場合は 'Success: no issues found in 1 source file' と表示されます。
次は、複数のデータをまとめるデータ型 (リストや辞書など) の型ヒントを説明します。型ヒントは 型[中身の型] のように記述します。Python 3.9 以降は標準の list や dict がそのまま使えます。
簡単な例を示します。
list[int] # 整数のリスト list[str] # 文字列のリスト list[list[int]] # 整数のリストのリスト dict[str, int] # キーが文字列、値が整数の辞書 dict[str, list[int]] # キーが文字列、値が list[int] の辞書 tuple[str, int] # 第 1 要素が str, 第 2 要素が int のタプル tuple[str, list[int]] # 第 1 要素が str, 第 2 要素が list[int] のタプル set[int] # 整数の集合 set[str] # 文字列の集合
Union 型は複数の型を許容するときに、Optional 型は特定の型と None を許容するときに使います。Python 3.10 以降では、| (パイプ演算子) を使うことで、よりシンプルに複数の型や None の許容を表現することができます。
簡単な例を示します。
int | str # 整数または文字列 int | float # 整数または実数 int | None # 整数または None str | None # 文字列または None # 3 つ以上の型はパイプで繋げて並べるだけ int | str | None
mypy などの多くの型チェッカー (静的解析ツール) では、float と指定されている場所に int の値を渡してもエラーにはなりません。そのため、「基本的には小数だが、整数が来ても問題ない」という関数の引数には、シンプルに float だけを指定しておけば十分なケースが多いようです。
関数の引数に高度な制約をかけたい場合や、特殊なオブジェクトを扱う場合、モジュール typing から特殊な型をインポートして使用します。
簡単な例を示します。
Callable[[int, int], int] # int を 2 つ受け取り、int を返す関数
Callable[[], int]] # 引数がない場合は [] を指定する
Callable[[str], None] # 返り値がない場合は None を指定する
Callable[..., None] # 引数の数や型を固定しない場合は ... を指定する
Literal['r', 'w'] # 文字列 'r' と 'w' しか受け付けない
Literal[1, 2, 3] # 整数 1, 2, 3 しか受け付けない
Literal[True] # True しか受け付けない
Sequence[int] # [1,2,3] でも (1, 2, 3, 4) でも受け付ける
Sequence[str] # ['A', 'B'] でも ('A', 'B', 'C') でも受け付ける
Mapping[str, int] # キーが str で値が int のコレクション
Python で高階関数(Higher-Order Functions)を型付けするには、collections.abc の Callable を使用します。簡単な例を示します。
リスト : 高階関数の型ヒント (sampleh02.py)
from collections.abc import Callable
def add(x: float, y: float) -> float:
return x + y
# 関数を引数に受け取る
def calc(x: float, y: float, func: Callable[[float, float], float]) -> float:
return func(x, y)
print(calc(1, 2, add))
# 関数を返す
def make_adder(n: float) -> Callable[[float], float]:
return lambda x: x + n
adder10: Callable[[float], float] = make_adder(10)
print(adder10(100))
# ジェネリクス
def mapcar[T](func: Callable[[T], T], xs: list[T]) -> list[T]:
return [func(x) for x in xs]
def square(n: int) -> int:
return n * n
print(mapcar(square, [1, 2, 3, 4, 5]))
(.venv) $ mypy sampleh02.py Success: no issues found in 1 source file (.venv) $ python sampleh02.py 3 110 [1, 4, 9, 16, 25]
Python におけるジェネリクス (汎用型) は、特定の型に依存せず、「入ってきた型と同じ型を返す」といった柔軟な型定義を可能にする仕組みです。関数型言語の型変数 (型パラメータ) と同様の機能です。本ページでは型変数と記述することにします。
Python 3.12 以降では、関数名やクラス名の直後に [T] のように記述するだけで型変数を定義することができます。それ以前の Python では、モジュール typing の関数 TypeVar() を使ってあらかじめ型変数を定義する必要があります。
自作したクラスは、そのまま型ヒントとして利用できます。クラスのインスタンスを受け取る場合や、メンバ変数に指定する場合は、クラス名をそのまま書きます。
リスト : クラスの型ヒント (sampleh03.py)
class User:
# メンバ変数の型ヒント
name: str
age: int
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
# 関数の引数にクラス (型ヒント) を指定
def print_user(user: User) -> None:
print(f"{user.name} ({user.age})")
# 実行
a = User("Alice", 25)
print_user(a)
(.venv) $ mypy sampleh03.py Success: no issues found in 1 source file (.venv) $ python sampleh03.py Alice (25)
自分自身のクラスを型ヒントにする場合、通常はクラス定義の途中のためエラーになります。この場合、ファイルの先頭に from __future__ import annotations を記述すると、クラス定義の途中でも自分自身のクラス名を書くことができます。
リスト : クラスの型ヒント (sampleh04.py)
from __future__ import annotations
class Node:
# 自分自身のクラスを型ヒントにする
data: int
next: Node | None
def __init__(self, value: int, link: Node | None) -> None:
self.data = value
self.next = link
def print_node(self) -> None:
cp: Node | None = self
print('[', end=' ')
while cp:
print(cp.data, end= ' ')
cp = cp.next
print(']')
a = Node(1, None)
b = Node(2, a)
c = Node(3, b)
c.print_node()
(.venv) $ mypy sampleh04.py Success: no issues found in 1 source file (.venv) $ python sampleh04.py [ 3 2 1 ]
Python 3.11 で追加された typing.Self は、そのメソッドが属するクラス自身の型を動的に表すための特殊な型ヒントです。自分自身のインスタンスを返すメソッド (メソッドチェーンやファクトリメソッドなど) や、継承が絡むクラスの型ヒントをスマートに記述できるようになりました。
簡単な例を示しましょう。メソッドの戻り値で self をそのまま返す場合、戻り値の型ヒントに Self を指定します。
リスト : Self の使用例 (メソッドチェーン, sampleh05.py)
from typing import Self
class Counter:
def __init__(self, n: int) -> None:
self.cnt = n
def up(self, n: int) -> Self:
self.cnt += n
return self
def down(self, n: int) -> Self:
self.cnt -= n
return self
a = Counter(0)
# メソッドチェーンを繋げても、型を正しく認識できる
a.up(1).up(2).down(1)
print(a.cnt)
(.venv) mhiro@mhiroi:~/python$ mypy sampleh05.py Success: no issues found in 1 source file (.venv) mhiro@mhiroi:~/python$ python sampleh05.py 2
従来の __future__ アノテーションを使った型ヒントだと、継承したときに不都合が起きていましたが、Self を使えば子クラスの型として正しく解釈されます。
リスト : Self の使用例 (継承, sampleh06.py)
from typing import Self
class Foo:
def clone(self) -> Self: # Self を指定
return self
def method(self) -> None:
print("foo method")
class Bar(Foo):
def method(self) -> None:
print("bar method")
a = Foo()
b = a.clone()
a.method()
b.method()
c = Bar()
d = c.clone() # d の型は Bar と解釈される
c.method()
d.method()
(.venv) $ mypy sampleh06.py Success: no issues found in 1 source file (.venv) $ python sampleh06.py foo method foo method bar method bar method
@classmethod を使って新しいインスタンスを生成して返す場合にも Self が利用できます。
リスト : Self の使い方 (クラスメソッド, sampleh07.py)
from typing import Self
class User:
def __init__(self, name: str) -> None:
self.name = name
@classmethod
def create_guest(cls) -> Self:
# クラス(cls)からインスタンスを作って返す
return cls("Guest")
guest = User.create_guest()
print(guest.name)
(.venv) $ mypy sampleh07.py Success: no issues found in 1 source file (.venv) $ python sampleh07.py Guest
関数の引数に「インスタンス」ではなく「クラス自体」を渡す場合、type[クラス名](または typing.Type)を使用します。
リスト : クラス自体を引数に渡す (sampleh08.py)
class Foo:
pass
class Bar(Foo):
pass
# Foo またはそのサブクラスを受け取る
def make_foo(foo_class: type[Foo]) -> Foo:
return foo_class() # インスタンスを生成して返す
# 呼び出し (インスタンスではなくクラス名をそのまま渡す)
a = make_foo(Bar)
print(a)
(.venv) mhiro@mhiroi:~/python$ mypy sampleh08.py Success: no issues found in 1 source file (.venv) mhiro@mhiroi:~/python$ python sampleh08.py <__main__.Bar object at 0x7701401feae0>
Python の型ヒントは型に別名を付けることができます。これを TypeAlias といいます。TypeAlias を使う最大のメリットは、複雑になってしまった型表記を 1 つのシンプルな名前にまとめ、コードの可読性を向上させる点にあります。Python 3.12 以前のバージョンでは、typing.TypeAlias をインポートする必要がありましたが、Python 3.12 以降からは新しい構文 (type 文) が導入されました。
type 新しい型名 = 型
簡単な例を示しましょう。
1. 複数の型(Union型)をまとめる type ID = int | str 2. 複雑なコレクション構造に名前をつける type UserDict = dict[str, int | str | list[float]] 3. タプルを使った座標表現 type Point = tuple[float, float] type Point3D = tuple[float, float, float] 4. 特定の引数と返り値を持つ関数を型として定義する type UserFunc = Callable[[int, str], bool]
type 文の真価は、型パラメータ (ジェネリクス) を持つ型を定義するときに発揮されます。これまでは TypeVar を使った複雑な準備が必要でしたが、type 文なら型名の後ろに [T] をつけるだけで、自動的にジェネリックな型として定義されます。
type 型名[T, ...] = 型[T, ...]
複数の型引数 (例: U と V) を扱う場合も、カンマで区切って記述するだけで定義できます。簡単な例を示しましょう。
リスト : type 文とジェネリクス (sampleh09.py)
type Pair[U, V] = tuple[U, V]
a: Pair[int, int] = (1, 1)
b: Pair[int, float] = (10, 1.2345)
c: Pair[str, int] = ("foo", 123)
print(a, b, c)
(.venv) mhiro@mhiroi:~/python$ mypy sampleh09.py
Success: no issues found in 1 source file
(.venv) mhiro@mhiroi:~/python$ python sampleh09.py
(1, 1) (10, 1.2345) ('foo', 123)
型変数には制約をかける事ができます。大きく分けると、「値制約 (Constraints) 」と「上界 (Bound) 」の 2 つの方法があります。
# 値制約 type 型名[T: (型A, 型B, ...)] = 型[T] # T の型はカッコで指定した型だけ許可される
上昇 (Bound) は、特定のクラスまたはそのサブクラスに制限したい場合に使用します。
type 型名[T: クラス] = 型[T] # T はクラス (またはそのサブクラス) に制限される
簡単な例を示しましょう。
リスト : 型変数の制約 (sampleh10.py)
type NumList[T: (int, float)] = list[T]
a: NumList[int] = [1, 2, 3, 4, 5]
b: NumList[float] = [1.1, 2.2, 3.3]
print(a, b)
class Foo:
data: int
def __init__(self, x: int) -> None:
self.data = x
class Bar(Foo):
pass
type XList[T: Foo] = list[T]
def tolist[T: Foo](xs: XList[T]) -> list[int]:
return [x.data for x in xs]
c: XList[Foo] = [Foo(1), Bar(2), Foo(3), Bar(4)]
d: XList[Bar] = [Bar(2), Bar(4)]
# e: XList[Bar] = [Foo(1)] エラー
print(tolist(c))
print(tolist(d))
(.venv) $ mypy sampleh10.py Success: no issues found in 1 source file (.venv) $ python sampleh10.py [1, 2, 3, 4, 5] [1.1, 2.2, 3.3] [1, 2, 3, 4] [2, 4]
Python の型ヒントの基本的な使い方を簡単に説明しました。型ヒントはコードが長くなるので、Python 本来の良さ (シンプルで簡潔な書きやすさ) に反すると思われるかもしれません。ですが、Python の型ヒントはすべての行に書く必要はありません。まずは「複数のモジュールから呼ばれる共通な関数」など、影響の大きい重要な箇所から少しずつ導入していくと、負担をかけずに型ヒントのメリットを享受できるようになると思われます。興味のある方はいろいろ試してみてください。
Python は使いやすいプログラミング言語ですが、実行速度はお世辞にも速いとは言えません。時間がかかる処理をC言語で記述してライブラリを作成し、それをインポートして呼び出す方法もありますが、Python のプログラムを変更なして高速化できるならば、その方が簡単で便利です。今回は Python のプログラムを高速化する方法を 3 つ紹介します。
Numba(ナンバ)は、Python や NumPy のコードをネイティブコードに変換する JIT (Just-In-Time) コンパイラです。関数にデコレータ(@njit など)を付けるだけで、Python 特有の「for ループが遅い」という弱点を克服し、C言語や Fortran に匹敵する速度まで処理を高速化することができます。
Numba のインストールはパッケージ管理ツール pip を使うと簡単です。次のコマンドでインストールすることができます。
(.venv) $ pip install numba
(.venv) $ pip freeze | grep numba numba==0.67.0
コマンド pip は仮想環境下で実行する必要があります。仮想環境については拙作のページ 仮想環境とパッケージ を参考にしてください。M.Hiroi がインストールしたのは ver 0.67.0 (2026 年 9 月時点) です。
Numbaを利用する場合、最も最適化効率が高い @njit (nopythonモード) を使用するのが一般的です。基本的な使い方は簡単で、高速化したい関数の前に @njit を付けるだけです。これでその関数が JIT コンパイルされます。それでは、たらいまわし関数で実行時間を計測してみましょう。
リスト : たらいまわし関数 (tarai1.py)
from numba import njit
import time
@njit
def tarai(x, y, z):
if x <= y: return y
return tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y))
@njit
def tak(x, y, z):
if x <= y: return z
return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
@njit
def fibo(n):
if n < 2:
return n
else:
return fibo(n - 1) + fibo(n - 2)
def test(func, x, y, z):
s = time.time()
print(func(x, y, z))
e = time.time()
print(e - s)
def testfibo(n):
s = time.time()
print(fibo(n))
e = time.time()
print(e - s)
(.venv) $ python Python 3.12.3 (main, Jul 15 2026, 23:46:41) [GCC 13.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from tarai1 import * >>> test(tarai, 14, 7, 0) 14 1.4476268291473389 >>> test(tarai, 14, 7, 0) 14 1.026611089706421 >>> test(tarai, 14, 7, 0) 14 1.0281002521514893 >>> test(tak, 22, 11, 0) 11 1.1670598983764648 >>> test(tak, 22, 11, 0) 11 1.1303346157073975 >>> test(tak, 22, 11, 0) 11 1.1114506721496582 >>> testfibo(38) 39088169 0.21191954612731934 >>> testfibo(39) 63245986 0.2781708240509033 >>> testfibo(40) 102334155 0.44927167892456055 実行環境 : Ubunts 24.04 (WSL2, Windows 11), intel CORE i5-1235U 1.30GHz
素の Python3 では tak(22, 11, 0) に 13.48 秒かかりますが、Numba を使用することで 1.13 秒に短縮することができました。初回の実行にはちょっと時間がかかりますが、2 回目以降はどの関数も高速に実行することができました。
JIT コンパイラを使ったスクリプト言語では Julia が速いのですが、tak(22, 11, 0) を実行すると約 0.87 秒ほどかかります。Julia に迫る速度を叩き出すのですから、Numba の JIT コンパイラは優秀ですね。Python のプログラムを高速化するときには試してみたいツールだと思ました。
Mypyc は、型ヒントが付いた Python コードをC言語の拡張モジュールへコンパイルし、高速化するコンパイラツールです。静的型チェッカーとして有名な mypy のプロジェクトの一部として開発されており、pip で mypy をインストールするだけで mypyc を利用することができます。mypyc はCコードを生成してコンパイルするため、Cコンパイラ (GCC、Clang、または MSVC) がシステムにインストールされている必要があります。
mypycで高速化の恩恵を受けるには、型ヒントをしっかりと記述することが重要です。たらいまわし関数で試してみましょう。
リスト : たらいまわし関数 (型ヒントバージョン)
import time
def tarai(x: int, y: int, z: int) -> int:
if x <= y: return y
return tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y))
def tak(x: int, y: int, z: int) -> int:
if x <= y: return z
return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
def fibo(n: int) -> int:
if n < 2:
return n
else:
return fibo(n - 1) + fibo(n - 2)
def test(func, x, y, z):
s = time.time()
print(func(x, y, z))
e = time.time()
print(e - s)
def testfibo(n):
s = time.time()
print(fibo(n))
e = time.time()
print(e - s)
プログラムはコマンド mypyc でコンパイルします。
(.venv) $ mypyc tarai2.py ... 略 ... (.venv) $ ls tarai2* tarai2.cpython-312-x86_64-linux-gnu.so tarai2.py
コンパイルが成功すると、同じディレクトリに共有ライブラリ (.so ファイル) が生成されます。コンパイルされたモジュールは、通常の Python ファイルと全く同じように import して使用することができます。
>>> from tarai2 import * >>> test(tarai, 14, 7, 0) 14 0.9971663951873779 >>> test(tarai, 14, 7, 0) 14 0.9940774440765381 >>> test(tak, 22, 11, 0) 11 1.0859012603759766 >>> test(tak, 22, 11, 0) 11 1.080787181854248 >>> testfibo(38) 39088169 0.19072270393371582 >>> testfibo(39) 63245986 0.3134427070617676 >>> testfibo(40) 102334155 0.5107564926147461 実行環境 : Ubunts 24.04 (WSL2, Windows 11), intel CORE i5-1235U 1.30GHz
tak(22, 11, 0) は Numba と同じくらいの速度で、fibo() は Numba よりも少しだけ遅くなるようです。それでも、素の Python3 よりはずっと高速なので、Mypyc の効果は十分に出ていると思います。型ヒントと同様に、影響の大きい重要な箇所 (モジュール) をコンパイルするだけでも、Mypyc のメリットを享受できるようになると思われます。型チェッカーに mypy を使うのであれば、mypyc も一緒に使ってみたいツールだと思いました。
ただし、型ヒントが間違っていると mypyc でコンパイルエラーになります。事前に mypy などで型チェックが通るか確認しておく必要があります。また、パフォーマンス向上のため Python の一部の動的な機能 (実行時のクラス定義の書き換えや、存在しない属性への動的アクセスなど) に制限がかかることがあります。ご注意くださいませ。
Codon (コドン) は、Python のコードをネイティブなマシン語にコンパイルすることで、C/C++ や Rust に匹敵する速度 (通常の Python の 10 〜 100倍以上) で実行可能にする高性能コンパイラです。
MIT (マサチューセッツ工科大学) の研究者らが開発し、バックエンドに LLVM を採用した完全なAOT (Ahead-of-Time / 事前) コンパイル方式である点が大きな特徴です。基本的な Python スクリプトであればそのまま、あるいはわずかな修正で動かすことができます。Python の実行環境 (インタプリタ) が入っていないマシンでも、ビルドしたバイナリファイルを単体で動かすことができます。
Linux 系の OS であれば、次のコマンドで Codon をインストールすることができます。
/bin/bash -c "$(curl -fsSL https://exaloop.io/install.sh)"
$ codon --version 0.20.0
M.Hiroi の環境では version 0.20.0 がインストールされました (2026 年 9 月時点)。
プログラムのコンパイルと実行は次のコマンドで行います。
# コンパイル codon build -o program program.py # 実行 ./program
また、Python のようにプログラムを即座に実行することもできます。
codon run program.py
それでは、たらいまわし関数で実行速度を計測してみましょう。
リスト : たらいまわし関数 (tarai3.py)
import time
def tarai(x, y, z):
if x <= y: return y
return tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y))
def tak(x, y, z):
if x <= y: return z
return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
def fibo(n):
if n < 2:
return n
else:
return fibo(n - 1) + fibo(n - 2)
def test(func, x, y, z):
s = time.time()
print(func(x, y, z))
e = time.time()
print(e - s)
def testfibo(n):
s = time.time()
print(fibo(n))
e = time.time()
print(e - s)
print('tarai:')
test(tarai, 12, 7, 0)
print('tak:')
test(tak, 22, 11, 0)
print('fibo(40):')
testfibo(40)
$ codon build -o tarai3 tarai3.py $ ls tarai3* tarai3 tarai3.py $ ./tarai3 tarai: 14 0.6769711971282959 tak: 11 0.6361310482025146 fibo(40): 102334155 0.38776612281799316 $ codon run tarai3.py tarai: 14 0.9038815498352051 tak: 11 0.9369392395019531 fibo(40): 102334155 0.49698472023010254
codon build でコンパイルすると、たらいまわし関数は超高速になりました。take(22, 11, 0) を GCC -O2 (ver 13.3.0) でコンパイル・実行すると 0.6 秒なので、GCC に匹敵する速度をたたき出しています。この結果には M.Hiroi も大変驚きました。Codon はとても優秀なコンパイラだと思います。codon run は手軽に実行できて便利なのですが、速度はちょっとだけ遅くなるようです。
このように、非常に強力な Codon ですが、Python の「完全な代替品」ではなく、高速化と引き換えにいくつかの制約があります。Codon は内部で厳密な型推論を行います。そのため、途中で変数の型が変わるコードや、異なる型が混ざったリストなどはコンパイルエラーになります。Python 特有の動的な機能 (eval() など) や一部関数は使用できません。
NumPy などの一部主要ライブラリは標準でサポートされつつありますが、Python の膨大なエコシステム (サードパーティ製ライブラリ) のすべてがそのまま動くわけではありません。また、Python の整数 (int) は多倍長整数ですが、Codon のデフォルトは 64 ビット整数になります。必要に応じて i128 などへの型指定が必要です。
Codon のバージョンはまだ 0.20.0 なので、今後のバージョンアップに期待したいと思います。