概要(lenは「そのオブジェクトの“サイズ”を即座に返す」ための特別メソッド)
lenは、len(obj)が呼ばれたときに使われる“サイズ取得”の入口です。数や件数など「直感的に数えられるもの」を、非負の整数で素早く返します。初心者がまず押さえるべき核心は、返す値の意味と制約(非負のint)、速度(原則O(1))、boolとの関係(空ならFalse)、そして“重い計算を入れない”設計です。
基本の使い方(len()とlenの関係)
最小の実装と動作
class Cart:
def __init__(self):
self._items: list[tuple[str, int]] = []
def add(self, sku: str, qty: int):
self._items.append((sku, qty))
def __len__(self) -> int:
return len(self._items) # 件数(品目数)を返す
c = Cart()
print(len(c)) # 0
c.add("A001", 2)
print(len(c)) # 1
Python- ポイント: len(obj)は内部でobj.len()を呼びます。返す値は「サイズ」を表す非負の整数にします。
非負のintであること(制約)
class Bad:
def __len__(self) -> int:
return -1 # 悪い実装:len()は非負を要求
Python- 必須条件: lenは非負のintを返すべきです。負を返すと例外になり、float等の型も不可です。
設計の要点(何を“サイズ”とみなすか・速度・意味の一貫性)
“サイズ”の意味を明確に決める
class TextBuffer:
def __init__(self):
self._text = ""
def write(self, s: str):
self._text += s
def __len__(self) -> int:
return len(self._text) # 文字数(バイト数ではない)
Python- ラベル: 文字数なのか、行数なのか、アイテム数なのかを一貫して定義すること。利用者が迷わない指標にします。
原則はO(1)で返す(重い計算をしない)
class TreeBad:
def __init__(self, root):
self.root = root
def __len__(self):
# NG:毎回ツリー全走査は重い
return self._count_nodes(self.root)
Python- 指針: サイズは保持・キャッシュしておき、len()は即座に返すのが基本。毎回の全走査やI/Oは避けます。
一貫性(メソッド・プロパティと整合)
- 狙い: list-likeならlenは要素数、文字列なら文字数、集合なら要素数。API全体で直感どおりに揃えると学習コストが下がります。
重要ポイントの深掘り(boolとの関係・コレクション設計・抽象基底)
空かどうかの判定に使われる(boolとの関係)
class Bag:
def __init__(self):
self._items = []
def __len__(self) -> int:
return len(self._items)
b = Bag()
if b: # bool(b) は len(b) == 0 かどうかで決まる(0ならFalse、>0ならTrue)
print("not empty")
Python- 重要: boolを実装していなくても、lenが0ならFalse、0より大ならTrueと評価されます。空判定の基礎になります。
コレクションの型としてふるまう(list風・set風)
class Inventory:
def __init__(self):
self._set: set[str] = set()
def add(self, sku: str):
self._set.add(sku)
def __len__(self) -> int:
return len(self._set)
Python- 狙い: コレクションを表す型では、lenは「含まれる要素数」にするのが定石です。
Sizedの契約(「長さがある」ことを示す設計)
- ヒント: collections.abc.Sizedを満たす(lenを持つ)と、「長さを持つもの」として多くの汎用コードから扱いやすくなります。
実務例(Web / APIでのモデル・キュー・キャッシュ)
モデルで件数を返す(コメント・添付など)
class Post:
def __init__(self, title: str):
self.title = title
self._comments: list[str] = []
def add_comment(self, text: str):
self._comments.append(text)
def __len__(self) -> int:
return len(self._comments) # コメント数
Python- 効果: UIやバッジ表示、条件分岐(空なら表示しない)に自然に使えます。
キューやバッファの残量
class Queue:
def __init__(self):
self._q: list[str] = []
def push(self, x: str): self._q.append(x)
def pop(self) -> str: return self._q.pop(0)
def __len__(self) -> int: return len(self._q) # 残件数
Python- 効果: 制御ロジックで「満杯か空か」を即判定できます。
キャッシュの項目数
class Cache:
def __init__(self):
self._store: dict[str, str] = {}
def put(self, k: str, v: str): self._store[k] = v
def __len__(self) -> int: return len(self._store) # キャッシュ件数
Python- 効果: 監視・ログで「サイズが肥大していないか」を把握できます。
よくある落とし穴と回避(意味のブレ・遅さ・型違反)
“何を数えるか”が曖昧
- 問題: 文字数なのか行数なのか、品目数なのか数量合計なのかが曖昧だと誤用が起きます。
- 対策: 名称とドキュメントで明示し、必要なら別プロパティ(lines、total_qty)を分けます。
毎回の重い計算で遅いlen
- 問題: 全走査・外部I/Oをlenに入れると、printやログだけで重くなります。
- 対策: サイズは内部構造に合わせて更新・キャッシュ。lenはO(1)で返す。
非intや負の値を返す
- 問題: float・負数は不正。エラーになります。
- 対策: 常にintで非負。設計段階で不変条件として固定する。
例題(行指向テキストと商品合計を分けて正しく数える)
“行数”と“数量合計”を別々に設計
class TextLines:
def __init__(self):
self._lines: list[str] = []
def add(self, line: str):
self._lines.append(line)
def __len__(self) -> int:
return len(self._lines) # 行数
@property
def chars(self) -> int:
return sum(len(s) for s in self._lines) # 文字数(重いならキャッシュ)
class Cart:
def __init__(self):
self._items: list[tuple[str, int]] = [] # (SKU, qty)
self._count: int = 0 # 件数キャッシュ
self._total_qty: int = 0 # 数量合計キャッシュ
def add(self, sku: str, qty: int):
if qty <= 0: raise ValueError("数量は正の数")
self._items.append((sku, qty))
self._count += 1
self._total_qty += qty
def __len__(self) -> int:
return self._count # 品目数(O(1))
@property
def total_qty(self) -> int:
return self._total_qty # 数量合計(別指標)
Pythonこのように、「何を数えるか」を分けて定義し、lenは即返せる指標に固定すると、使いやすくて速いクラスになります。
まとめ(lenは“即わかるサイズ”を非負intで返す。重さと曖昧さを排除する)
lenはlen(obj)のための特別メソッドで、「そのオブジェクトのサイズ」を非負のintで、原則O(1)で返します。何を数えるかは一貫して明示し、空判定やUI/ログで活きる設計にする。重い計算やI/Oを入れず、必要な別指標はプロパティ等で分ける。これを徹底すれば、初心者でも「直感的で速く、壊れにくい」サイズ設計が自然に書けます。
