目次
Pythonで if not s: と書けば空文字列はだいたい弾けるのですが、Noneと空文字を区別したい場面で同じ書き方をして失敗したことがあります。AWS Lambdaで環境変数を扱ったときに「未設定のまま起動された」と「明示的に空欄を渡してきた」を分けたかったのに、not だけだとどちらも同じ扱いになる。そこから条件式を書き分けるようになりました。根拠を PEP 8 と内部の動きで整理しました。
基本は if not s: で済む
空文字列は真偽値として偽に評価されるので、bool("") は False を返します。
s = ""
if not s:
print("空")
この書き方が他の比較より速いのは、__bool__(フォールバックで __len__)を呼び出すだけで済むからです。len(s) == 0 のような関数呼び出しや、s == "" のような値比較を経由しません。読みやすさの面でも、Python公式ドキュメントの真理値判定の節[1]で示されている標準的なやり方です。
普段のユーティリティ関数や、欠損も空も等しく扱っていい場面はこれで十分。
「None」と「空文字」は意味が違う
if not s: は次の値をすべて偽として扱います。
None""0/0.0False[]/{}/set()
困るのは、APIのレスポンスで "city": null と "city": "" を区別したいときです。null は値が無いということで、"" はユーザーが「空欄として送ってきた」可能性がある。実装の判断として、未送信と空入力を別の処理に分けたいことがあります。
明示的に分けるなら is None と == "" を併記
def describe(value):
if value is None:
return "未設定"
if value == "":
return "空文字で送られた"
return f"値あり: {value}"
value is None の判定で == ではなく is を使うのは、PEP 8 の「シングルトン(=実行中に1つしか存在しない値)と比較するときは常に is か is not を使う」という指針に沿っているからです[2]。None は処理系の中で同じ実体を共有しているため、同一性で見たほうが速く、誤った __eq__ 実装に左右される心配もありません。
一行で両方を空扱いする
数値の 0 まで空に巻き込まないようにしつつ短く書くなら、明示的に並べます。
if value is None or value == "":
return "空っぽ"
if not value: だと 0 も False も拾ってしまうので、フォームの数値入力欄を扱うコードでは特にこの並べ方が役立ちます。
ユーザー入力は strip を挟む
半角スペースやタブだけ入力されたケースを弾きたい場合は、strip() で前後の空白を落としてから判定します。
text = " "
if not text.strip():
print("実質空")
" " 自体は bool(" ") が True を返すので、if not text: のままだとすり抜けます。問い合わせフォームやCLIの引数のように人間が手で入力する経路では、見えない空白を吸収するこの一手間が、後から「なぜか空欄チェックが通る」というバグ報告を減らしてくれます。
型ヒントで前段に弾く
書き分けの前に、引数の型で「空文字や None が来るのか」を明示しておくのも手です。
from typing import Optional
def fetch(url: Optional[str]) -> str:
if url is None:
raise ValueError("url is required")
...
str として宣言された引数に None が混ざると、mypy や pyright が呼び出し側で警告を出してくれます。判定の前段に型のレイヤを置けば、本体のロジックを if not value: 1つに絞れる場面が出てきます。
まとめ
== None の書き方を撲滅したいだけなら、ruff の E711 ルールを有効にしておけば自動で指摘してくれます[3]。lint を入れていないプロジェクトなら、チームのコーディング規約に「None 判定は is None」と1行だけ加えておくのが小さい予防になります。バグを踏んでから「ああ、これが None と空文字の取り違えか」と気付くより、入口で型と判定の癖を揃えておくほうが、後の自分が助かります。
