C1802:use-implicit-booleaness-not-lenと怒られた反省

# 概要
Python3でシーケンス（リストやタプル）の要素が空であるか判定したいとき、以下のようなコードになると思う。
```python
list_1 = []
if list_1 == []:
    print(list_1)


[] # 出力

tuple_1 = ()
if tuple_1 == ():
    print(tuple_1)


() # 出力
```
if文の条件式での比較対象に、空のシーケンスを指定すればいい。

それはそれとして、「Python」「リスト」「空判定」といったキーワードで検索を行うと、以下のような書き方を示すサイトが検索上位に多く表示される。
```python
list_1 = []
if not len(list_1):
    print(list_1)

[] # 出力

tuple_1 = ()
if not len(tuple_1):
    print(tuple_1)

() # 出力
```
前述のコードのように同じ条件式でリストとタプルの両方の判定を行えるので便利。
ただ、Pylintでコードのチェックを行うと、このコードに対して以下のような注意がされる。

https://pylint.pycqa.org/en/v2.13.9/messages/convention/use-implicit-booleaness-not-len.html

> **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翻訳](https://www.deepl.com/translator)から
## なぜ注意されるのか

まず、組み込み関数`len()`を使った空判定の処理の流れを確認する。
1. `len(SEQUENCE)`でシーケンスの長さ（要素数）を得る
2. ifの条件式の中で、整数型の要素数を真偽値にキャストされる
3. Pythonにおいて0はFalse、それ以外はTrueとして判断される

```python
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は非推奨と注意を促している（のだと解釈した）

あるシーケンスが空であるかを判定する場合、以下のようなコードが最もシンプルで意図が伝わりやすい。

```python
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`を指定する方法

例えば、シーケンスの要素が以下のような場合はどうなるだろうか。
```python
list_3 = [[]]
tuple_3 = ((),)
```
`SEQUENCE_3`は要素として空のシーケンスを持っている。
(1)(2)の方法はどちらも対象のシーケンスが持つ要素の有無を判断材料にしている。なので、どちらの方法でもこのようなシーケンスは空ではないと判定される。

```python
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を返す。

https://docs.python.org/ja/3/library/functions.html#any

これならば、空のシーケンスのみを持つ場合も除けることができて安心する。

```python
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
```

もちろん、操作する前にしっかりと要素の確認を行うのがベストだけれども