Python でオブジェクトを if 文や bool() で評価するとき、そのオブジェクトが「真」か「偽」かを決めるのが __bool__ と __len__ という特殊メソッドだ。これらをカスタマイズすることで、自作クラスのインスタンスがどのように真偽判定されるかを制御できる。
真偽値判定のデフォルト動作
Python のオブジェクトは、特に何も定義しなければ常に True と判定される。
class MyClass: pass obj = MyClass() print(bool(obj)) # True if obj: print("obj is truthy") # 出力される
これは「存在するオブジェクトは真」という直感に沿った動作だ。しかし、空のコンテナや無効な状態を表すオブジェクトでは False を返したいことがある。
__bool__ メソッド
__bool__ を定義すると、真偽値判定を直接カスタマイズできる。
class Result: def __init__(self, success, data=None): self.success = success self.data = data def __bool__(self): return self.success # 使用例 success_result = Result(True, {"id": 1}) failure_result = Result(False) print(bool(success_result)) # True print(bool(failure_result)) # False if success_result: print("Success!") # 出力される if not failure_result: print("Failed!") # 出力される
__bool__ は必ず True または False を返さなければならない。それ以外を返すと TypeError になる。
class BadBool: def __bool__(self): return 1 # int を返している obj = BadBool() bool(obj) # TypeError: __bool__ should return bool, returned int
__len__ メソッドによる暗黙の真偽値
__bool__ が定義されていない場合、Python は __len__ を探す。__len__ が 0 を返せば False、それ以外なら True と判定される。
class Playlist: def __init__(self, songs=None): self.songs = songs or [] def __len__(self): return len(self.songs) # __bool__ は定義していない # 使用例 empty_playlist = Playlist() full_playlist = Playlist(["song1", "song2"]) print(bool(empty_playlist)) # False(len が 0) print(bool(full_playlist)) # True(len が 2) if full_playlist: print(f"Playing {len(full_playlist)} songs")
これにより、コンテナ的なオブジェクトは「空なら False、要素があれば True」という自然な振る舞いを得られる。
__bool__ と __len__ の優先順位
両方定義されている場合、__bool__ が優先される。
class Container: def __init__(self, items): self.items = items self.active = True def __len__(self): print("__len__ called") return len(self.items) def __bool__(self): print("__bool__ called") return self.active c = Container([1, 2, 3]) c.active = False print(bool(c)) # 出力: # __bool__ called # False # __len__ は呼ばれない
これは意図的な設計だ。__bool__ を定義したなら、それがオブジェクトの真偽を決める責任を持つ。
実践的な使用例
いくつかの実践的なパターンを見てみよう。
# 例1: 検証結果クラス class ValidationResult: def __init__(self): self.errors = [] def add_error(self, message): self.errors.append(message) def __bool__(self): return len(self.errors) == 0 def __len__(self): return len(self.errors) result = ValidationResult() if result: print("Validation passed") result.add_error("Name is required") if not result: print(f"Validation failed with {len(result)} errors")
# 例2: Optional 風のラッパー class Maybe: def __init__(self, value=None): self._value = value self._has_value = value is not None def __bool__(self): return self._has_value @property def value(self): if not self._has_value: raise ValueError("No value present") return self._value some = Maybe(42) none = Maybe() if some: print(f"Value: {some.value}") # Value: 42 if not none: print("No value") # No value
# 例3: 接続状態を表すクラス class Connection: def __init__(self, host): self.host = host self.connected = False def connect(self): self.connected = True def disconnect(self): self.connected = False def __bool__(self): return self.connected conn = Connection("localhost") if not conn: print("Not connected, connecting...") conn.connect() if conn: print("Ready to use")
注意点:__len__ が負の値を返す場合
__len__ は非負の整数を返すことが期待される。負の値を返すと ValueError になる。
class BadLength: def __len__(self): return -1 obj = BadLength() len(obj) # ValueError: __len__() should return >= 0 bool(obj) # ValueError: __len__() should return >= 0
注意点:継承時の挙動
親クラスで __bool__ や __len__ を定義していると、子クラスはそれを継承する。必要に応じてオーバーライドする。
class BaseContainer: def __init__(self): self.items = [] def __len__(self): return len(self.items) class SpecialContainer(BaseContainer): def __init__(self): super().__init__() self.enabled = True # __bool__ をオーバーライドして独自の判定 def __bool__(self): return self.enabled and len(self.items) > 0 sc = SpecialContainer() sc.items = [1, 2, 3] print(bool(sc)) # True sc.enabled = False print(bool(sc)) # False(enabled が False)
まとめ
__bool__ と __len__ を適切に実装することで、自作クラスのオブジェクトを if 文で自然に扱えるようになる。
オブジェクトの有効/無効、成功/失敗、接続/切断など、長さとは関係ない状態を真偽値で表現したいとき。
コンテナ的なオブジェクトで、「空なら False、要素があれば True」という挙動が自然なとき。
両方定義する場合は __bool__ が優先されることを忘れずに。意図した挙動になるよう、どちらを使うか(あるいは両方使うか)を設計時に決めておこう。