シーケンスの長さを判定するケース
list,tuple,str などのシーケンスの場合、長さ 0 の判定では
if seq: # PEP 8 - OK
if len(seq): # PEP 8 - NG
とされているようです。
この記述がトラブルとなるケース
独自クラスやモジュールでシーケンスに対応しつつ
中身が特定パターンのときに __bool__() が True (長さが 0 でなくても False)を返す
オブジェクトを作ると不具合の原因になりかねません。
class A:
def __init__(self): pass
def __bool__(self):
print('A.__bool__')
return False
def __len__(self):
print('A.__len__')
return 1
class B:
def __init__(self): pass
def __len__(self):
print('B.__len__')
return 1
a = A()
if a:
print('if a >> TRUE')
else:
print('if a >> FALSE')
if len(a):
print('if len(a) >> TRUE')
else:
print('if len(a) >> FALSE')
b = B()
if b:
print('if b >> TRUE')
else:
print('if b >> FALSE')
if len(b):
print('if len(b) >> TRUE')
else:
print('if len(b) >> FALSE')
print('bool(b) >>', bool(b))
$ python3 sample.py
A.__bool__
if a >> FALSE
A.__len__
if len(a) >> TRUE
B.__len__
if b >> TRUE
B.__len__
if len(b) >> TRUE
B.__len__
bool(b) >> True
$
$ python3
>>> l = list()
>>> l.__bool__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__bool__'
>>> l.__len__()
0
>>> bool(l)
False
>>> s = str()
>>> s.__bool__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__bool__'
>>> s.__len__()
0
>>> bool(s)
False
>>> t = tuple()
>>> t.__bool__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute '__bool__'
>>> t.__len__()
0
>>> bool(t)
False
if 文では __len__() より __bool__() が優先されることが分かる。
list, str, tuple は __bool__() を持っていないらしい。
PEP 8 に従うならば
シーケンスを扱う PEP 8 準拠のプログラムに、独自のオブジェクトを渡すのならば
def __bool__(self):
return self.__len__() != 0
長さを調べて 0 かどうか(上記相当)以外の処理を __bool__() メソッドで作るのはやめたほうがいいことになります。
世の中のプログラムが PEP 8 準拠ばかりならば、事実上の仕様と考えた方がよさそうです。
シーケンスに対応しつつ、シーケンスの中身で __bool__() の戻り値を決めるオブジェクトを作った場合、それをシーケンス処理が出来る他所のプログラムに渡すには、長さが 0 のときの判定方法を確認する必要がありそうです。__bool__() で判定していないかを…
この地雷は以前踏んだので、シーケンスの長さで判定する処理は必ず len を書くようにしている。