[Python] Trueは1, Falseは0.
環境
この記事の内容は、Python 2.4.4c1, Twisted 2.4.0-1で確認しました。
疑問点
Twistedのソースコードを読んでいたら、以下のような記述を見つけました。ファイルは、/usr/lib/python2.4/site-packages/twisted/internet/defer.pyです。
def _runCallbacks(self): : callback, args, kw = item[ isinstance(self.result, failure.Failure)]
ここで、itemは配列です。配列ですが、インデックスには数値ではなく、TrueまたはFalseであるisinstance関数の戻り値を与えています。なぜこんなことが可能なのか、調べてみました。
結論
TrueとFalseは整数であり、Trueは1, Falseは0です。
詳細
現象からの検証
他のところでTrueは1, Falseは0であるということを耳にしていたので、確かめてみました。
>>> True == 1 True >>> False == 0 True
確かに、Trueは1, Falseは0のようです。なので、以下のような四則演算も可能です。
>>> True + True 2 >>> True - True 0 >>> True * True 1 >>> True * False 0 >>> True / True 1 >>> False / True 0 >>> True / False Traceback (most recent call last): File "<stdin>", line 1, in ? ZeroDivisionError: integer division or modulo by zero
ソースコードからの検証
Pythonのソースコードで、TrueとFalseがどのようにして定義されているか、調べてみます。
grepしてみたら、Python/bltinmodule.cにそれらしい記述が見つかりました。
PyObject * _PyBuiltin_Init(void) { : #define SETBUILTIN(NAME, OBJECT) \ if (PyDict_SetItemString(dict, NAME, (PyObject *)OBJECT) < 0) \ return NULL; \ ADD_TO_ALL(OBJECT) : SETBUILTIN("False", Py_False); SETBUILTIN("True", Py_True);
どうやら、Py_FalseとPy_Trueというオブジェクト(C言語でいうと構造体)をそれぞれFalseとTrueにしているようです。
ふたたびgrepしてみたら、Include/boolobject.hでPy_FalseとPy_Trueは定義されていました。
/* Don't use these directly */ PyAPI_DATA(PyIntObject) _Py_ZeroStruct, _Py_TrueStruct; /* Use these macros */ #define Py_False ((PyObject *) &_Py_ZeroStruct) #define Py_True ((PyObject *) &_Py_TrueStruct)
PyAPI_DATAはInclude/pyport.hで定義されているマクロで、コンパイル時の条件によって様々な尾ひれがつきますが、おおむね以下のようになっています。
# define PyAPI_DATA(RTYPE) extern RTYPE
このマクロを展開すると、上のコードは、
extern PyIntObject _Py_ZeroStruct, _Py_TrueStruct;
となります(ところで、なんで"_Py_FalseStruct"ではなくて"_Py_ZeroStruct"なんでしょうか)。PyIntObject型はその名の通り整数を表すのでしょうから、TrueとFalseは整数であることが、この時点で推測されます。
それらの実体である_Py_ZeroStructと_Py_TrueStructは、Objects/boolobject.cで見つかりました。
PyIntObject _Py_ZeroStruct = { PyObject_HEAD_INIT(&PyBool_Type) 0 }; PyIntObject _Py_TrueStruct = { PyObject_HEAD_INIT(&PyBool_Type) 1 };
PyObject_HEAD_INITマクロが使用されているので、これを調べます。これの定義は、Include/object.hにありました。関連している箇所もあわせて抜き出します。
#ifdef Py_TRACE_REFS : #define _PyObject_EXTRA_INIT 0, 0, #else : #define _PyObject_EXTRA_INIT #endif : #define PyObject_HEAD_INIT(type) \ _PyObject_EXTRA_INIT \ 1, type,
_PyObject_EXTRA_INITマクロがPy_TRACE_REFSシンボルの定義によって二通りにわかれますが、Pythonのドキュメント (http://www.python.jp/doc/2.3.5/api/common-structs.html) によると、Py_TRACE_REFSシンボルは通常は定義されないとのことなので、マクロを展開すると、上の_Py_ZeroStruct変数と_Py_TrueStruct変数は、
PyIntObject _Py_ZeroStruct = { 1, &PyBool_Type, 0 }; PyIntObject _Py_TrueStruct = { 1, &PyBool_Type, 1 };
となります。ここで、PyIntObject構造体の定義を見てみます。これはInclude/intobject.hにありました。
typedef struct { PyObject_HEAD long ob_ival; } PyIntObject;
PyObject_HEADマクロが出てきているので、これを調べます。これの定義は、先ほども出てきたInclude/object.hにありました。関連している箇所も抜き出して掲載します。
#ifdef Py_TRACE_REFS /* Define pointers to support a doubly-linked list of all live heap objects. */ #define _PyObject_HEAD_EXTRA \ struct _object *_ob_next; \ struct _object *_ob_prev; : #else #define _PyObject_HEAD_EXTRA : #endif /* PyObject_HEAD defines the initial segment of every PyObject. */ #define PyObject_HEAD \ _PyObject_HEAD_EXTRA \ int ob_refcnt; \ struct _typeobject *ob_type;
先ほど述べた通り、Py_TRACE_REFSシンボルは標準では定義されないので、PyObject_HEADマクロは、
#define PyObject_HEAD \ int ob_refcnt; \ struct _typeobject *ob_type;
となり、PyIntObject型は、
typedef struct { int ob_refcnt; struct _typeobject *ob_type; long ob_ival; } PyIntObject;
となります。名前から推察するに、PyIntObject型の各メンバは、
名前 | 説明 |
---|---|
ob_refcnt | リファレンスカウント |
ob_type | Pythonの型を表す変数へのポインタ |
ob_ival | 変数の値 |
となりそうです。すなわち、先ほど出てきた、
PyIntObject _Py_ZeroStruct = { 1, &PyBool_Type, 0 }; PyIntObject _Py_TrueStruct = { 1, &PyBool_Type, 1 };
というコードは、以下を表していることとなります。
名前 | リファレンスカウント | Pythonの型を表す変数へのポインタ | 変数の値 |
---|---|---|---|
_Py_ZeroStruct | 1 | &PyBool_Type | 0 |
_Py_TrueStruct | 1 | &PyBool_Type | 1 |
つまり、Falseは0で、Trueは1, ということです。
ちなみに、PyBool_Type変数はObjects/boolobject.cにあり、
PyTypeObject PyBool_Type = {
:
&PyInt_Type, /* tp_base */
のように、基底クラスを指すのであろうメンバ変数に、整数を表すと思われるPyInt_Typeを指定しています。まとめると、TrueとFalseは整数 (PyIntObject) であるが、Pythonとしての型はPyBool_Typeであり、これは整数を継承している、ということになります。
考えられる応用例
例えばTrueまたはFalseが要素となっているリストbool_arrayがあったとして、その中のTrueを数えるとき、素直に書けば、
count = 0 for b in bool_array: if b is True: count += 1
となるところを、
count = sum(bool_array)
と置き換えられます(これがあとで読む人にとって優しいかは別にして)。