「オブジェクト指向プログラミング」を取り巻く中核キーワード群です。 検索やインデックス作成で参照する際の手がかりにしてください。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になります。
🍰 まずはやさしく
データと処理をセットにする方法です。
効率よくプログラムを作るために使います。
スマホのアプリ開発などで役立ちます。
この章では基本の考え方を学びます。
最も忙しい読者のために、 まず結論だけまとめます。 詳細は以下のセクションへ:
sklearn の fit/predict が典型 OOP。🍰 まずはやさしく
プログラムの書き方のルールです。
コードの意味を正しく理解するために使います。
機械学習のライブラリでよく出会います。
この章では使いどころを解説します。
「sklearn の model.fit(X, y) って何で model から呼ぶの?」 — それは model が オブジェクト で、 fit がそのメソッドだから。 OOP の世界に足を踏み入れています。
このページの読み方:まず 30秒結論 と 直感 を読み、 必要に応じて 数式 や 計算例、 落とし穴 に進んでください。
🍰 まずはやさしく
設計図から実物を作るイメージです。
複雑な仕組みをシンプルに整理して使います。
部活の役割分担のように考えます。
この章では具体的な例で仕組みを学びます。
「車」というクラスを考えます:
「私の車」は車クラスの インスタンス。 「スポーツカー」は車を 継承 した子クラス。 「加速する」を「ターボ加速する」に オーバーライド できる — これがポリモーフィズム。
OOP の理解には、 (1) クラス継承の木構造、 (2) インスタンスの集約とクラスタリング、 (3) クラス比較によるポリモーフィズム判定 の 3 点を同時に把握する必要があります。 ここでは html/glossary/figures/ にある既存図を借りて、 OOP 設計のキャンバスを描き出します。
Animal)、 内部ノードが中間クラス (例: Mammal, Bird)、 リーフが具体クラス (例: Dog, Cat) に対応する。 SSDSE-B-2026 の都道府県データを扱うクラス階層も同様で、 Region → KantoRegion → Tokyo のような is-a 関係を木で表現できる。 継承の深さが 4 を超えると保守が困難になるサインで、 Composition (has-a) への切替を検討する。読み取るポイント:
読み取るポイント:
Clusterer という抽象クラスを継承した複数の具象クラスが、 fit() メソッドを各自の流儀で実装する OOP パターンそのもの。 戦略パターン (Strategy Pattern) の典型例で、 アルゴリズムの差し替えが容易になる。読み取るポイント:
この 3 枚を順に追うと、 「継承階層の設計 → クラス抽出 → ポリモーフィズム活用」 という OOP 設計の三本柱が体系化される。 関連用語 NumPy / API / scikit-learn へ進むと、 各図の理論背景を深掘りできる。
1分で答えられる確認問題。 まず自分で答えを書き出してから解答を開きましょう。 SSDSE-B-2026 を扱う場面に当てはめて考えると定着しやすい。
Prefecture クラスが人口・面積・出生数を private 属性として持ち、 アクセスはメソッド経由 (get_density() 等) に限定。 直接代入を禁止することで整合性 (面積 0 など) を守る。Prefecture を UrbanPrefecture / RuralPrefecture が継承し、 共通属性は親クラスで管理。 重複コードを削減。summary() を子クラスごとに上書き。 呼び出し側は型を意識せず一括処理可能。AdministrativeUnit を抽象基底にして「人口」「面積」を必須メソッドとして強制。 仕様だけ決めて実装は子に委ねる。pd.DataFrame + 関数) と OOP スタイル (Class + メソッド) のどちらを選ぶべきか、 SSDSE 分析の場面例 2 つで使い分けせよ。BaseModel 抽象クラスを定義して LinearModel/TreeModel を切替可能にすると保守性が高い。判断基準: 「状態を持つか」「同じ操作を複数オブジェクトに繰り返すか」。 yes なら OOP、 no なら関数型。
PopulationModel は「予測」のみを担当し、 CSV 読込は DataLoader、 可視化は PlotRenderer に分離する。 1 つのクラスが変わる理由は 1 つだけにする。PopulationModel を編集せず XGBPopulationModel(PopulationModel) を継承して新規追加。 既存コードを「閉じた」まま新機能を「開く」。結果: 新規モデル追加でも既存テストが壊れにくく、 SSDSE 年次更新時の再学習サイクルが安全になる。
OOP の4つの核 — クラス(設計図)・インスタンス(実体)・継承・多態性(ポリモーフィズム) — を、 実際に ブラウザ内で動く簡易クラス で体感します。 下のデモの描画やふるまいは、 このページの <script> 内に定義した本物の JavaScript クラス(class Shape / class Circle extends Shape …)が動かしています。 CDN も外部ライブラリも一切使わず、 オフラインで完結します。
「Cat クラス」という 1枚の設計図 から、 猫(インスタンス)を何匹でも生成できます。 設計図は 属性(データ) と メソッド(操作) を定義するだけ。 生成された各インスタンスは name / x / energy といった 状態を個別に 持ち、 1匹に feed() しても他の猫は太りません。 これが「クラスは共有、 状態は独立」という OOP の骨格です。
キャンバス上の猫を クリック/タップ すると選択(黄色い枠)。 選択中の猫だけを feed() して、 状態が独立している ことを確かめてください。
Animal(親)は name 属性と move()・speak() メソッドを持ちます。 Dog・Cat(子)は extends Animal で 親の資産を丸ごと継承 し、 さらに speak() を各自の鳴き声に 上書き(override)、 Dog は独自メソッド fetch() を 拡張 します。 下の親子図でボタンを押すと、 どのメソッドが「継承」「上書き」「新規」なのかが色分けで光ります。
draw()・area() で違うふるまいShape を継承した Circle(円)・Square(四角)・Triangle(三角)は、 すべて同じ名前のメソッド draw() と area() を持ちます。 呼ぶ側は種類を気にせず for (const s of shapes) s.draw(ctx) と 一律に呼ぶだけ。 中身は各クラスが自分の流儀で実装しているので、 描かれる形も面積の公式も自動で切り替わります。 これが多態性(ポリモーフィズム)— 「同じ命令、 違う結果」です。
追加した図形は配列 shapes[] に入り、 1本の同じループ で draw() されます。 下段に各図形が自分で計算した area() が表示されます。
体感のまとめ: ①で「設計図と実体は別物・状態は独立」、 ②で「継承は資産の再利用+上書き+拡張」、 ③で「同じインターフェースに違う実装を差せる(多態)」を掴めたはず。 コード側で fit() / predict() を持つ scikit-learn の推定器も、 まさにこの③の多態性で「どのモデルでも同じ呼び方」を実現しています。 内部でデータ配列を扱う NumPy や、 外部に操作を公開する API の設計思想も、 この体感が土台になります。
🍰 まずはやさしく
状態と振る舞いをまとめた考え方です。
プログラムの構造をきれいにするために使います。
買い物リストと計算処理をセットにする例です。
この章では言葉の意味を詳しく定義します。
OOP は「データ(属性)と、 それを操作する処理(メソッド)を オブジェクト という 1 つの単位に束ねる」設計パラダイムである。 特定の数式で定義される計算手法ではなく、 プログラムをどう構造化するかという考え方を指す点が、 統計量や機械学習アルゴリズムのような他の用語と大きく異なる。
あえて最小限の形式で表すなら、 オブジェクトは「状態(属性の集まり)」と「振る舞い(メソッドの集まり)」の組と見なせる:
$$\text{オブジェクト} = (\text{状態},\ \text{振る舞い})$$
ここで状態はそのオブジェクトが保持するデータ、 振る舞いはそのデータに対して定義された操作を指す。 同じクラスから作られたインスタンスは、 状態は個別に持つが振る舞い(メソッド)を共有する。 これ以上の数式化は不要で、 OOP の本質は「何をひとまとまりの単位として設計するか」という設計上の判断にある。
animal.cry()」が犬なら「ワン」、 猫なら「ニャー」。オブジェクト指向プログラミング(OOP)は、 データ(属性)とそれに対する操作(メソッド)を「オブジェクト」に統合する設計パラダイム。 SSDSE-B-2026 のような表データを扱うとき、 Pandas DataFrame そのものがオブジェクトで、 .head()、 .groupby()、 .merge() がメソッドです。 OOP の理解は Python データ分析の中核。
OOP の 4 本柱は、 いずれも「オブジェクト=状態(属性)+ 振る舞い(メソッド)」という 1 単位を軸に整理できる。 同じクラスから作られたインスタンスは、 状態(属性値)は個別に持つが、 振る舞い(メソッド)はクラスで共有する — この「まとめ方」の工夫こそが OOP の要点であり、 数式で定義されるものではない。
| 柱 | 意味 | SSDSE-B-2026 での例 |
|---|---|---|
| カプセル化 | 内部状態を隠蔽し、 公開メソッド経由で操作 | DataFrame の _data 直接触らず df.loc[] で操作 |
| 継承 | 親クラスの属性・メソッドを子が引き継ぐ | class PrefectureFrame(pd.DataFrame) |
| ポリモーフィズム | 同じメソッド名で型に応じた振る舞い | .sum() が Series でも DataFrame でも動く |
| 抽象化 | 本質だけ取り出し詳細を隠す | df.plot() は背後の matplotlib を意識せず描画 |
pd.DataFrame の派生は元の API を壊さない。open('file.csv')」より「data_source.load()」。class Prefecture:
def __init__(self, name, pop): self.name, self.pop = name, pop
def density(self, area): return self.pop / area__init__:コンストラクタ。 self:インスタンス自身。 メソッドは第 1 引数に self。オブジェクト指向プログラミング を含むデータ分析は、 分野によって使われ方が違います。 下の表は分野ごとの代表的な用途で、 オブジェクト指向プログラミング だけの用途一覧ではありません。 自分の分野の行を見て、 どんな問いにデータを使うのかを掴んでください。
| 業界 | 事例 | OOP の役割 | SSDSE-B-2026 との対比 |
|---|---|---|---|
| Web 開発 | Django / Flask の Model クラス | DB テーブル=クラス、 行=インスタンス | class PrefectureStats(Model) で SSDSE 行を表現 |
| ゲーム開発 | Unity の MonoBehaviour 継承 | キャラクター・敵・アイテムをクラス階層化 | 都道府県をキャラクター視覚化のメタファ |
| 機械学習 | scikit-learn の fit/predict インタフェース | すべてのモデルが共通メソッド | LinearRegression().fit(X, y).predict(X) |
| 金融 | 取引システムの Order / Trade / Position クラス | 状態と振る舞いの分離 | SSDSE-B でポートフォリオ最適化練習 |
| 製造業 | センサーデータの DeviceState クラス | IoT デバイスの状態管理 | 都道府県を「センサー」と見立てた状態管理 |
| データ分析 | pandas DataFrame / Series / Index | OOP で表データを直感的に扱う | SSDSE-B-2026 解析は pandas OOP の典型 |
| パラダイム | 核となる概念 | 代表言語 | SSDSE-B での例 | 強み | 弱み |
|---|---|---|---|---|---|
| OOP | クラス、 オブジェクト、 継承 | Java, C++, Python | PrefectureRecord クラス | 大規模設計に強い | 過度な階層で複雑化 |
| 関数型 | 純粋関数、 不変、 高階関数 | Haskell, Lisp, Scala | map(birth_rate, prefectures) | 並列・テスト容易 | 状態管理に工夫必要 |
| 手続き型 | 命令の順次実行 | C, Fortran, COBOL | for ループで集計 | シンプル・高速 | 大規模で破綻 |
| 論理型 | 事実とルールの宣言 | Prolog | 「人口 > 1M の県は…」 | 探索問題に強い | 性能予測難 |
| データ指向 | データ構造中心 | Clojure, SQL | SQL クエリで集計 | 不変・並列 | 振る舞い分散 |
| イベント駆動 | イベントとハンドラ | JavaScript, Erlang | UI 更新コールバック | 反応性高 | 制御フロー追跡難 |
class Analyzer 内に読み込み・集計・描画・保存全部」にすると保守不能。 SRP に分割。def method(arg): と書くと「missing self」エラー。 メソッドの第 1 引数は必ず self。def __init__(self, lst=[]): は全インスタンスで lst を共有してしまう。 lst=None にして内部で lst or []。Prefecture クラスのインスタンスに変換せよ。
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd class Prefecture: def __init__(self, name, pop, births): self.name, self.pop, self.births = name, pop, births def birth_rate(self): return self.births / self.pop * 1000 df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) # 日本語の列名で読む(2 行目を見出しにする) d = df[df['年度']==2023] prefs = [Prefecture(r['都道府県'], r['総人口'], r['出生数']) for _, r in d.iterrows()] print(f"{prefs[0].name}: 人口{prefs[0].pop:,}, 出生率{prefs[0].birth_rate():.2f}‰") # 北海道: 人口5,092,000, 出生率4.80‰ |
1 2 3 4 5 6 7 8 | class BigCity(Prefecture): THRESHOLD = 5_000_000 def is_megacity(self): return self.pop > self.THRESHOLD big = BigCity('東京都', 14086000, 86348) print(big.is_megacity()) # True print(big.birth_rate()) # 継承で利用可 |
1 2 3 4 5 | class Prefecture: def __init__(self, name, pop): self.name, self.pop = name, pop def __str__(self): return f"<Prefecture {self.name}: {self.pop:,}人>" p = Prefecture('東京都', 14086000) print(p) # <Prefecture 東京都: 14,086,000人> |
from_csv_row を使って Prefecture を生成せよ。
1 2 3 4 5 6 7 8 9 | class Prefecture: def __init__(self, name, pop, births): self.name, self.pop, self.births = name, pop, births @classmethod def from_csv_row(cls, row): return cls(row['都道府県'], row['総人口'], row['出生数']) import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) # 日本語の列名で読む(2 行目を見出しにする) p = Prefecture.from_csv_row(df.iloc[0]) print(p.name, p.pop) |
population() メソッドで扱え。
class Single:
def __init__(self, p): self.p = p
def population(self): return self.p.pop
class Multi:
def __init__(self, ps): self.ps = ps
def population(self): return sum(p.pop for p in self.ps)
# 両方とも .population() で呼べる = ポリモーフィズム
abc.ABC。type。@property, @classmethod, @staticmethod。@dataclass で __init__/__repr__/__eq__ を自動生成。 Python 3.7+。🎯 やること:47 都道府県を「北海道・東北・関東・中部・近畿・中国・四国・九州」の 8 ブロックに分け、 各ブロックの人口・出生数を Composite パターンで集計。 各レベルで同じ .population() インタフェースが動く(ポリモーフィズム)。
📥 入力:SSDSE-B-2026 の 2023 年データ 47 行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import pandas as pd from abc import ABC, abstractmethod from dataclasses import dataclass, field # 抽象基底 class Region(ABC): @abstractmethod def population(self) -> int: ... @abstractmethod def name(self) -> str: ... # 葉ノード:個別都道府県 @dataclass class Prefecture(Region): _name: str _pop: int def population(self) -> int: return self._pop def name(self) -> str: return self._name # 複合ノード:地方ブロック @dataclass class Block(Region): _name: str members: list = field(default_factory=list) def population(self) -> int: return sum(m.population() for m in self.members) def name(self) -> str: return self._name # 地方区分 BLOCKS = { '北海道': ['北海道'], '東北': ['青森県','岩手県','宮城県','秋田県','山形県','福島県'], '関東': ['茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県'], '中部': ['新潟県','富山県','石川県','福井県','山梨県','長野県','岐阜県','静岡県','愛知県'], '近畿': ['三重県','滋賀県','京都府','大阪府','兵庫県','奈良県','和歌山県'], '中国': ['鳥取県','島根県','岡山県','広島県','山口県'], '四国': ['徳島県','香川県','愛媛県','高知県'], '九州': ['福岡県','佐賀県','長崎県','熊本県','大分県','宮崎県','鹿児島県','沖縄県'], } # データ取得 df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度']==2023] pop_map = dict(zip(d['都道府県'], d['総人口'])) # Composite 構築 blocks = [ Block(b, [Prefecture(p, pop_map[p]) for p in prefs]) for b, prefs in BLOCKS.items() ] nation = Block('日本', blocks) # レポート出力(ポリモーフィズム) for b in blocks: print(f"{b.name():4s}: {b.population():>11,} 人 ({len(b.members)} 県)") print('-' * 40) print(f"{nation.name():4s}: {nation.population():>11,} 人") |
📤 実行結果:
💬 結果の読み方:Composite パターンで「個別県」と「ブロック」と「全国」を同じインタフェース(population())で扱える。 関東が 4,350 万人(全国の 35%)と圧倒的、 東京一極集中を構造的に示す。 OOP の真価は「データ階層を自然に反映できる」こと。
機械学習推定器の典型的 OOP 設計:
Estimator が fit() と predict() を約束LinearRegression, RandomForest, SVM がそれを 継承model.fit(X, y); model.predict(X_new) と 同じインタフェースで呼べる合成データで 3 クラスのインスタンス数と継承階層を集計する。
| クラス | 親 | メソッド数 |
|---|---|---|
| Animal | — | 3 |
| Dog | Animal | 5 (3 継承 + 2 追加) |
| Puppy | Dog | 6 (5 継承 + 1 追加) |
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Animal: def eat(self): pass def sleep(self): pass def breathe(self): pass class Dog(Animal): def bark(self): pass def fetch(self): pass class Puppy(Dog): def play(self): pass print(f"Puppy メソッド (継承含む): {len([m for m in dir(Puppy) if not m.startswith('_')])}") |
💬 手計算 (Step 2) 6 と Python 出力が完全一致。
最小再現コード。 SSDSE-B のような実データを前提に、 4〜8 行で動く例です:
1 2 3 4 5 6 7 8 9 10 | class Estimator: def fit(self, X, y): raise NotImplementedError def predict(self, X): raise NotImplementedError class MeanRegressor(Estimator): def fit(self, X, y): self.mu = sum(y)/len(y); return self def predict(self, X): return [self.mu]*len(X) m = MeanRegressor().fit([[1],[2],[3]], [10,20,30]) print(m.predict([[1],[2]])) # [20.0, 20.0] |
補足:ライブラリのバージョンや前処理状態によって出力は変わります。 自分の環境で動かすときは pip list でバージョンを確認し、 入力 CSV のパス・列名を実態に合わせてください。
🎯 このコードでやること:CSV を読み込み、 47 都道府県(2023 年)を Prefecture クラスのインスタンスリストに変換。 各インスタンスは name / population / birth_count 属性と birth_rate() メソッドを持つ。
📥 入力データ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import pandas as pd from dataclasses import dataclass @dataclass class Prefecture: name: str population: int births: int tfr: float # 合計特殊出生率 def birth_rate(self) -> float: return self.births / self.population * 1000 def __repr__(self) -> str: return f"<{self.name}: 人口{self.population:,} 出生率{self.birth_rate():.2f}‰>" df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度']==2023] prefs = [ Prefecture(r['都道府県'], r['総人口'], r['出生数'], r['合計特殊出生率']) for _, r in d.iterrows() ] for p in prefs[:3]: print(p) print(f"最大: {max(prefs, key=lambda x: x.population).name}") |
📤 実行結果:
💬 結果の読み方:47 都道府県が Prefecture オブジェクトとして表現され、 各オブジェクトに .name, .population, .birth_rate() が紐づく。 dataclass で __init__ 自動生成、 __repr__ をカスタムして読みやすく。 人口最大は東京都 14,086,000 人。 OOP の利点:「データ+振る舞い」を 1 箇所に統合。
🎯 このコードでやること:500 万人超を「大都市圏(BigCity)」として継承クラスで表現、 ポリモーフィズムで統一処理。
📥 入力データ:上記 prefs 47 件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import pandas as pd from dataclasses import dataclass, field @dataclass class Prefecture: name: str population: int def category(self) -> str: return '一般県' @dataclass class BigCity(Prefecture): THRESHOLD = 5_000_000 def category(self) -> str: return '大都市圏' if self.population > self.THRESHOLD else '一般県' df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度']==2023] prefs = [BigCity(r['都道府県'], r['総人口']) for _, r in d.iterrows()] big = [p for p in prefs if p.category() == '大都市圏'] print(f"大都市圏: {len(big)} 県") for p in big: print(f" {p.name} {p.population:,}") |
📤 実行結果:
💬 結果の読み方:500 万人超の都道府県は 9 件(北海道・関東 4 都県・愛知・大阪・兵庫・福岡)。 category() をオーバーライドすることで、 Prefecture と BigCity を同じ for ループで処理できる(ポリモーフィズム)。
🎯 このコードでやること:Prefecture に @classmethod from_csv() でファクトリ、 @property で計算属性、 __enter__/__exit__ でリソース管理を実装。
📥 入力データ:CSV ファイルパス。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import pandas as pd class PrefectureDataset: def __init__(self, df): self.df = df @classmethod def from_csv(cls, path, year=2023): df = pd.read_csv(path, encoding='cp932', skiprows=1) return cls(df[df['年度']==year].copy()) @property def n(self): return len(self.df) @property def total_population(self): return self.df['総人口'].sum() def __enter__(self): print(f"データセット開始: {self.n} 県") return self def __exit__(self, *a): print("データセット終了") with PrefectureDataset.from_csv('data/raw/SSDSE-B-2026.csv', 2023) as ds: print(f"全国人口合計: {ds.total_population:,}人") print(f"平均人口: {ds.total_population / ds.n:,.0f}人") |
📤 実行結果:
💬 結果の読み方:3 つの OOP 機能を一度に活用。 @classmethod で「CSV からインスタンス化」する別コンストラクタ、 @property で計算結果を属性アクセスで取得、 with 文でリソース取得・解放を自動化。 全国合計人口 1.26 億人、 平均約 269 万人/県。
Pref がクラス、 Pref('東京', 14086000) がインスタンス。this 相当。__init__/__repr__/__eq__ を自動生成。 ボイラープレート削減。 Python 3.7+。_x(慣習)または __x(name mangling で _ClassName__x)。 ただし完全な隠蔽は不可。prefecture.birth_rate は属性アクセスだが内部で除算。type で十分。__dict__ を持たない。 メモリ 40〜50% 削減、 属性アクセス高速化。 大量インスタンス時に有効。async def 可能。 __aenter__/__aexit__ で async context manager。| 年 | 言語 / 出来事 | OOP への貢献 |
|---|---|---|
| 1962 | Simula I(Dahl, Nygaard) | シミュレーション用クラス概念 |
| 1967 | Simula 67 | クラス、 継承、 仮想関数の初導入 |
| 1972 | Smalltalk-72(Alan Kay) | 「Object-Oriented」命名、 メッセージパッシング |
| 1980 | Smalltalk-80 | 純粋 OOP の完成形 |
| 1983 | C++(Stroustrup) | C 拡張で OOP を産業界へ |
| 1991 | Python(Van Rossum) | 動的型 OOP の代表 |
| 1993 | Ruby(まつもとゆきひろ) | 純粋 OOP + スクリプト言語 |
| 1994 | GoF「Design Patterns」 | 23 種の標準パターン体系化 |
| 1995 | Java(Gosling, Sun) | 「Write once, run anywhere」、 OOP 標準化 |
| 2000 | C# (Microsoft) | Java 影響下の OOP 言語 |
| 2002 | SOLID(Robert Martin) | 5 原則の体系化 |
| 2010 年代 | Scala, Kotlin, Swift, Rust | OOP + 関数型ハイブリッド |
| 2020 年代 | Python 3.10+, dataclass, Protocol | 型ヒント + OOP の現代化 |
インスタンス属性を固定し、 メモリ消費を 40〜50% 削減。 1 万件以上のインスタンスを扱う SSDSE-B-2026 拡張(市区町村レベル)では効く。
class Prefecture:
__slots__ = ('name', 'population', 'births')
def __init__(self, name, pop, births):
self.name, self.population, self.births = name, pop, births
サブクラス作成時に自動で呼ばれるフック。 クラス階層の検証や登録に使う。
@property、 @cached_property の背後にある仕組み。 __get__/__set__/__delete__ で属性アクセスを完全制御。
1 2 3 4 5 6 7 8 | from abc import ABC, abstractmethod class DataSource(ABC): @abstractmethod def load(self) -> "pd.DataFrame": ... class SSDSESource(DataSource): def load(self): import pandas as pd return pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) |
1 2 3 4 | from typing import Protocol class Sortable(Protocol): def __lt__(self, other) -> bool: ... # クラスが __lt__ を実装すれば自動で Sortable とみなされる(duck typing の型版) |
悪い例:SSDSEAnalyzer 1 クラスで読込・前処理・可視化・保存。 良い例:SSDSELoader + SSDSEPreprocessor + SSDSEPlotter + SSDSEExporter に分離。
新しいデータソース(SSDSE-A、 SSDSE-C)を追加する際、 既存コード修正なし。 抽象基底 SSDSESource に対し SSDSEBSource, SSDSEASource を派生。
SSDSESource 型変数に SSDSEBSource インスタンスを代入しても、 .load() 呼出しが期待通り動作。 違反例:「子クラスが特定条件で例外を投げる」のは LSP 違反。
Readable, Writable, Plottable を分けて、 必要なものだけ実装。
具象 SSDSEBSource に依存せず、 抽象 DataSource に依存。 テストでモックに差し替え可。
| パターン | SSDSE-B-2026 での適用例 |
|---|---|
| Strategy | 集計方法(平均、 中央値、 トリム平均)を切替可能なオブジェクトに |
| Factory Method | 都道府県名から適切なクラス(Metropolis, BigCity, NormalPref)を生成 |
| Singleton | SSDSE-B-2026 のキャッシュロード(メモリ重複防止) |
| Observer | データ更新時に複数のグラフを自動再描画 |
| Decorator | DataFrame に「ライセンス情報」を追加する装飾 |
| Composite | 都道府県 → 地方ブロック → 全国 の階層集計 |
| Iterator | SSDSE-B の年度ごとイテレーション |
| Adapter | SSDSE-B-2026 (CP932) を Pandas (UTF-8) に変換 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | from dataclasses import dataclass, field from abc import ABC, abstractmethod from typing import Iterator import pandas as pd # 抽象データソース class DataSource(ABC): @abstractmethod def load(self) -> pd.DataFrame: ... # SSDSE-B-2026 実装 @dataclass class SSDSEBSource(DataSource): path: str = 'data/raw/SSDSE-B-2026.csv' encoding: str = 'cp932' skiprows: int = 1 def load(self) -> pd.DataFrame: return pd.read_csv(self.path, encoding=self.encoding, skiprows=self.skiprows) # 都道府県エンティティ @dataclass class Prefecture: code: str name: str year: int population: int births: int @property def birth_rate(self) -> float: return self.births / self.population * 1000 def __repr__(self) -> str: return f"<{self.name}({self.year}) 人口{self.population:,} 出生率{self.birth_rate:.2f}‰>" # データセットコレクション class PrefectureDataset: def __init__(self, source: DataSource): self.source = source self._df = None @property def df(self) -> pd.DataFrame: if self._df is None: self._df = self.source.load() return self._df def __iter__(self) -> Iterator[Prefecture]: for _, r in self.df.iterrows(): yield Prefecture(r['地域コード'], r['都道府県'], r['年度'], r['総人口'], r['出生数']) def __len__(self) -> int: return len(self.df) # 使用例 ds = PrefectureDataset(SSDSEBSource()) for p in list(ds)[:3]: print(p) print(f"全 {len(ds)} レコード") |
SSDSE-B-2026 をドメインモデルとして捉える:
Prefecture(一意 ID = 地域コード + 年度)BirthRate, PopulationDensity(不変、 ID なし)RegionalBlock(北海道・東北など、 複数県を束ねる)PrefectureRepository.find_by_year(2023)PopulationTrendService(時系列分析ロジック)p.population で済む__add__, __len__, __iter__ で組込み関数と統合with open(...) as f: のリソース管理| 用途 | 推奨 | SSDSE-B-2026 での例 |
|---|---|---|
| 純粋データ保持 | dataclass / NamedTuple | Prefecture(name, pop, births) |
| 振る舞い豊富 | 通常クラス | PrefectureAnalyzer |
| イミュータブル | frozen dataclass | BirthRate(値オブジェクト) |
| 純粋計算 | 関数 | compute_birth_rate(pop, births) |
| パイプライン | 関数合成 | map(transform) | filter | reduce |
| 状態遷移 | OOP | 分析ステップを保持する Analyzer |
OOP の落とし穴は「設計を抽象化しすぎる」「継承の階層が深くてデバッグできない」「データ分析タスクで OOP を強引に使う」の 3 系統に集約される。 SSDSE 分析を例にすると、 Prefecture クラスを 5 階層継承で表現するより、 pd.DataFrame に集計関数を渡す方が読みやすい — OOP は状態を持つドメイン (GUI・ゲーム・シミュレータ) で真価を発揮し、 ステートレスな ETL では関数型の方が適合する。
_attr や __attr は規約止まり。 アクセスできてしまうが、 公開していないことを意味する。特に Python では _attr のアンダースコア接頭辞が「公開しない」という規約に過ぎず実際にはアクセス可能な点、 __attr の name mangling もリフレクションで突破可能な点を理解しておくと、 過度な隠蔽設計の罠を避けられる。
オブジェクト指向プログラミング (OOP) を中心に、 4 本柱 (カプセル化・継承・多態性・抽象化)、 対照パラダイム (関数型・手続き型)、 データサイエンス応用 (scikit-learn Estimator API・PyTorch nn.Module)、 設計原則 (SOLID・GoF パターン) を配置した SVG マップ。
本セクションではオブジェクト指向の カプセル化・継承・多態性 (ポリモーフィズム) を補足する。 データ分析でも `sklearn` の `BaseEstimator` を継承して fit/predict を実装したり、 pandas の DataFrame を拡張する自作クラスを作ったり — OOP は「共通インターフェースで拡張する」設計力として実務に直結する。
「オブジェクト指向プログラミング」は単独で完結せず、 前後の手法と組み合わさって価値が発揮される。 入力データの準備 (上流)・同目的の代替手法との比較 (並列)・結果の活用 (下流) という 3 軸で隣接領域を整理する。
この上流・並列・下流の対応を地図化することで、 「オブジェクト指向プログラミング」を中核に据えた分析パイプライン (データ準備 → 手法選択 → 結果の検証と展開) の全体像が見えてくる。
「オブジェクト指向プログラミング」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。
分析スクリプトのように 1 回書いて捨てるコードでは、 クラス化は手間が増えるだけのことが多い。 同じ処理を何度も呼ぶ、 状態を持つ、 差し替えたい — このどれかが出てから導入する。
手続き型で書くと、 コードは「動詞(関数)」が主役になります。 calc_density(pop, area) のように、 データはただ関数へ渡される材料にすぎません。 一方 OOP では 「主語(オブジェクト)」が先に立ち、 tokyo.density() のように「そのオブジェクト自身が振る舞う」形で書きます。 これが「データ(属性)と操作(メソッド)を一体にまとめる」という OOP の核心です。
// 手続き型:データと処理がバラバラ
d = calc_density(pop, area) # 誰の pop/area か関数の外で管理
# OOP:データと処理が「東京都」という 1 単位に凝集
d = tokyo.density() # pop も area も tokyo が抱えているクラスは「たい焼きの型」、 インスタンスは「焼き上がったたい焼き」という比喩が定番です。 型(クラス)は 1 枚で、 そこから中身(あんこ・カスタード)の違う実体(インスタンス)を何個でも量産できます。 このページ上部の 🎮 インタラクティブ体感 で「new Cat() を押すたびに独立した状態を持つ猫が増える」のは、 まさにこの「型は共有・中身は個別」を目で見る仕掛けです。 SSDSE-B-2026 なら Prefecture が型、 Prefecture('東京都', 14086000, ...) が 47 個の実体にあたります。
| 柱 | 一言で | ご利益(なぜ嬉しいか) | 日常のたとえ |
|---|---|---|---|
| カプセル化 | 中身を隠し、 窓口だけ公開 | 内部を変えても外部コードが壊れない(変更の局所化) | ATM:現金の在庫管理は隠され、 「引き出す」ボタンだけ触れる |
| 継承 | 共通部分を親にまとめ、 差分だけ子で書く | 重複コードの削減、 共通仕様の一元管理 | 「乗り物」の共通機能を「車」「バイク」が受け継ぐ |
| 多態性 | 同じ命令、 違う実装 | 呼ぶ側が型を意識せず一律に扱える(拡張が楽) | 「再生ボタン」は音楽でも動画でも同じ操作で違う結果 |
ただし「継承」は三本柱の中で最も誤用されやすい柱です。 「共通部分があるから継承しよう」は早計で、 多くの場合は次節で述べる合成(コンポジション)の方が安全です。 三本柱は「必ず全部使う」ものではなく、 カプセル化と多態性が主役、 継承は控えめにが現代的な感覚です。
OOP の失敗は、 文法ミスよりも設計判断の誤りで起きます。 とくに「継承を使いたくなる衝動」と「なんでもクラスにしたくなる衝動」の 2 つが、 コードを静かに腐らせます。 ここでは既存の よくある落とし穴 を踏まえ、 「なぜダメか」と「代わりに何をするか」をセットで深掘りします。
A → B → C → D と 4 階層以上に伸びると、 親 A の些細な変更が全子孫に波及し、 どこが壊れるか読めなくなります(脆い基底クラス問題 / fragile base class)。 目安:継承の深さは 2〜3 まで。 このページ上部の 図A(決定木=継承階層) で「深さ 4 以上は危険信号」と述べたのと同じ話です。class Analyzer が DataLoader を継承するのではなく、 self.loader = DataLoader() として保持し、 必要な操作だけ self.loader.load() と委譲する。 差し替え(別ローダーの注入)が容易になり、 密結合が切れます。 これは後述の依存性逆転(DIP)とも直結します。a.b.c.do_something() のように他オブジェクトの内部を掘って操作すると(デメテルの法則違反)、 相手の構造変更で自分も壊れます。 窓口メソッド経由でのみやり取りし、 内部構造を外に漏らさない(カプセル化の徹底)。def __init__(self, items=[]) の可変デフォルト引数は全インスタンスでリストを共有する古典的な罠(items=None にして内部で items or [])。 対策は不変(immutable)設計:値オブジェクトは @dataclass(frozen=True) にする、 状態を持つオブジェクトは書き換え口を絞る。ここまでの三本柱と落とし穴を土台に、 実務で効く 4 テーマへ橋渡しします。 いずれも既存セクション(SOLID の一覧・デザインパターン表)を前提に、 「なぜそれが効くか」の角度で補足します。
SOLID は「継承を安全に使うための制約集」とも読めます。 とくに L(リスコフ置換)は「その継承、 本当に is-a か?」の検問所。 ここを通れないなら、 前節の合成に切り替えるサインです。
| パターン | 解く問題 | データ分析での例 |
|---|---|---|
| Strategy | アルゴリズムを差し替えたい | 集計方法(平均/中央値/トリム平均)を切替。 scikit-learn の推定器選択も実質これ |
| Factory | 生成ロジックを一箇所に集約 | 県名から Metropolis / BigCity / NormalPref を振り分け生成 |
| Composite | 木構造を「部分も全体も同じ扱い」 | 県 → 地方ブロック → 全国 の階層集計(実習セクションで実装済み) |
| Adapter | 互換のない I/F をつなぐ | CP932 の SSDSE を UTF-8 前提の処理に橋渡し |
23 個の GoF パターンを暗記する必要はありません。 「差し替えたい=Strategy」「生成を隠したい=Factory」「木=Composite」「変換=Adapter」の 4 語感覚があれば、 分析コードの設計はほぼ回ります。
ライブラリを「使える」だけの人と「読める」人の差は、 この規約を知っているかどうかです。 scikit-learn は全推定器が同じ 3 メソッドを約束しています:
fit(X, y) … データから状態(学習済みパラメータ)を内部に持つ。 まさに OOP の「状態+振る舞い」transform(X) … 学習済み状態を使ってデータを変換(前処理・特徴量エンジニアリング)predict(X) … 学習済み状態で予測「fit で状態を仕込み、 transform/predict で使う」という統一 I/F があるおかげで、 StandardScaler も LinearRegression も RandomForest も Pipeline に一列に並べて差し替え可能になります。 これは前節の Strategy パターン+多態性そのもの。 pandas の DataFrame も、 データ(値)と操作(.groupby()/.merge())を束ねた巨大なオブジェクトで、 DataFrame を触ること自体が OOP の実践です。
fit_transform() は「学習」と「変換」を続けて呼ぶ糖衣構文。 ただしテストデータには transform() のみ(再 fit は情報漏洩=リーク)。 この規約違反が分析コンペでの典型的な減点ポイントです。データクラス(@dataclass)は「データ保持が主・振る舞いは少し」という中間解。 __init__/__repr__/__eq__ を自動生成し、 ボイラープレートを消します。 frozen=True にすれば不変となり、 前節の「可変状態の副作用」を根本から断てます(値オブジェクトに最適)。
| やりたいこと | 推奨 | 理由 |
|---|---|---|
| 純粋にデータを束ねる | @dataclass / NamedTuple | 軽量・型ヒント・自動 __repr__ |
| 絶対に書き換えない値 | @dataclass(frozen=True) | 副作用を構造的に排除 |
| 状態を持ち回り、 操作が豊富 | 通常クラス(OOP) | 状態+振る舞いの凝集が効く |
| 一発の計算・変換 | 関数(関数型) | クラスは過剰。 map/filter や pandas チェーンが素直 |
| 前処理パイプライン | 関数合成 + 一部 OOP | ステートレスな関数を並べ、 状態を持つ学習器だけクラスに |
結論:OOP・関数型・データクラスは対立ではなく道具箱。 現代 Python の定石は「データは frozen dataclass、 変換は純粋関数、 状態を持つ学習器・接続だけをクラス」というハイブリッド。 「クラスを何個書いたか」ではなく「状態の在りかを最小に保てたか」で設計の良し悪しが決まります。
さらに深掘りするなら:scikit-learn(Estimator 設計)/pandas・DataFrame(オブジェクトとしての表データ)/特徴量エンジニアリング(transform の実務)/MLOps(学習器の管理・再学習)/API(外部への操作公開)へ。 なお本ページで「プログラミング」「Python」「関数」「クラス」「データ構造」の各基礎に言及していますが、 これらは独立ページが未整備のためテキストのみの参照です。
_attr や __attr は規約止まり。 アクセスできてしまうが、 公開していないことを意味する。class Analyzer 内に読み込み・集計・描画・保存全部」にすると保守不能。 SRP に分割。def method(arg): と書くと「missing self」エラー。 メソッドの第 1 引数は必ず self。def __init__(self, lst=[]): は全インスタンスで lst を共有してしまう。 lst=None にして内部で lst or []。SOLID 原則やデザインパターンの教科書的知識は重要だが、 過剰適用が最大の罠でもある — Python なら関数ベースの dataclass + duck typing で十分なケースが多く、 「クラス定義 5 個ある = OOP できている」とは限らない。 状態を持つドメイン (Worker, Connection, Session) と純粋関数で済むドメイン (前処理パイプライン) を見極めることが OOP 習得の核心。
オブジェクト指向プログラミング は「プログラミング」分野の中で発展してきた概念・手法です。 学術的には継続的な研究で精緻化され、 実務的にはツール・ライブラリの普及で誰でも使えるようになってきました。 用語の使い方・意味は時代と分野で少しずつ変わるため、 文脈に応じた解釈が大切です。 入門書だけでなく、 標準的な教科書(例:データサイエンス・統計学の定本)や信頼できるオンライン教材も併用すると、 ぶれない理解に近づけます。
「オブジェクト指向プログラミング」 はこのページで詳しく扱った概念です。 持ち帰ってほしい 3 つの要点:
sklearn の fit/predict が典型 OOP。さらに学ぶには、 関連用語 や 関連グループ教材 を参照してください。 各用語ページを縦断的に読むことで、 体系的な理解が育ちます。