概要
Python3でシーケンス(リストやタプル)の要素が空であるか判定したいとき、以下のようなコードになると思う。
list_1 = []
if list_1 == []:
print(list_1)
[] # 出力
tuple_1 = ()
if tuple_1 == ():
print(tuple_1)
() # 出力
if文の条件式での比較対象に、空のシーケンスを指定すればいい。
それはそれとして、「Python」「リスト」「空判定」といったキーワードで検索を行うと、以下のような書き方を示すサイトが検索上位に多く表示される。
list_1 = []
if not len(list_1):
print(list_1)
[] # 出力
tuple_1 = ()
if not len(tuple_1):
print(tuple_1)
() # 出力
前述のコードのように同じ条件式でリストとタプルの両方の判定を行えるので便利。
ただ、Pylintでコードのチェックを行うと、このコードに対して以下のような注意がされる。
Message emitted:
Do not use len(SEQUENCE) without comparison to determine if a sequence is empty
Description:
Used when Pylint detects that len(sequence) is being used without explicit comparison inside a condition to determine if a sequence is empty. Instead of coercing the length to a boolean, either rely on the fact that empty sequences are false or compare the length against a scalar.シーケンスが空であるかどうかを判断するために比較せずにlen(SEQUENCE)を使用しないでください
Pylint が、シーケンスが空かどうかを判断するための条件内で len(sequence) が明示的に比較されずに使用されていることを検出した場合に使用します。長さをブール値に強制する代わりに、空のシーケンスは偽であるという事実に依存するか、長さをスカラーと比較します。
日本語訳はDeepL翻訳から
なぜ注意されるのか
まず、組み込み関数len()
を使った空判定の処理の流れを確認する。
-
len(SEQUENCE)
でシーケンスの長さ(要素数)を得る - ifの条件式の中で、整数型の要素数を真偽値にキャストされる
- Pythonにおいて0はFalse、それ以外はTrueとして判断される
list_t = ['hoge']
list_f = []
tuple_t = ('hoge',)
tuple_f = ()
print(f'list_t is {bool(len(list_t))}')
print(f'list_f is {bool(len(list_f))}')
print(f'tuple_t is {bool(len(tuple_t))}')
print(f'tuple_f is {bool(len(tuple_f))}')
# list_t is True
# list_f is False
# tuple_t is True
# tuple_f is False
この流れで、if len(SEQUENCE)
はSEQUENCE
が空であるかの判定を行っている。
説明文中の「明示的に比較されない」「ブール値に強制」というのは主に2の処理にあたる。
同じく説明文中にあるが、Pythonでは要素が空のシーケンスはFalse、要素を持つものはTrueと判定される。
この前提から見ると、len()
で要素数を得るのは何か他の意図があるのではないかと読み手に誤解させてしまう恐れがあるため、Pylintは非推奨と注意を促している(のだと解釈した)
あるシーケンスが空であるかを判定する場合、以下のようなコードが最もシンプルで意図が伝わりやすい。
list_t = ['hoge']
tuple_t = ('hoge',)
if list_t:
print('list_t is not empty')
if tuple_t:
print('tuple_t is not empty')
# list_t is not empty
# tuple_t is not empty
余談
(1) len(SEQUENCEC)
を真偽値にキャストする方法
(2) 条件式にSEQUENCE
を指定する方法
例えば、シーケンスの要素が以下のような場合はどうなるだろうか。
list_3 = [[]]
tuple_3 = ((),)
SEQUENCE_3
は要素として空のシーケンスを持っている。
(1)(2)の方法はどちらも対象のシーケンスが持つ要素の有無を判断材料にしている。なので、どちらの方法でもこのようなシーケンスは空ではないと判定される。
list_3 = [[]]
tuple_3 = ((),)
print(f'(1) list_3 is {bool(len(list_3))}')
print(f'(2) list_3 is {bool(list_3)}')
print(f'(1) tuple_3 is {bool(len(tuple_3))}')
print(f'(2) tuple_3 is {bool(tuple_3)}')
# list_3 is True
# list_3 is True
# tuple_3 is True
# tuple_3 is True
(1)(2)の方法はあらゆるシーケンスに真となる要素が含まれていることを保証するものではないとわかる。
個人的には、手癖でコーディングするときにはシーケンス等の空判定には組み込み関数any()
を使う。
any()
は引数にイテラブルを取り、そのいずれかの要素が真ならばTrue、要素が空ならばFalseを返す。
これならば、空のシーケンスのみを持つ場合も除けることができて安心する。
list_3 = [[]]
list_4 = [['hoge']]
tuple_3 = ((),)
tuple_4 = (('hoge',),)
print(f'list_3 is {any(list_3)}')
print(f'list_4 is {any(list_4)}')
print(f'tuple_3 is {any(tuple_3)}')
print(f'tuple_4 is {any(tuple_4)}')
# list_3 is False
# list_4 is True
# tuple_3 is False
# tuple_4 is True
もちろん、操作する前にしっかりと要素の確認を行うのがベストだけれども