あるオブジェクトとNoneを比較するときって、どんなやり方をしていますか? それって普段は問題なくても実はダメなやり方じゃないですか? ちょっと確認してみましょ。
以下は変数nの値がNoneかどうかを判定するコードである。このコードは問題なく動作するが、Python風ではない。どこを修正すればよいだろう。
n = None
if n == None:
print('n == None')
else:
print('n != None')
今回はコードもちょっとシンプルでChatGPT/Claude/Geminiに聞くようなことも思い付かなかったので、「Noneて何て読む?」と聞いてみることにしました。ちょっとした目的があってこんなことを聞いてみたのですが、そこに気が付いたLLMは1つもありませんでした。さて、その目的とは何でしょう。え? 画像がない? そうです。今回はないんです。なんかよいインスピレーションが湧かなかったんです。ごめんなさい。
どうもHPかわさきです。
Python 3.14.0が正式リリースされましたねー。2025年10月7日の予定だったので、いつリリースになるかをジッと待っていたのですが、日本時間では10月8日に日付が変わる少し前くらいだったんじゃないかな。21時にはまだリリースされていなかったような気がします。
Python 3.14.0の正式リリースに合わせて、Deep Insiderでは「Python祭り」を自称して、10月中はPython 3.14の新機能を紹介する記事をちょっと多めに投下していく予定です。その手始めに『「Python 3.14」で公式サポートに移行したフリースレッド版Pythonとは? その進化の道筋』を公開しています。興味のある方はぜひ。
正解のコード例を以下に示します。
n = None
if n is None:
print('n is None')
else:
print('n is not None')
==演算子を使うのではなく、is演算子を使って変数nとNoneとを比較することが、Pythonでは推奨されています。2つのprint関数呼び出しではこれに対応してメッセージも変更していますが、これはあくまでおまけの変更です。
では、is演算子を使う理由とは何でしょう。
Pythonに限らず、多くのプログラミング言語では2つのオブジェクトの値を比較する際に次に示す2種類のやり方があります。
簡単な例でこれらの違いを見てみましょう。以下は2つの変数s0とs1に文字列'abc'を代入するコードです。
s0 = 'abc'
s1 = ''.join(['a', 'b', 'c'])
print(id(s0)) # 4553174368など
print(id(s1)) # 4559210032など
このコードを実行すると、s0とs1には別々のオブジェクトが代入されます。s0にはコードに記述された文字列がそのまま代入されるのに対して、s1ではリストの全要素を見て、それらを結合して文字列にまとめるのに必要なメモリを確保して、そこに個々の文字列要素を埋め込んで……のような処理が行われた結果が代入されるからです(ちなみに、ここで「s2 = 'abc'」とすると、多くの場合、s0とs2は同じオブジェクトを指すようになります)。
下の2行で呼び出しているid関数は引数に指定したオブジェクトが位置するメモリアドレスを返します(CPythonの実装)。今述べたような動作をすることから、それぞれのオブジェクトが位置するメモリアドレスは異なるものになります(読者がこのコードを実行するとid関数の戻り値は上のコメントとは異なるものになるでしょうが、2つの戻り値が異なることに変わりはないはずです)。
そして、2つのid関数呼び出しの戻り値が異なっていることから、これら2つのオブジェクトが別モノであることが分かります。が、その値はどちらも'abc'という文字列です。よって、これらのオブジェクトの値が同一かどうかを知りたいのであれば、==演算子を使います。
print(s0 == s1) # True
しかし、2つのオブジェクトは異なるものです。よって、is演算子を使うとそれらが別モノであるかどうかを調べられます。
print(s0 is s1) # False
ここまで話したことが大前提です。もう1つ重要なことがあります。それはNoneという値はPythonでは「シングルトン」と呼ばれ、「そのコードの実行時にただ一つだけ存在する」もう少し詳しく書くと「Python処理系の実行時にそのプロセス内で、NoneType型の唯一のインスタンスとして存在する」ということです。'abc'という文字列は上でも見たように、複数存在することが許されていますが、Noneはそうではないということです。
そして、このような値(シングルトン)と比較するときには、is演算子(もしくはis not演算子)を使うことが強く推奨されているのです(PEP 8の『Programming Recommendations』でもそう述べられています)。
あ、さらにもう1つ重要なことがありました。それは==演算子による比較は、==演算子の左辺に置かれたオブジェクトの__eq__特殊メソッドを呼び出すということです。つまり、2つのオブジェクトの値が等しいかどうかを判定する「s0 == s1」という比較は次のコードと等しくなります。
s0.__eq__(s1)
では、次のようなクラスがあったとしましょう。
class AlwaysEqual:
def __eq__(self, other):
return True
このようなクラスがあったとして、そのオブジェクトとNoneを比較することを考えてみます。
ae = AlwaysEqual()
print(ae == None) # True
このように==演算子の振る舞い(__eq__特殊メソッド)は上書きできるので、==演算子によるNoneとの比較はすべきではないということです(逆に、is演算子の振る舞いはオブジェクトのメモリアドレスを比較すると決まっていて、これを変更することはできません)。
上の例は極端ですが、実際に==演算子の振る舞いを上書きした結果、オブジェクトとNoneの比較がプログラマーの意図とは異なるかもしれないという状況は起こります。例えば、以下のコードがそうです。
import numpy as np
a = np.array([1, 2, 3, None])
print(a == None)
このコードでは変数aには多次元配列が代入されているので、Noneではありません。ということは、「a == None」という比較はFalseがその値になりそうです。でも、実行結果は以下の通りです。
NumPyの多次元配列では==演算子による比較が上書きされ、配列の各要素と比較対象の値が等しいかどうかを判定した結果が多次元配列として戻されます。そのため、オブジェクトが存在しないかどうかを判定しようと思って「a == None」と書くと、想定外の結果となるかもしれません。
こんなこともあるので、オブジェクトが存在するかどうか(存在しないかどうか)を調べるときにはis演算子(またはis not演算子)を使うことが推奨されているのです。
さて「Noneて何て読む?」と聞いた結果を発表します。
こんなことを聞いた狙いは『これは「何」と「ナン」を掛けたダジャレですか?』というツッコミを受けることでした。が、そこに気付いたLLMは皆無(がっくり)。というか、Geminiさんは発音まで本場っぽいのかどうなのか、「ナン」とすら答えてくれなかったのでした。後から「ダジャレって分かった?」と聞いたら、皆さん納得はしてくれましたけどね。筆者もまだまだ修行が必要です(ナンの?)。
Copyright© Digital Advantage Corp. All Rights Reserved.