コース進捗 コース目次 24レッスン中 24件を公開中
Pythonの基礎
データとコレクション
信頼できるプログラムを作る
オブジェクトでモデル化する
プロフェッショナルなPython
上級Python
型ヒントは門番ではなく付箋
型ヒントは「この値は何であるべきか」を、読み手と静的ツールへ伝えます。通常は入口に立って、異なる実行時値を止める門番ではありません。
-
注釈
name: str - 型チェッカー 実行前に読む
- Python 実際の物を使う
def repeat(label: str, times: int) -> str:
return label * times
print(repeat("go", 3))
print(repeat(2, 3))
gogogo
6
型チェッカーなら2つ目を警告するはずですが、Pythonは整数の掛け算を実行します。利用者、ファイル、ネットワークから来るデータは、自分で検証します。注釈は自動変換もしません。
箱の中身と「ないかも」を説明する
コレクションの型ヒントは、入れ物と中身の両方を表します。
def total_by_topic(sessions: list[tuple[str, int]]) -> dict[str, int]:
totals: dict[str, int] = {}
for topic, minutes in sessions:
totals[topic] = totals.get(topic, 0) + minutes
return totals
print(total_by_topic([("Python", 20), ("Python", 25)]))
{'Python': 45}
ヒントが実行時に全要素を検査するわけではありません。必要な能力だけを求めます。反復するだけならIterable[str]、位置を読むならSequence[str]、リスト固有の変更が必要ならlist[str]が候補です。
str | Noneは文字列またはNoneで、Optional[str]の現代的な書き方です。int | strはUnion[int, str]と同じユニオンの考え方です。
def author_label(author: str | None) -> str:
if author is None:
return "不明"
return author.strip().title()
None分岐が戻った後、型チェッカーはauthorをstrへ絞り込みできます。Optionalは「引数を省略できる」という意味ではありません。author_label()だけでは引数不足です。省略も許すならauthor: str | None = Noneとデフォルトを付けます。
-
ユニオン
int | str -
調べる
isinstance(value, int) - intの道 整数操作が安全
- 残りの道 文字列操作が安全
型エイリアスは、繰り返す形へ分野の名前を付けます。
from typing import TypeAlias
Session: TypeAlias = tuple[str, int]
Python 3.12以降ならtype Session = tuple[str, int]とも書けます。エイリアスは語彙であり、新しい実行時クラスや検証器ではありません。フィールド名や規則が必要ならデータクラスを使います。
振る舞いと型の関係を説明する
Callable[[str], str]は、文字列1つで呼び出せて、文字列を返す物を表します。
from collections.abc import Callable
def clean_all(words: list[str], clean: Callable[[str], str]) -> list[str]:
return [clean(word) for word in words]
この契約を守る関数、束縛メソッド、呼び出し可能オブジェクトを渡せます。
TypeVarは、具体的な型1つではなく関係を保ちます。
from collections.abc import Sequence
from typing import TypeVar
T = TypeVar("T")
def first_or_none(items: Sequence[T]) -> T | None:
return items[0] if items else None
list[int]なら結果はint | None、list[str]ならstr | Noneです。object | Noneでは、この便利なつながりが消えます。本当の入出力関係があるときだけ型変数を加えます。
Protocolは能力を示すカード
Protocolは、利用側が必要とする小さな振る舞いに名前を付けます。クラスは、互換性のあるメンバーを持つことで構造的に満たします。継承は不要です。
-
Protocol
summary()が必要 - Article その能力がある
- Video こちらにもある
from typing import Protocol
class Summarizable(Protocol):
def summary(self) -> str:
...
def show(item: Summarizable) -> None:
print(item.summary())
Protocolは小さく保ちます。@runtime_checkableを付けると限定的なisinstance()検査ができますが、完全なメソッドシグネチャや注釈された値の型までは検証しません。
from typing import Protocol, runtime_checkable
@runtime_checkable
class Fetcher(Protocol):
def fetch(self) -> list[str]:
...
class WrongShape:
def fetch(self, required_argument):
return 42
print(isinstance(WrongShape(), Fetcher))
fetch属性が存在するため、これでもTrueになり得ます。静的検査と実行時のデータ検証は引き続き必要です。
ミニ制作:型付き通知ダイジェスト
notification_digest.pyを作ります。from __future__ import annotationsにより、この現代的な注釈を古めの対応インタープリターでも実行できます。型エイリアス、Callable、ジェネリックヘルパー、Protocol、本物の実行時検証を組み合わせます。
from __future__ import annotations
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
from typing import Protocol, TypeVar
RawNotice = tuple[str, int]
T = TypeVar("T")
@dataclass(frozen=True)
class Notice:
source: str
message: str
score: int
def __post_init__(self) -> None:
if not self.source.strip() or not self.message.strip():
raise ValueError("text must not be blank")
if isinstance(self.score, bool) or not isinstance(self.score, int):
raise TypeError("score must be an integer")
if not 0 <= self.score <= 100:
raise ValueError("score must be from 0 to 100")
class NoticeSource(Protocol):
def fetch(self) -> Iterable[Notice]:
...
class MemorySource:
def __init__(self, name: str, records: list[RawNotice]) -> None:
self.name = name
self.records = records
def fetch(self) -> Iterable[Notice]:
for message, score in self.records:
yield Notice(self.name, message, score)
def first_or_none(items: Sequence[T]) -> T | None:
return items[0] if items else None
def collect(
sources: Iterable[NoticeSource], keep: Callable[[Notice], bool]
) -> list[Notice]:
notices = [
notice
for source in sources
for notice in source.fetch()
if keep(notice)
]
return sorted(notices, key=lambda notice: (-notice.score, notice.source))
def format_digest(notices: Sequence[Notice]) -> str:
if not notices:
return "通知はありません。"
lines = ["通知ダイジェスト"]
lines.extend(
f"- {notice.score:03d} | {notice.source}: {notice.message}"
for notice in notices
)
top = first_or_none(notices)
if top is not None:
lines.append(f"最高得点の情報源:{top.source}")
return "\n".join(lines)
sources: list[NoticeSource] = [
MemorySource("Python", [("型ヒントのメモ", 88), ("スタイルガイド", 62)]),
MemorySource("テスト", [("回帰テスト一式", 91)]),
]
notices = collect(sources, lambda notice: notice.score >= 70)
print(format_digest(notices))
print(f"空のヘルパー:{first_or_none([]) is None}")
python3 notification_digest.pyで実行し、次を確認します。
通知ダイジェスト
- 091 | テスト: 回帰テスト一式
- 088 | Python: 型ヒントのメモ
最高得点の情報源:テスト
空のヘルパー:True
MemorySourceはNoticeSourceを継承していません。互換性のあるfetch()があれば静的な構造的型付けを満たします。境界の注意:型ヒントは得点範囲を強制しないため、データクラスが実行時に検証します。不正なコールバックも、実行中に失敗する可能性があります。
3つの小さなミッション
- ユニオンを絞る。
int | strを受け取り、isinstance()の後で整数はゼロ埋め、空でない文字列は大文字にします。 - 型を保つ。
last_or_none(items: Sequence[T]) -> T | Noneを書き、整数とNoticeで試します。 - 情報源を追加。
NoticeSourceを継承せず、タプルに保存した通知をfetch()するクラスを作ります。
第17章へ進む準備
- 型ヒントは通常、実行時値を強制しないと説明できる
- 必要な能力に合わせてコレクションへ注釈を付けられる
- Optional/Unionの考えを使い、実際の検査で絞り込める
- 型エイリアスは形に名前を付けるだけで、検証しないと分かる
Callableでコールバックへ注釈を付けられるTypeVarで入出力間の型関係を保てる- 小さな
Protocolを定義し、構造的に満たせる runtime_checkableは存在だけを調べ、完全なシグネチャを検証しないと分かる- ダイジェストを実行し、「空のヘルパー:True」を確認できる
次章では、イテレーターとジェネレーターが値を1つずつ作る仕組みを学びます。