Python のクラスと型ヒントの要点
内容の確認日: 2026-10-04
この分野は『Python実践レシピ』の第 4 章 (Python のクラス・3 問) と第 5 章 (型ヒント・2 問) に対応します。基礎試験のクラスの知識に加えて、property などのメソッドの種類、多重継承での super() の動き、dataclass のオプションが問われます。
型ヒントは「実行時には検査されない」という前提を押さえたうえで、list[str]・int | None などの書き方と、typing モジュールの主な型を覚えましょう。
クラス変数とインスタンス変数
クラスの直下で定義した変数はクラス変数で、すべてのインスタンスで共有されます。self.名前 = 値 で作るのはインスタンス変数で、インスタンスごとに別です。
インスタンスから属性を読むときは、インスタンス変数 → クラス変数の順に探します。ただし インスタンス.名前 = 値 と代入すると、クラス変数を書き換えるのではなく、同じ名前のインスタンス変数が新しく作られます。
class Player: count = 0 def __init__(self, name): self.name = name Player.count += 1a = Player("ann")b = Player("bob")a.count = 99print(a.count, b.count, Player.count)print(vars(a))python3 main.py 99 2 2 {'name': 'ann', 'count': 99}
名前の先頭のアンダースコアは慣習で、_name は「内部用」の目印です (アクセスは制限されません)。__name のように 2 つ付けると名前修飾 (ネームマングリング) され、_クラス名__name という名前で保存されるため、サブクラスとの名前の衝突を避けられます。
property・classmethod・staticmethod
@property を付けたメソッドは、obj.area のように属性として読み出せます。@名前.setter で代入時の処理を定義でき、setter がないプロパティに代入すると AttributeError になります。値の検証や、ほかの属性から計算する値に使います。
@classmethod は第 1 引数にクラス (cls) を受け取るメソッドで、cls(...) でインスタンスを作る「代替コンストラクター」によく使います。@staticmethod は self も cls も受け取らない関数を、クラスの中にまとめるときに使います。
class Money: def __init__(self, yen): self.yen = yen @property def yen(self): return self._yen @yen.setter def yen(self, value): if value < 0: raise ValueError("負の金額") self._yen = value @classmethod def from_text(cls, text): return cls(int(text.removesuffix("円"))) @staticmethod def tax(yen): return yen // 10m = Money.from_text("1200円")print(m.yen, Money.tax(m.yen))try: m.yen = -1except ValueError as e: print(e)python3 main.py 1200 120 負の金額
__init__ の中の self.yen = yen もプロパティの setter を通るので、生成時の値も検証されます。
特殊メソッド
__ で囲まれた名前のメソッドを定義すると、組み込みの関数や演算子の動きを決められます。
・__repr__・__str__: 表示用の文字列。print() や f-string は __str__ を、リストの中の要素や !r は __repr__ を使う (__str__ がなければ __repr__ が使われる)
・__eq__・__lt__ など: ==・< などの比較
・__add__ など: + などの演算子
・__len__・__getitem__・__iter__・__contains__: len()・obj[i]・for・in
・__call__: obj() のように呼び出せるようにする
__eq__ だけを定義すると __hash__ が None になり、そのクラスのインスタンスは辞書のキーや集合の要素に使えなくなります。等しいもの同士は同じハッシュ値を持つ、という規則を守るためです。
class Deck: def __init__(self, cards): self.cards = list(cards) def __len__(self): return len(self.cards) def __getitem__(self, index): return self.cards[index] def __repr__(self): return f"Deck({self.cards})"deck = Deck("ABC")print(len(deck), deck[0], deck[-1], "B" in deck, list(deck))print([deck])python3 main.py 3 A C True ['A', 'B', 'C'] [Deck(['A', 'B', 'C'])]
__getitem__ を定義すると、__iter__ や __contains__ がなくても、for や in が先頭から順に取り出して動きます。
継承と MRO・抽象基底クラス
super() は「親クラス」ではなく、メソッド解決順序 (MRO) で次のクラスを指します。多重継承では、MRO は クラス.__mro__ で確認でき、各クラスの super() をたどると、すべてのクラスのメソッドが 1 回ずつ呼ばれます。
class Base: def setup(self): print("Base")class Logging(Base): def setup(self): print("Logging") super().setup()class Cache(Base): def setup(self): print("Cache") super().setup()class App(Logging, Cache): passApp().setup()print([c.__name__ for c in App.__mro__])python3 main.py Logging Cache Base ['App', 'Logging', 'Cache', 'Base', 'object']
Logging.setup の super() は Base ではなく、MRO で次の Cache を指します。
abc.ABC を継承し、@abstractmethod を付けたメソッドを持つクラスは抽象基底クラスになり、インスタンスを作ろうとすると TypeError になります。抽象メソッドをすべて実装したサブクラスだけがインスタンス化できるので、「このメソッドを必ず実装すること」を強制できます。
dataclass
@dataclass を付けたクラスでは、型ヒント付きのクラス変数がフィールドになり、__init__・__repr__・__eq__ が自動で作られます。主なオプションは次のとおりです。
・frozen=True: フィールドへの代入を禁止する (代入すると FrozenInstanceError)。__hash__ も作られ、辞書のキーに使える
・order=True: < などの比較メソッドを作る。フィールドを定義順に並べたタプルとして比べる
・field(default_factory=list): リストなど変更可能な既定値は、インスタンスごとに作る (= [] と書くとエラー)
・field(init=False)・field(repr=False)・field(compare=False): __init__・__repr__・比較の対象から外す
・__post_init__: 自動生成の __init__ の最後に呼ばれる。ほかのフィールドから値を計算するのに使う
from dataclasses import dataclass, field, asdict@dataclass(order=True)class Task: priority: int title: str = field(compare=False) tags: list[str] = field(default_factory=list, repr=False)tasks = [Task(2, "書く"), Task(1, "調べる"), Task(3, "見直す")]print(min(tasks))print([t.title for t in sorted(tasks)])print(asdict(Task(1, "x", ["a"])))python3 main.py Task(priority=1, title='調べる') ['調べる', '書く', '見直す'] {'priority': 1, 'title': 'x', 'tags': ['a']}
既定値のないフィールドを、既定値のあるフィールドより後に書くと TypeError になります (__init__ の引数の並びが作れないため)。
オブジェクト関連関数
属性の名前を文字列で扱う関数として、getattr(obj, 名前, 既定値)・setattr(obj, 名前, 値)・hasattr(obj, 名前)・delattr(obj, 名前) があります。getattr は既定値を渡すと、属性がないときに AttributeError ではなく既定値を返します。
型を調べるには、サブクラスも含めて判定する isinstance(obj, 型) を使います。第 2 引数はタプルで複数の型を指定できます。type(obj) is 型 は型が完全に一致するときだけ真になるので、bool の値に対して type(True) is int は False です (bool は int のサブクラス)。
class Settings: debug = Falses = Settings()for key, value in {"debug": True, "level": "info"}.items(): setattr(s, key, value)print(getattr(s, "debug"), getattr(s, "port", 8000), hasattr(s, "level"))print(vars(s))print(isinstance(True, int), type(True) is int, isinstance(1.5, (int, float)))python3 main.py True 8000 True {'debug': True, 'level': 'info'} True False True
vars(obj) はインスタンスの属性の辞書 (__dict__) を返し、クラス変数は含みません。ほかに、属性の一覧を返す dir()、呼び出せるかを調べる callable()、オブジェクトの ID を返す id() などがあります。
型ヒント
型ヒントは、引数・戻り値・変数の型を def f(name: str, n: int = 1) -> str: のように書く注釈です。実行時には検査されず、違う型を渡してもエラーにはなりません。誤りは mypy などの静的型チェッカーや、エディターの補完・警告で見つけます。注釈は 関数.__annotations__ から参照できます。
Python 3.9 以降は list[str]・dict[str, int]・tuple[int, ...] のように組み込み型に直接型引数を書け、3.10 以降は int | None のように | で「いずれかの型」を書けます。Optional[int] は int | None と同じ意味です。
typing モジュールの主な型は次のとおりです。
・Any: どんな型とも互換性がある
・Callable[[int, str], bool]: 引数と戻り値の型を指定した呼び出し可能オブジェクト
・Literal["r", "w"]: 決まった値だけを取る
・Final: 再代入しない定数 (実行時には検査されない)
・TypedDict・NamedTuple: キーやフィールドに型を付けた辞書・タプル
・Protocol: 「このメソッドを持っていればよい」という構造的な型 (継承していなくても、同じメソッドを持てば互換とみなされる)
Python 3.12 からは、def first[T](items: list[T]) -> T: のように関数名やクラス名の後ろで型変数を宣言でき、type 名前 = 型 で型エイリアスを定義できます。
from typing import Protocolclass Closable(Protocol): def close(self) -> None: ...class File: def close(self) -> None: print("ファイルを閉じた")def close_all[T: Closable](items: list[T]) -> int: for item in items: item.close() return len(items)def double(n: int) -> int: return n * 2print(close_all([File(), File()]))print(double("ab"), double.__annotations__)python3 main.py ファイルを閉じた ファイルを閉じた 2 abab {'n': <class 'int'>, 'return': <class 'int'>}
double("ab") は型ヒントに反しますが、実行時には検査されないので "ab" * 2 が計算されます。[T: Closable] は「T は Closable を満たす型」という制約付きの型変数です。
試験でよくある引っかけ
インスタンスからクラス変数に代入する
a.count = 99はクラス変数を変えず、aだけのインスタンス変数を作ります。setter のないプロパティ
@propertyだけのプロパティに代入するとAttributeErrorです。super()が指すクラス多重継承では、
super()は MRO で次のクラスを指します。親クラスとは限りません。抽象クラスのインスタンス化
抽象メソッドが残っているクラスをインスタンス化すると
TypeErrorです。dataclassの変更可能な既定値tags: list = []はエラーです。field(default_factory=list)を使います。__eq__だけを定義したクラス__hash__がNoneになり、辞書のキーや集合に使えなくなります。type()とisinstance()の違いtype(True) is intはFalse、isinstance(True, int)はTrueです。型ヒントは実行時に検査されない
型ヒントと違う型を渡しても、実行時のエラーにはなりません。
問題で確認する
この分野の問題を、解説つきで読めます (登録不要)。
出典・参考
- Python エンジニア育成推進協会「Python 実践試験」(出題範囲)
- Python ドキュメント「クラス」
- Python ドキュメント「dataclasses」
- Python ドキュメント「abc」
- Python ドキュメント「typing」
コード例は、実際に実行して結果を確認しています。