はじめに
Pythonを学んでいると、「None
の判定に==
を使ってはいけない、is
を使いなさい」というアドバイスをよく見かけます。しかし、その理由を調べてみると、多くの記事では「is
を使いましょう」という結論だけで、なぜそうすべきなのか、その背景にある仕組みまで踏み込んで解説しているものは少ないように感じました。
また、情報が断片的であったり、信頼できる出典が示されていなかったりすることも少なくありません。結局、正確な情報を得るためには、複数の記事を読み比べ、公式ドキュメントまで遡る必要がありました。
そこでこの記事では、以下の点について、公式ドキュメントなどの信頼できる情報を元に、包括的(self-contained)に解説することを目指します。
- なぜ
None
の判定に==
ではなくis
を使うべきなのか、その根本的な理由 -
if x is None
vs.if not x
(暗黙的な偽)の使い分け -
is
演算子を使う上での思わぬ落とし穴
この記事が、皆さんのPythonコーディングにおける迷いを一つでも解消できれば幸いです。
結論:is
は"同一性"、==
は"等価性"を比べる
早速結論から述べます。None
の判定にis
を使うべき理由は、is
がオブジェクトの「同一性(identity)」を比較するのに対し、==
はオブジェクトの「等価性(equality)」を比較するからです。
そして、None
はPythonの実行環境全体でただ一つしか存在しない「シングルトン(Singleton)」オブジェクトであるため、同一性での比較が最も確実なのです。
実際、公式ドキュメントでは次のように説明されています。
This type has a single value. There is a single object with this value. This object is accessed through the built-in name None. It is used to signify the absence of a value in many situations, e.g., it is returned from functions that don’t explicitly return anything. Its truth value is false.
(この型は単一の値を持ちます。この値を持つオブジェクトは一つだけです。このオブジェクトは組み込み名
None
を通じてアクセスされます。多くの状況で値が存在しないことを示すために使用されます。例えば、明示的に何も返さない関数からはNone
が返されます。その真偽値はFalse
です。)
それぞれ詳しく見ていきましょう。
is
演算子:オブジェクトの"同一性"を比較する (id()
)
is
演算子は、2つの変数が全く同じオブジェクトを指しているかどうかを判定します。これは、言い換えるとオブジェクトのメモリ上のアドレスが同じかどうかを調べている、ということです。実際、Pythonには、オブジェクトのメモリアドレスを返すid()
という組み込み関数があり、a is b
という式は、内部的にはid(a) == id(b)
を実行しているのと等価です。
# is は id() が一致するかを見ている
a = [1, 2, 3]
b = a # bはaと全く同じオブジェクトを指す
c = [1, 2, 3] # cはaと同じ"値"を持つが、別のオブジェクト
print(f"a is b: {a is b}") # aとbは同じオブジェクトなのでTrue
print(f"id(a) == id(b): {id(a) == id(b)}")
print(f"a is c: {a is c}") # aとcは違うオブジェクトなのでFalse
print(f"id(a) == id(c): {id(a) == id(c)}")
実行結果:
a is b: True
id(a) == id(b): True
a is c: False
id(a) == id(c): False
ちなみに、公式ドキュメントでは、is
演算子は次のようにも説明されています。つまり、デフォルト動作では、x is y
がTrue
ならばx == y
もTrue
になるということです。
The default behavior for equality comparison (== and !=) is based on the identity of the objects. Hence, equality comparison of instances with the same identity results in equality, and equality comparison of instances with different identities results in inequality. A motivation for this default behavior is the desire that all objects should be reflexive (i.e. x is y implies x == y).
(等価比較(
==
と!=
)のデフォルトの動作は、オブジェクトの同一性に基づいています。したがって、同じ同一性を持つインスタンスの等価比較は等しいという結果になり、異なる同一性を持つインスタンスの等価比較は等しくないという結果になります。このデフォルトの動作の動機は、すべてのオブジェクトが反射的であること(つまり、x is y
が真ならばx == y
も真であること)を望むことです。)
==
演算子:オブジェクトの"等価性"を比較する (__eq__()
)
一方、==
演算子は、2つのオブジェクトが値として等しいかどうかを判定します。これは、オブジェクトの__eq__()
という特殊メソッドを呼び出すことで実現されています。
# == は値が等しいかを見ている
a = [1, 2, 3]
c = [1, 2, 3]
print(f"a == c: {a == c}") # aとcは値が等しいのでTrue
実行結果:
a == c: True
重要なのは、この__eq__()
メソッドは、クラスを定義する際に開発者が自由にオーバーライド(上書き)できるという点です。
なぜx == None
は危険なのか?
None
は、Pythonの実行環境において常に唯一の存在(シングルトン)であることが保証されています。どの変数にNone
を代入しても、それはすべて同じNone
オブジェクトを指します。
a = None
b = None
# aとbは同じNoneオブジェクトを指している
print(f"a is b: {a is b}") # -> True
print(f"id(a) == id(b): {id(a) == id(b)}") # -> True
この性質から、None
であるかどうかを判定するには、オブジェクトがNone
そのものであるかを確かめる「同一性」の比較、つまりis
演算子が最も確実で適切です。
もし==
を使ってしまうと、__eq__()
メソッドの予期せぬ実装によって、バグの原因となる可能性があります。例えば、以下のように__eq__
をオーバーライドしたクラスを考えてみましょう。
class MyObject:
def __eq__(self, other):
# どんな比較対しても常にTrueを返してしまう
return True
obj = MyObject()
# objはNoneではないのに、== で比較するとTrueになってしまう!
if obj == None:
print("objはNoneです。 (== で判定)")
else:
print("objはNoneではありません。 (== で判定)")
# is で判定すれば、正しく判定できる
if obj is None:
print("objはNoneです。 (is で判定)")
else:
print("objはNoneではありません。 (is で判定)")
実行結果:
objはNoneです。 (== で判定)
objはNoneではありません。 (is で判定)
このような実装は極端ですが、==
は開発者がその振る舞いを変更できるため、None
という特別な値の判定に使うには不向きなのです。このルールは、Pythonの公式スタイルガイドであるPEP 8でも明確に推奨されています。
Comparisons to singletons like None should always be done with
is
oris not
, never the equality operators.(
None
のようなシングルトンとの比較は、常にis
またはis not
で行うべきであり、等価演算子(==
)は決して使うべきではありません。)
if x is None:
vs. if not x:
None
の判定に==
ではなく、is
を使うのが正しい、ということは分かりました。
一方で、Pythonでは、if not x:
のようにオブジェクトが持つ「真偽値」を利用して、より広い意味での「存在しない」「空である」状態を判定することもよくあります。これには、None
だけでなく、数値の0
や空文字列、空リストなども含まれます。
if not x:
のような「暗黙的な偽」の利用
Pythonでは、None
以外にも「偽(Falsy)」として扱われるオブジェクトがあります。
None
False
- 数値のゼロ (
0
,0.0
,0j
など) - 空のシーケンス (
""
,[]
,()
) - 空のマッピング (
{}
) - その他、
__bool__()
や__len__()
がFalse
または0
を返すオブジェクト
(詳しくは公式ドキュメントを参照)
関数の戻り値が「値が存在しない」ことを示すためにNone
を返す場合や、リストが空である場合など、「存在しない、または空である」という状態をまとめて判定したいケースは非常に多いです。
このような文脈では、if x is None:
と書くよりも、if not x:
と書くこともできます。
Pythonにおいて、暗黙的な偽の利用は、PEP 8でも推奨されており、以下のように説明されています。
For sequences, (strings, lists, tuples), use the fact that empty sequences are false:
(シーケンス(文字列、リスト、タプル)では、空のシーケンスが偽であることを利用してください。)
# Correct: if not seq: if seq:
# Wrong: if len(seq): if not len(seq):
さらに、GoogleのPythonスタイルガイドでも推奨されています。
Use the “implicit” false if possible, e.g.,
if foo:
rather thanif foo != []:
. There are a few caveats that you should keep in mind though:
- Always use
if foo is None:
(oris not None
) to check for aNone
value. E.g., when testing whether a variable or argument that defaults toNone
was set to some other value. The other value might be a value that’s false in a boolean context!- Never compare a boolean variable to
False
using==
. Useif not x:
instead. If you need to distinguishFalse
fromNone
then chain the expressions, such asif not x and x is not None:
.- For sequences (strings, lists, tuples), use the fact that empty sequences are false, so
if seq:
andif not seq:
are preferable toif len(seq):
andif not len(seq):
respectively.- When handling integers, implicit false may involve more risk than benefit (i.e., accidentally handling None as 0). You may compare a value which is known to be an integer (and is not the result of
len()
) against the integer 0.(可能であれば、「暗黙的な」false条件を使用することをお勧めします。例えば、
if foo != []:
の代わりにif foo:
と記述する場合などです。ただし、以下の注意点があります:
None
値の有無を確認する際は、常にif foo is None:
またはif foo is not None:
を使用してください。例えば、デフォルト値がNone
に設定されている変数や引数が、他の値に変更されているかどうかをテストする場合などです。なお、他の値にはブール値として false と評価されるものが含まれる可能性がある点にご注意ください!- ブール型変数を
False
と比較する場合、==
演算子は使用しないでください。代わりにif not x:
を使用してください。False
とNone
を区別する必要がある場合は、if not x and x is not None:
のように条件を連鎖させて記述します。- シーケンス型(文字列、リスト、タプル)の場合、空のシーケンスは false と評価される性質を利用するため、
if seq:
やif not seq:
といった記述方法が、if len(seq):
やif not len(seq):
よりも推奨されます。- 整数を扱う場合、暗黙的な false 条件には利点よりもリスクが伴う可能性があります(例えば、誤って None を 0 として扱ってしまうケースなど)。整数であることが確実な値(len() の結果ではない値)を、整数 0 と直接比較する方法も検討してください。)
ただし、予期せぬ入力や結果を招きかねないため、注意が必要です。例えば文字列の"0"
はTrue
になるが、数値の0
はFalse
になるといったような落とし穴があります。
if x is None:
と if not x:
の使い分け
基本的には、if x is None:
を使うのが最も明確で安全な方法です。
None
と他のFalsyな値(0
や空文字列など)とで処理が変わるようなケースでは、必ずif x is None:
を使うべきです。例えば、以下のような、関数の引数が省略された(デフォルト値のNone
のまま)かどうかを判定するようなケースです。
def my_function(value=None):
# 引数valueが明確に0や空文字として渡された場合と、
# 省略された(Noneのまま)場合とで処理を分けたい
if value is None:
print("valueは指定されませんでした。")
elif not value:
print(f"valueは偽(Falsy)の値({value!r})です。")
else:
print(f"valueは真(Truthy)の値({value!r})です。")
my_function() # 引数を省略
my_function(0) # 偽(Falsy)だがNoneではない
my_function("") # 偽(Falsy)だがNoneではない
my_function("hello") # 真(Truthy)
実行結果:
valueは指定されませんでした。
valueは偽(Falsy)の値(0)です。
valueは偽(Falsy)の値('')です。
valueは真(Truthy)の値('hello')です。
しかし、None
以外の値(例えば0
や空文字列""
)も含めて「存在しない」「空である」としてまとめて扱いたい場合は、if not x:
を使うと、よりシンプルなコードになります。
使い分けのまとめ
-
if x is None:
:まず、0
や空文字など他のFalsyな値とは明確に区別したい場合もしくはNone
の判定をしていることを明示的に示したい場合に使う書き方。 -
if not x:
: 値が存在しない、または空である状態(None
,0
,""
,[]
など)をまとめて扱いたい場合に使う書き方。
余談:is
演算子の思わぬ落とし穴
ここまでis
演算子の有用性を説明してきましたが、None
以外のオブジェクトに使うと思わぬ挙動をすることがあり、注意が必要です。特に、数値や文字列の比較にis
を使うのは避けるべきです。
CPython(標準のPython実装)では、内部的な最適化のために、-5から256までの整数オブジェクトをあらかじめキャッシュして使い回しています。(Optimization in Python – Interning)
これにより、この範囲内の整数ではis
による比較がTrue
になります。
a = 256
b = 256
print(f"a = {a}, b = {b}")
print(f"a is b: {a is b}") # -> True
print(f"id(a) == id(b): {id(a) == id(b)}") # -> True
実行結果:
a = 256, b = 256
a is b: True
id(a) == id(b): True
しかし、この範囲外の数値では、同じ値でも異なるオブジェクトとして生成される可能性があります。
x = 257
y = 257
print(f"x = {x}, y = {y}")
print(f"x is y: {x is y}") # -> False (環境によるが、一般的にFalse)
print(f"id(x) == id(y): {id(x) == id(y)}") # -> False
print(f"x == y: {x == y}") # -> True (値の比較なので、もちろんTrue)
実行結果:
x = 257, y = 257
x is y: False
id(x) == id(y): False
x == y: True
この挙動はあくまでCPythonの実装の詳細であり、Pythonの言語仕様として保証されているものではありません。したがって、値が同じかどうかを比較したい場合は、必ず==
を使いましょう。
より豊富な説明がPython 比較演算子 'is' を使ったときの意外な落とし穴 - Qiitaにもありますので、興味のある方はぜひご覧ください。
まとめ
本記事の要点をまとめます。
-
None
の判定にはis
を使う-
is
はオブジェクトの同一性(id()
が同じか)を比較します。 -
None
はシングルトンオブジェクトなので、同一性で比較するのが最も確実・高速です。
-
-
==
でのNone
判定は避ける-
==
はオブジェクトの等価性(__eq__()
メソッドの結果)を比較します。 -
__eq__()
はユーザーが振る舞いを変更できるため、意図しない結果を招く危険性があります。
-
-
文脈に応じて
if not x:
も活用- 「値が存在しない、または空である」ことを広く判定したい場合は、
if not x:
と書くのがよりシンプルでPythonicです。 -
0
や空文字とNone
を厳密に区別したい場合はif x is None:
を使いましょう。
- 「値が存在しない、または空である」ことを広く判定したい場合は、
-
is
の多用は禁物-
is
は同一性の比較です。数値や文字列など、一般的なオブジェクトの値の比較には==
を使いましょう。
-
これらの原則を理解し、適切に使い分けることで、より良いPythonコードを書くことができます。