いろは2986023 views
LaTeX957300 views
Computer365120 views
中学理科1626207 views
小学算数1194618 views
教育148875 views
ヒストリア284143 views
高校国語785655 views
りんご192546 views
中学社会667106 views
Help
Tools

English

__bool__ と __len__ による真偽値カスタマイズ(Python)

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 文で自然に扱えるようになる。

__bool__ を使う場面

オブジェクトの有効/無効、成功/失敗、接続/切断など、長さとは関係ない状態を真偽値で表現したいとき。

__len__ に任せる場面

コンテナ的なオブジェクトで、「空なら False、要素があれば True」という挙動が自然なとき。

両方定義する場合は __bool__ が優先されることを忘れずに。意図した挙動になるよう、どちらを使うか(あるいは両方使うか)を設計時に決めておこう。