TL;DR クイックフィックス
Python 3.8 以前では、list[int] や dict[str, int] のような組み込み型をジェネリック型ヒントとして直接使用できません。手っ取り早い解決策が2つあります:typing モジュールの同等品に置き換えるか、ファイルの先頭に from __future__ import annotations を追加するかです。
# Python 3.9 未満では動作しない
def get_scores(names: list[str]) -> dict[str, int]:
return {name: 0 for name in names}
# 修正1: typing モジュールを使用(Python 3.5 以降で動作)
from typing import List, Dict
def get_scores(names: List[str]) -> Dict[str, int]:
return {name: 0 for name in names}
# 修正2: 遅延アノテーション(Python 3.7 以降で動作)
from __future__ import annotations
def get_scores(names: list[str]) -> dict[str, int]:
return {name: 0 for name in names}
このエラーの原因
Python 3.9 で導入された PEP 585 により、組み込み型にネイティブのジェネリックサポートが追加されました。それ以前は、list や dict は実行時に添字演算子([])をサポートしていませんでした。それを扱えるのは typing モジュールのラッパークラス — List、Dict など — だけでした。
デフォルトでは、Python はインポート時にアノテーションを評価します。古いインタープリタが list[int] に遭遇した瞬間、生の list 型を添字アクセスしようとして即座にエラーが発生します。主なトリガー:
- 関数シグネチャ:
def foo(x: list[int]) - 変数アノテーション:
scores: dict[str, float] = {} - PEP 585 構文を使用したデータクラスのフィールド定義
- Python 3.8 上で動作する Pydantic や attrs モデル
使用中の Python バージョンがわからない場合は、素早く確認できます:
python --version
# またはスクリプト内で確認する場合:
import sys
print(sys.version_info) # 例: sys.version_info(major=3, minor=8, micro=18, ...)
修正方法
方法1: typing モジュールからインポートする(ライブラリに最も安全)
組み込み型の名前をそのまま使う代わりに、typing から大文字の対応するクラスに置き換えます。冗長に見えますが、Python 3.5 まで遡って動作し、エコシステムのあらゆるツールと互換性があります。
from typing import Dict, FrozenSet, List, Optional, Set, Tuple, Type
def process(items: List[int]) -> Tuple[int, ...]:
return tuple(items)
def lookup(mapping: Dict[str, List[str]]) -> Optional[str]:
return mapping.get("key", [None])[0]
よく使う型の簡易チートシート:
# Python < 3.9 → Python 3.9+
List[int] → list[int]
Dict[str, int] → dict[str, int]
Tuple[int, ...] → tuple[int, ...]
Set[str] → set[str]
FrozenSet[str] → frozenset[str]
Type[MyClass] → type[MyClass]
Optional[str] → str | None (3.10+)
方法2: from future import annotations
インポート1行で、アノテーションの変更はゼロ。この行により、Python は実行時にアノテーションを評価する代わりに文字列として保存するようになります — Python 3.7 以降で PEP 585 構文を自由に記述できます。
from __future__ import annotations
def merge(a: list[str], b: list[str]) -> dict[str, int]:
return {k: i for i, k in enumerate(a + b)}
数百ファイルに及ぶ大規模なコードベースでは、これが最も手軽な解決策です — インポートを一括で検索・置換する必要がありません。ただし注意点が1つ:実行時にアノテーションをイントロスペクトするライブラリ(FastAPI の依存性注入、cattrs などのシリアライザ)は、実際の型オブジェクトではなく文字列を受け取ることになるため、追加の処理が必要になる場合があります。
方法3: Python 3.9 以上にアップグレードする
ランタイムを制御できる場合、これが最もクリーンな長期的解決策です。Python 3.9 では組み込みジェネリクスが第一級市民になりました — インポートも回避策も不要です。
# Python 3.9+ — インポート不要
def summarize(data: list[dict[str, int]]) -> list[int]:
return [sum(d.values()) for d in data]
補足: データクラスのフィールド
データクラスは気づきにくいトリガーです。関数アノテーションとは異なり、フィールドのアノテーションはクラス定義時に評価されます — そのため、関数呼び出し時ではなくインポート時にエラーが発生します。
# Python 3.8 では失敗する
from dataclasses import dataclass
@dataclass
class Report:
scores: dict[str, int] # インポート時にここで TypeError が発生
tags: list[str]
# 修正: future インポートを1行追加するだけでOK
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Report:
scores: dict[str, int] # これで問題なし
tags: list[str]
修正の確認
モジュールを直接実行してみてください。クリーンにインポートできれば完了です:
python my_module.py
スクリプト全体を実行せずに素早く確認する場合:
python -c "from my_module import get_scores; print(get_scores(['alice', 'bob']))"
アノテーションが正しい形式かどうかを確認したい場合は、直接検査できます:
import inspect
import my_module
print(inspect.signature(my_module.get_scores))
# Output: (names: List[str]) -> Dict[str, int]

