MathPython491378 views
りんご192546 views
教育148875 views
世界の国560595 views
高校物理158224 views
いろは2986023 views
高校化学2913383 views
雑学1472593 views
ヒストリア284143 views
Computer365120 views
Help
Tools

English

オブジェクトの真偽値判定が決まるまでの内部フロー(Python)

Python でオブジェクトを if 文や bool() で評価するとき、内部では何が起きているのか。CPython のソースコードを追いながら、真偽値判定の仕組みを詳しく見ていく。

判定フローの全体像

オブジェクトの真偽値が決まるまでの流れは以下のようになる。

bool(obj) または if obj: を実行

obj.bool() を探す

なければ obj.len() を探す

どちらもなければ True を返す

この判定は CPython の PyObject_IsTrue 関数で実装されている。

CPython の実装を読む

CPython のソースコード(Objects/object.c)にある PyObject_IsTrue 関数が真偽値判定の中核だ。

# CPython の PyObject_IsTrue を Python 風に書き下すと
def PyObject_IsTrue(v):
    # None と False は特別扱い(高速パス)
    if v is None or v is False:
        return 0
    if v is True:
        return 1
    
    # 型の tp_as_number に nb_bool があるか
    # (__bool__ のスロット)
    if hasattr(type(v), '__bool__'):
        res = v.__bool__()
        if not isinstance(res, bool):
            raise TypeError("__bool__ should return bool")
        return res
    
    # tp_as_sequence に sq_length があるか
    # (__len__ のスロット)
    if hasattr(type(v), '__len__'):
        res = v.__len__()
        if res < 0:
            raise ValueError("__len__() should return >= 0")
        return res != 0
    
    # どちらもなければ True
    return 1

実際の C コードでは、型オブジェクトのスロット(tp_as_number->nb_booltp_as_sequence->sq_length)を直接参照するため、辞書検索よりも高速に動作する。

組み込み型の実装

組み込み型はそれぞれ最適化された真偽値判定を持つ。

# int の __bool__(CPython では nb_bool スロット)
# 0 なら False、それ以外は True
print(bool(0))    # False
print(bool(42))   # True
print(bool(-1))   # True

# float の __bool__
# 0.0 なら False
print(bool(0.0))  # False
print(bool(0.1))  # True

# str の __bool__(__len__ 経由ではなく専用実装)
# 空文字列なら False
print(bool(""))     # False
print(bool("hi"))   # True

# list, dict, set なども同様
print(bool([]))     # False
print(bool([1]))    # True

None と False の高速パス

NoneFalse は最も頻繁にチェックされるため、PyObject_IsTrue の先頭で特別扱いされている。

# CPython での最初のチェック
if (v == Py_None || v == Py_False)
    return 0;
if (v == Py_True)
    return 1;

これにより、if x is None のような頻出パターンが高速に処理される。

スロットの優先順位

CPython の型システムでは、__bool__tp_as_number->nb_bool スロットに、__len__tp_as_sequence->sq_length スロットに格納される。判定順序は以下の通りだ。

nb_bool(__bool__)が存在すればそれを呼ぶ
nb_bool がなければ sq_length(__len__)を呼ぶ
mp_length(マッピング用の __len__)も sq_length がなければ確認する
すべてなければ True を返す

マッピング型(dict など)は mp_length スロットを持つが、真偽値判定では sq_length が優先される。ただし、dict は両方を持っているため実用上の違いはない。

継承と MRO の影響

カスタムクラスでは、メソッド解決順序(MRO)に従って __bool____len__ が検索される。

class Base:
    def __len__(self):
        print("Base.__len__")
        return 1

class Child(Base):
    def __bool__(self):
        print("Child.__bool__")
        return False

c = Child()
print(bool(c))
# 出力:
# Child.__bool__
# False

# Child に __bool__ があるので Base.__len__ は呼ばれない
# __bool__ を継承せず __len__ だけ継承する場合
class Container:
    def __len__(self):
        return len(self.items)

class SpecialContainer(Container):
    def __init__(self):
        self.items = []

sc = SpecialContainer()
print(bool(sc))  # False(__len__ が 0)

sc.items = [1, 2, 3]
print(bool(sc))  # True(__len__ が 3)

bool() 関数と暗黙の変換の違い

bool() 関数を明示的に呼ぶ場合と、if 文での暗黙の変換は、内部的には同じ PyObject_IsTrue を使う。ただし、bool() は必ず TrueFalse を返すのに対し、PyObject_IsTrue は C の int(0 または 1)を返す。

# bool() は常に bool 型を返す
print(type(bool(42)))      # <class 'bool'>
print(type(bool("hello"))) # <class 'bool'>

# if 文では内部で PyObject_IsTrue が呼ばれる
# 戻り値は bool 型に変換されない(C の int として扱われる)
if 42:
    print("truthy")

dis モジュールで確認する

バイトコードを見ると、真偽値判定がどのように行われるか分かる。

import dis

def check_bool(x):
    if x:
        return True
    return False

dis.dis(check_bool)

出力(Python 3.11 以降で若干異なる場合がある):

#   2           0 LOAD_FAST                0 (x)
#               2 POP_JUMP_IF_FALSE        8
#   3           4 LOAD_CONST               1 (True)
#               6 RETURN_VALUE
#   4     >>    8 LOAD_CONST               2 (False)
#              10 RETURN_VALUE

POP_JUMP_IF_FALSE 命令が内部で PyObject_IsTrue を呼び出している。

パフォーマンスへの影響

真偽値判定のパフォーマンスは、オブジェクトの型によって異なる。

import timeit

# 組み込み型は最適化されている
print(timeit.timeit("bool([])", number=1000000))
print(timeit.timeit("bool('')", number=1000000))

# カスタムクラスは若干遅い
class Custom:
    def __bool__(self):
        return True

c = Custom()
print(timeit.timeit("bool(c)", globals={"c": c}, number=1000000))

組み込み型は C レベルで最適化されているため、カスタムクラスよりも高速だ。ただし、ほとんどのアプリケーションでは無視できる差である。

まとめ

Python の真偽値判定は、以下の順序で行われる。

判定の優先順位
  1. None / True / False の高速パス → 2. __bool__ メソッド → 3. __len__ メソッド → 4. デフォルトで True
実装のポイント

組み込み型は型スロットを直接参照するため高速。カスタムクラスは MRO に従ってメソッドが検索される。

この内部フローを理解しておくと、カスタムクラスの設計や、予期せぬ真偽値判定の動作をデバッグするときに役立つ。