42. 型ヒントへのやさしい入門(任意)
この本を通して、これまであなたは、変数がどの種類のデータ(型)を保持するのか、また関数(function)がどの型を受け取り、どの型を返すのかを明示せずに Python コードを書いてきました。Python はこのやり方でまったく問題なく動作します。Python は 動的型付け(dynamically typed) の言語であり、プログラムが実行される実行時に型が決まるという意味です。この柔軟性は Python の最大の強みの一つで、素早く表現力豊かにコードを書けます。
しかし、プログラムが大規模で複雑になるにつれて、この柔軟性が、コードの理解や保守を難しくしてしまうことがあります。def process_data(items): のような関数を見ると、あなたはこう思うかもしれません。items にはどんな種類のデータが入っているのか?文字列のリスト(list)?辞書(dictionary)?それともまったく別のもの?
型ヒント(type hints)(型アノテーション(type annotations) とも呼ばれます)は、コード内で期待する型を文書化するための方法を提供します。Python への任意の追加要素で、コードをより明確にし、エラーを早期に見つけやすくし、強力な IDE 機能を有効にできます。しかも、Python が実際にコードを実行する仕組み自体は変わりません。
この章では、型ヒントをやさしく紹介し、それが何で、なぜ存在し、どのように効果的に使うのかを示します。型ヒントは任意であり、Python がコードを実行する方法には影響しないため、この章全体は任意として扱います。型ヒントを一度も使わなくても、優れた Python プログラムは書けます。とはいえ、型ヒントを理解しておくと、現代の Python コードを読む助けになり、自分のプロジェクトで役に立つ場面を判断できるようになります。
42.1) 型ヒントが Python に追加された理由
Python は最初から動的型付け言語として設計されました。何十年もの間、Python プログラマは型情報なしでコードを書いてきましたし、それは無数のプロジェクトで素晴らしく機能してきました。ではなぜ、2015 年(Python 3.5)に型ヒントが Python に追加されたのでしょうか?
大規模コードベースの課題
Python が大規模なアプリケーションでより人気になるにつれて、チームは次のような課題に直面しました。
# 大規模なコードベースでは、この関数は何を受け取り、何を返すのでしょうか?
def calculate_discount(customer, items, code):
# ... 50 lines of code ...
return result関数本体全体やドキュメントを読まない限り、次のことが分かりません。
customerは辞書なのか、独自のオブジェクトなのか、それとも別のものなのか?itemsはリストなのか、タプル(tuple)なのか、セット(set)なのか?codeの型は何か—文字列なのか、整数なのか?- 関数は何を返すのか—数値なのか、辞書なのか、あるいは
Noneなのか?
小さなプログラムなら、この曖昧さは何とかなります。ほかの場所でその関数がどう使われているかを簡単に確認できます。ですが、何十ものファイルにまたがって何千もの関数があるコードベースでは、これは難しくなります。
解決策:任意の型ヒント
Python の開発者は、型を文書化するための 任意 の仕組みを追加することにしました。重要なキーワードは「任意」です。型ヒントは完全に自由意思で、役立つときに使い、役立たないときには無視でき、注釈付きと注釈なしのコードを自由に混在させられます。
基本構文を示すために、簡単な例を見てみましょう。
# 型ヒントなし
def add(a, b):
return a + b
# 型ヒントあり
def add(a: int, b: int) -> int:
return a + b構文は単純です。
- 引数の後ろの コロン(
:) は、その引数がどの型であるべきかを示します:a: int - アロー(
->) は、関数がどの型を返すかを示します:-> int
では先ほどの例を、これで見てみましょう。
def calculate_discount(customer: dict, items: list, code: str) -> float:
# ... 50 lines of code ...
return resultこれで即座に明確になります。customer は辞書、items はリスト、code は文字列、そして関数は float を返します。
この構文に見慣れなくても心配しないでください。42.3〜42.6 節で詳しく見ていきます。今は、関数が何を期待し、何を返すのかが一目で分かることに注目してください。
型ヒントの有無に関わらず、関数はまったく同じように動作します。Python は実行時にこれらの型をチェックしません。(この重要な点は 42.2 節で詳しく説明します)
段階的で実用的なアプローチ
Python の型ヒントシステムは、次のようになるよう設計されています。
- 任意:型ヒントを使う必要はまったくありません
- 段階的:コードの一部にはヒントを付け、ほかの部分には付けない、ということができます
- 非侵入的:ヒントは Python がコードを実行する方法を変えません
- ツールに優しい:外部ツールはヒントをチェックできますが、Python 自体は実行時には無視します
この実用的なアプローチにより、Python は柔軟性を保ちつつ、必要とする人にはメリットを提供できます。
42.2) 黄金律:実行時には強制されない
型ヒントについて理解すべき最も重要なことはこれです。Python は実行時に型ヒントを強制しません。型ヒントは純粋に情報としての役割しかありません。この驚くべき現実が実際には何を意味するのか、見てみましょう。
型ヒントは間違った型を防がない
型ヒント付きのこの関数を考えてください。
def greet(name: str) -> str:
return f"Hello, {name}!"
# 42 は文字列ではありませんが、これは問題なく動きます
result = greet(42)
print(result) # Output: Hello, 42!型ヒントは name が文字列であるべきだと明確に示していますが、Python は整数 42 を喜んで受け取り、関数を実行します。Python は型ヒントをチェックしません。渡された値をそのまま使います。
これは、Java や C++ のような言語とは根本的に異なります。これらの言語では、コンパイラが実行前に型をチェックし、型の不一致があれば実行を拒否します。Python のアプローチはより寛容で、正しい型を渡すことをあなたに任せますが、強制はしません。
問題:動的型付けのリスクは残る
ここが本当の課題です。型ヒントがあっても、Python が動的型付けである以上、実行時にしか現れない型のミスは依然として起こり得ます。
def calculate_total(prices: list) -> float:
"""Calculate the sum of prices."""
return sum(prices)
# これは問題なく動きます
print(calculate_total([10.99, 5.50, 3.25])) # Output: 19.74
# しかし、これは実行時に失敗します!
print(calculate_total("not a list")) # TypeError: 'str' object is not iterable型ヒントは prices がリストであるべきだと明確に示していますが、Python は文字列を渡すことを止めません。エラーは、コードが実際に実行され、文字列に対して sum() を使おうとしたときに初めて現れます。
これはイライラします! 問題を捕まえるために型ヒントを追加したのに、動的型付けのリスクはまだ残っています。型エラーは実行時までコード内に潜み続け、ユーザーが想定外のことをしたときに本番環境で発生する可能性すらあります。
では、型ヒントが実行時エラーを防がないなら、使う意味は何でしょうか?
では、型ヒントは何のため?
型ヒントは Python の実行時の挙動を変えませんが、重要な目的があります。型ヒントは Python 自体のためではなく、人とツール のために情報を提供します。
- ドキュメント:関数がどの型を受け取り、どの型を返すかを教えてくれます
- IDE 支援:エディタがヒントを使ってオートコンプリートや警告表示を提供できます
- 静的解析(static analysis):外部ツール(mypy など)が、実行前に型エラーをチェックできます
- コード理解:大規模なコードベースを読みやすくし、保守しやすくします
型ヒントは、ツールが理解できるコメント だと考えてください。Python の実行方法は変えませんが、より良いコードを書く助けになります。
では、先ほど見た実行時エラーを捕まえるのに、実際にはどのように役立つのでしょうか?
解決策:型ヒント + IDE 支援
ここで型ヒントが本領を発揮します。Python は実行時に型ヒントを強制しませんが、IDE はコードを実行する前にミスを見つけられます。
def add_numbers(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
# ここで IDE が警告を表示します(コードを実行する前に)
result = add_numbers("Hello", "World") # IDE: Warning - expected int, got strコードエディタは型ヒントを見て、型の不一致を 入力中 に警告できます。コードを実行するずっと前にです。これにより、多くのバグが本番ではなく開発中に見つかります。
現代の Python 開発は一般的に次のように進みます。
- 型ヒント付きでコードを書く
- IDE が型が合わない箇所で警告を表示する
- 実行前に問題を修正する
- 型の不一致による実行時エラーはずっと起きにくくなる
型ヒント自体が実行時のエラーを防ぐわけではありませんが、IDE がそれを使って、そもそもバグのあるコードを書かないようにしてくれるのです!
両方の良いところ取り
型ヒントは、柔軟性を保ちながら多くのエラーを早期に捕まえることで、Python に「良いところ取り」をもたらします。
開発時の安全性:IDE や型チェッカーが開発中にほとんどの型エラーを捕まえるので、早い段階でバグを見つけられます。
def process(data: list) -> list:
return [x * 2 for x in data]
# うっかり文字列を渡した場合:
process("hello") # IDE warns: expected list, got str
# 実行する前に直せます!実行時の柔軟性:Python は型が合わなくても実行してくれます。これは、素早いプロトタイピングや、意図的に複数の型を受け入れたい場合に便利です。
def add_numbers(a: int, b: int) -> int:
return a + b
# 型が合わなくても Python は実行します
print(add_numbers(5.5, 3.2)) # Output: 8.7 (works!)
print(add_numbers("Hi", " there")) # Output: Hi there (also works!)この柔軟性のおかげで、厳格な型システムに縛られることはありません。ルールを破る必要があるとき(テスト、プロトタイピング、または正当なユースケース)でも、Python は許してくれます。しかし本番コードを書くときは、IDE が安全を保ってくれます。
黄金律を忘れないでください:型ヒントは Python の実行時の挙動を変えません。あなたとツールに、問題を早く見つけるための情報を与えるだけです。注意は必要ですが、今や強力な味方があなたを支えてくれます。
42.3) 関数への注釈:引数と戻り値
型ヒントの最も一般的な使い方は、関数の引数と戻り値に注釈を付けることです。これにより、関数がどの型を受け取り、どの型を生成するのかが読者(およびツール)に伝わります。最も単純なケースから始めて、段階的に見ていきましょう。
基本:引数の注釈
引数に型ヒントを付けるには、引数名の後ろにコロンを置き、その後に型を書きます。
def greet(name: str):
"""Greet a person by name."""
return f"Hello, {name}!"
# Usage
message = greet("Alice")
print(message) # Output: Hello, Alice!name: str という構文は、「引数 name は文字列であるべき」という意味です。複数の引数に型ヒントを付けることもできます。
def calculate_area(width: float, height: float):
"""Calculate the area of a rectangle."""
return width * height
# Usage
area = calculate_area(5.0, 3.0)
print(area) # Output: 15.0ここでは width と height の両方が float として注釈付けされています。関数は以前と同じように動作します(型ヒントは挙動を変えません)が、これで IDE は期待すべき型が分かります。
戻り値の型注釈を追加する
関数が返す型を指定するには、引数リストの後、コロンの前に -> type を追加します。
def get_full_name(first: str, last: str) -> str:
"""Combine first and last names."""
return f"{first} {last}"
# Usage
name = get_full_name("John", "Doe")
print(name) # Output: John Doe-> str は「この関数は文字列を返す」という意味です。戻り値の型注釈は、関数名から戻り値の型が分かりにくい場合に特に役立ちます。
def is_adult(age: int) -> bool:
"""Check if someone is an adult (18 or older)."""
return age >= 18
# Usage
adult = is_adult(25)
print(adult) # Output: True実装を見なくても、この関数が boolean 値を返すことがすぐに分かります。
まとめる:完全な関数
多くの関数では、引数と戻り値の両方に型注釈が付きます。完全に注釈付けされた関数は次のようになります。
def calculate_discount(price: float, discount_percent: float) -> float:
"""Calculate the discounted price."""
discount_amount = price * (discount_percent / 100)
return price - discount_amount
# Usage
original_price = 100.0
discount = 20.0
final_price = calculate_discount(original_price, discount)
print(f"Final price: ${final_price:.2f}") # Output: Final price: $80.00この関数シグネチャ(signature)は、知りたいことをすべて教えてくれます。
floatの引数を 2 つ受け取る:priceとdiscount_percentfloatを返す- 実装を読まなくても、この関数の使い方を理解できます
別の型を使った例も見てみましょう。
def repeat_message(message: str, times: int) -> str:
"""Repeat a message a specified number of times."""
return message * times
# Usage
repeated = repeat_message("Hello! ", 3)
print(repeated) # Output: Hello! Hello! Hello! 型ヒントにより、文字列と整数を渡し、文字列が返ることがはっきり分かります。
デフォルト値を扱う
引数にデフォルト値がある場合は、引数名とデフォルト値の間に型ヒントを置きます。
def create_greeting(name: str, formal: bool = False) -> str:
"""Create a greeting message."""
if formal:
return f"Good day, {name}."
return f"Hi, {name}!"
# Usage
print(create_greeting("Alice")) # Output: Hi, Alice!
print(create_greeting("Bob", formal=True)) # Output: Good day, Bob.formal: bool = False という構文は、「formal は boolean で、デフォルト値が False」という意味です。
デフォルト付きの引数を複数持ち、それらすべてに注釈を付けることもできます。
def format_price(amount: float, currency: str = "USD", decimals: int = 2) -> str:
"""Format a price with currency symbol."""
if currency == "USD":
symbol = "$"
elif currency == "EUR":
symbol = "€"
else:
symbol = currency
return f"{symbol}{amount:.{decimals}f}"
# Usage
print(format_price(99.99)) # Output: $99.99
print(format_price(99.99, "EUR")) # Output: €99.99
print(format_price(99.995, "USD", 3)) # Output: $99.995各引数が型とデフォルト値をはっきり示すため、関数を理解して使うのが簡単になります。
特別なケース:値を返さない関数
一部の関数は、値を返さずに(表示したりファイルに書き込んだりなどの)処理だけを行います。そのような関数が何も返さないことを明確にするには、-> None を使います。
def print_report(title: str, data: list) -> None:
"""Print a formatted report."""
print(f"=== {title} ===")
for item in data:
print(f" - {item}")
# return 文がないので、暗黙的に None を返します
# Usage
print_report("Sales Data", [100, 150, 200])Output:
=== Sales Data ===
- 100
- 150
- 200-> None の注釈は、この関数が意味のある値を返さないことを明示します。
なぜ -> None を使うのですか?
- 明確さ:意図を明確にできます—この関数は結果ではなく処理のためのものです
- IDE 支援:誤って戻り値を使おうとした場合に IDE が警告できます
42.4) シンプルな変数の注釈
型ヒントは関数で最もよく使われますが、変数にも注釈を付けられます。ここではその仕組みと、実際にいつ役立つのかを見ていきます。
基本的な変数注釈の構文
変数に注釈を付けるには、関数引数と同じコロン構文を使います。
# 変数に注釈を付ける
name: str = "Alice"
age: int = 30
height: float = 5.7
is_student: bool = True
print(f"{name} is {age} years old") # Output: Alice is 30 years oldname: str = "Alice" という構文は、「変数 name は文字列で、値は 'Alice'」という意味です。この注釈は変数の動作を変えません。純粋に情報です。
変数注釈は省略されることが多い
実際には、変数注釈はほとんど使われません。理由は単純で、Python は値から型を推論できるため、注釈がたいてい冗長だからです。
# これらの注釈は不要です
name: str = "Alice" # 明らかに文字列
count: int = 0 # 明らかに int
prices: list = [10.99, 5.50] # 明らかに list
settings: dict = {} # 明らかに dict
# 代わりにこう書けばよいです
name = "Alice"
count = 0
prices = [10.99, 5.50]
settings = {}name = "Alice" と書けば、あなたにも IDE にも、それが文字列であることはすぐに分かります。注釈を付けても有益な情報は増えません。
実際の Python コードでは、変数注釈はめったに見かけません。 これは普通で、想定どおりです。関数注釈のほうがはるかに重要で一般的です。
唯一役立つケース:代入前に変数を宣言する
変数注釈が本当に役立つ状況が 1 つあります。それは、値を代入する前に変数を宣言する必要があるときです。
def calculate_statistics(numbers: list) -> dict:
"""Calculate basic statistics from a list of numbers."""
# 使う前に変数を宣言する
total: float
count: int
average: float
# ここで値を代入する
total = sum(numbers)
count = len(numbers)
average = total / count if count > 0 else 0.0
return {
"total": total,
"count": count,
"average": average
}
# Usage
result = calculate_statistics([10, 20, 30, 40])
print(f"Average: {result['average']}") # Output: Average: 25.0注釈がないと、値を代入せずに変数だけを宣言することはできません。注釈を使うと、先に型を指定できるため、コード構造がより明確になる場合があります。
これが、変数注釈の主な実用ケースです。
覚えておきましょう:変数は別の型に再代入できる
型注釈があっても、変数を別の型に再代入できます。
# 文字列から始める
value: str = "hello"
print(value) # Output: hello
# 別の型に再代入する - Python はこれを許します
value = 42
print(value) # Output: 42
# さらに型を変える - それでも許されます
value = [1, 2, 3]
print(value) # Output: [1, 2, 3]IDE や静的型チェッカーはこれらの型変更を警告しますが、Python 自体は防ぎません。型ヒントは一貫性へ導きますが、実行時に強制はしません。
42.5) 「None」の扱い:Optional 型と | 演算子
Python で最も一般的なパターンの 1 つが、「値を返すかもしれないし、None を返すかもしれない」関数です。たとえば、何かを検索して成功すれば項目を返し、失敗すれば None を返す、ということがあります。型ヒントは、このパターンを明確に表現する方法を提供します。
問題:None を返すかもしれない関数
メールアドレスでユーザーを検索する次の関数を考えてみましょう。
def find_user_by_email(email: str) -> dict:
"""Find a user by email address."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None # 型の不一致!-> dict ヒントと矛盾します
# Usage
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
else:
print("User not found")この関数は None を返し得るため、型ヒント -> dict は誤解を招きます。静的型チェッカーは、None を返すことが宣言された戻り値型 dict と一致しないと警告するでしょう。
解決策:Optional 型に | 演算子を使う
Python 3.10 では、型ヒントで | 演算子が導入されました。これは「または」を意味します。これを使うと、関数がある型または別の型を返す可能性があることを示せます。
def find_user_by_email(email: str) -> dict | None:
"""Find a user by email address. Returns None if not found."""
users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]
for user in users:
if user["email"] == email:
return user
return None
# Usage
user = find_user_by_email("alice@example.com")
if user:
print(f"Found: {user['name']}") # Output: Found: Alice
missing = find_user_by_email("charlie@example.com")
if missing is None:
print("User not found") # Output: User not found型ヒント -> dict | None は「この関数は辞書か None のどちらかを返す」という意味です。これは関数の挙動を正確に表します。
注: 古い Python コード(3.10 より前)では、dict | None の代わりに typing モジュールの Optional[dict] を見かけることがあります。意味は同じですが、| のほうが現代的で推奨される構文です。
複数の型で | を使う
| を使うと、2 つ以上の型を示すこともできます。
def parse_value(text: str) -> int | float | None:
"""Parse a string into a number. Returns None if parsing fails."""
try:
# まず整数としてパースを試す
if '.' not in text:
return int(text)
# それ以外は float としてパースする
return float(text)
except ValueError:
return None
# Usage
print(parse_value("42")) # Output: 42 (int)
print(parse_value("3.14")) # Output: 3.14 (float)
print(parse_value("invalid")) # Output: None型ヒント -> int | float | None は、関数が整数、float、または None を返せることを意味します。
None のチェック:ベストプラクティス
関数が None を返し得る場合、結果を使う前に必ず None をチェックしてください。そうしないと、期待する型として None を扱おうとしてエラーになるリスクがあります。
def get_user_age(user_id: int) -> int | None:
"""Get user's age. Returns None if user not found."""
users = {1: 25, 2: 30, 3: 35}
return users.get(user_id)
# 値を使う前に必ず None をチェックする
age = get_user_age(1)
if age is not None:
print(f"User is {age} years old") # Output: User is 25 years old
if age >= 18:
print("User is an adult") # Output: User is an adult
else:
print("User not found")
# 存在しないユーザーの場合
age = get_user_age(999)
if age is None:
print("User not found") # Output: User not foundポイントは、値を使う前に if age is not None: や if age is None: を使って明示的にチェックすることです。
| None を使った任意の引数
| は引数にも使えます。多くの場合、デフォルト値と組み合わせます。
def format_name(first: str, middle: str | None = None, last: str = "") -> str:
"""Format a full name. Middle name is optional."""
if middle and last:
return f"{first} {middle} {last}"
elif last:
return f"{first} {last}"
return first
# Usage
print(format_name("John", "Q", "Doe")) # Output: John Q Doe
print(format_name("Jane", None, "Smith")) # Output: Jane Smith
print(format_name("Prince")) # Output: Prince型ヒント middle: str | None = None は、middle が文字列または None になり得て、デフォルト値が None であることを示します。これは任意の引数でよく使われるパターンです。
42.6) よくある型ヒントの読み方:list、dict、tuple
他の人が書いた Python コードを読むと、リストや辞書、タプルといったコレクション(collection)の型ヒントに出会います。現代の Python では、単に「リストである」だけでなく、そのリストにどんな型の要素が入るのかも明確に指定できます。
注: ここで示す構文(list[int]、dict[str, int] など)は Python 3.9+ で動作します。古いコードでは、typing モジュールの List[int] や Dict[str, int](先頭が大文字)を見かけることがありますが、同じように動作します。
基本的なコレクション型ヒント
最も単純なコレクションの型ヒントは、コレクションの種類だけを指定します。
def print_items(items: list) -> None:
"""Print all items in a list."""
for item in items:
print(item)
def get_user_settings() -> dict:
"""Get user settings as a dictionary."""
return {"theme": "dark", "notifications": True}
def get_position() -> tuple:
"""Get x, y position."""
return (10, 20)これらのヒントはコレクションの型は教えてくれますが、中身については分かりません。
リスト:要素の型を指定する
リストにどの型の要素が入るのかを指定するには、角括弧を使います。
def calculate_total(prices: list[float]) -> float:
"""Calculate the total of all prices."""
return sum(prices)
# Usage
total = calculate_total([10.99, 5.50, 3.25])
print(f"Total: ${total:.2f}") # Output: Total: $19.74型ヒント list[float] は「float を含むリスト」という意味です。単に list とするより情報量があります。
文字列の例も見てみましょう。
def format_names(names: list[str]) -> str:
"""Format a list of names as a comma-separated string."""
return ", ".join(names)
# Usage
students = ["Alice", "Bob", "Charlie"]
print(format_names(students)) # Output: Alice, Bob, Charlie型ヒント list[str] は「文字列を含むリスト」という意味です。
辞書:キーと値の型を指定する
辞書では、キーの型と値の型の両方を指定します。
def get_student_grades() -> dict[str, int]:
"""Get student names mapped to their grades."""
return {
"Alice": 95,
"Bob": 87,
"Charlie": 92
}
# Usage
grades = get_student_grades()
for name, grade in grades.items():
print(f"{name}: {grade}")Output:
Alice: 95
Bob: 87
Charlie: 92型ヒント dict[str, int] は「キーが文字列で、値が整数の辞書」という意味です。
値が複数の型になり得る例もあります。
def get_user_data(user_id: int) -> dict[str, str | int]:
"""Get user data. Values can be strings or integers."""
return {
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"id": 12345
}
# Usage
user = get_user_data(1)
print(f"{user['name']} is {user['age']} years old") # Output: Alice is 30 years old型ヒント dict[str, str | int] は「キーが文字列で、値が文字列または整数の辞書」という意味です。
タプル:固定長と可変長
タプルはリストとは異なり、固定の構造を持つことがよくあります。各位置の型を指定できます。
def get_user_info(user_id: int) -> tuple[str, int, bool]:
"""
Get user information as a tuple.
Returns: (name, age, is_active)
"""
return ("Alice", 30, True)
# Usage
name, age, active = get_user_info(1)
print(f"{name}, {age}, active: {active}") # Output: Alice, 30, active: True型ヒント tuple[str, int, bool] は「要素がちょうど 3 つのタプルで、順に文字列、整数、boolean」という意味です。
同じ型の要素からなる可変長のタプルには、省略記号(...)を使います。
# 固定長タプル:float がちょうど 2 つ
def get_2d_point() -> tuple[float, float]:
"""Get 2D coordinates (x, y)."""
return (10.5, 20.3)
# 可変長タプル:float がいくつでも
def get_coordinates() -> tuple[float, ...]:
"""Get coordinates. Can be 2D, 3D, or any dimension."""
return (10.5, 20.3, 15.7) # この場合は 3D
# Usage
point = get_2d_point()
coords = get_coordinates()
print(f"2D point: {point}") # Output: 2D point: (10.5, 20.3)
print(f"Coordinates: {coords}") # Output: Coordinates: (10.5, 20.3, 15.7)型ヒント tuple[float, ...] は「float が任意個入ったタプル」という意味です。... は「この型が任意の個数」という意味です。
ネストしたコレクション
複雑なデータ構造では、型ヒントをネストできます。まずは簡単な例から始めましょう。
def get_scores_by_student() -> dict[str, list[int]]:
"""Get test scores for each student."""
return {
"Alice": [95, 87, 92],
"Bob": [88, 91, 85],
"Charlie": [90, 88, 94]
}
# Usage
scores = get_scores_by_student()
for name, tests in scores.items():
average = sum(tests) / len(tests)
print(f"{name}: {average:.1f}")Output:
Alice: 91.3
Bob: 88.0
Charlie: 90.7型ヒント dict[str, list[int]] は「キーが文字列で、値が整数のリストの辞書」という意味です。
さらに複雑な例です。
def get_student_records() -> list[dict[str, str | int]]:
"""Get a list of student records."""
return [
{"name": "Alice", "age": 20, "major": "CS"},
{"name": "Bob", "age": 21, "major": "Math"},
{"name": "Charlie", "age": 19, "major": "Physics"}
]
# Usage
students = get_student_records()
for student in students:
print(f"{student['name']}, {student['age']}, {student['major']}")Output:
Alice, 20, CS
Bob, 21, Math
Charlie, 19, Physics型ヒント list[dict[str, str | int]] は「辞書のリストで、各辞書はキーが文字列、値が文字列または整数」という意味です。
型ヒントを読む:クイックリファレンス
コードで型ヒントに出会ったとき、次のように読めます。
コレクション:
list[int]- 「整数のリスト」dict[str, float]- 「キーが文字列で、値が float の辞書」tuple[str, int]- 「要素がちょうど 2 つのタプルで、最初が文字列、次が整数」tuple[float, ...]- 「float が任意個入ったタプル」
Optional と複数の型:
int | None- 「整数または None」str | int | float- 「文字列、整数、または float」
ネスト:
list[dict[str, int]]- 「辞書のリスト(各 dict はキーが文字列、値が整数)」dict[str, list[float]]- 「キーが文字列で、値が float のリストの辞書」
注: 古いコード(Python < 3.10)では、int | str の代わりに Union[int, str]、int | None の代わりに Optional[int] を見かけることがあります。意味は同じです。