オブジェクトの真偽値判定が決まるまでの内部フロー(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_bool や tp_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])) # TrueNone と False の高速パス
None と False は最も頻繁にチェックされるため、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 スロットに格納される。判定順序は以下の通りだ。
マッピング型(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() は必ず True か False を返すのに対し、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_VALUEPOP_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 の真偽値判定は、以下の順序で行われる。
- None / True / False の高速パス → 2.
__bool__メソッド → 3.__len__メソッド → 4. デフォルトで True
組み込み型は型スロットを直接参照するため高速。カスタムクラスは MRO に従ってメソッドが検索される。
この内部フローを理解しておくと、カスタムクラスの設計や、予期せぬ真偽値判定の動作をデバッグするときに役立つ。










