`if None .__ eq __(“ a”)`がTrueと評価されるように見えるのですが(完全ではありません)。


146

Python 3.7で次のステートメントを実行すると、(私のテストから)印刷されますb

if None.__eq__("a"):
    print("b")

ただし、にNone.__eq__("a")評価されNotImplementedます。

当然、に"a".__eq__("a")評価されTrue、に"b".__eq__("a")評価されFalseます。

私は最初に関数の戻り値をテストしたときにこれを発見しましたが、2番目のケースでは何も返しませんでしたNone。つまり、関数は戻りました。

何が起きてる?

回答:


178

これは、__dunder__メソッドが同等の演算子の適切な置換ではない場合が多いため、メソッドを直接使用しない理由の良い例です。==等価比較には代わりに演算子を使用する必要があります。または、この特殊なケースでは、を確認するときNoneに使用しますis(詳細については、回答の最後までスキップしてください)。

あなたがやった

None.__eq__('a')
# NotImplemented

NotImplemented比較される型が異なるため、どちらが返されますか。以下のような、異なるタイプの2つのオブジェクトが、この方法で比較されている別の例を考える1'a'。行うこと(1).__eq__('a')も正しくなく、戻りNotImplementedます。これら2つの値が等しいかどうかを比較する正しい方法は、

1 == 'a'
# False

ここで何が起こるか

  1. 最初に、(1).__eq__('a')が返されますNotImplemented。これは、操作がサポートされていないことを示しているため、
  2. 'a'.__eq__(1)が呼び出され、これも同じを返しますNotImplemented。そう、
  3. オブジェクトは同じでないかのように扱われ、False返されます。

これがどのように発生するかを説明するためにいくつかのカスタムクラスを使用した素敵な小さなMCVEです。

class A:
    def __eq__(self, other):
        print('A.__eq__')
        return NotImplemented

class B:
    def __eq__(self, other):
        print('B.__eq__')
        return NotImplemented

class C:
    def __eq__(self, other):
        print('C.__eq__')
        return True

a = A()
b = B()
c = C()

print(a == b)
# A.__eq__
# B.__eq__
# False

print(a == c)
# A.__eq__
# C.__eq__
# True

print(c == a)
# C.__eq__
# True

もちろん、それは操作がtrueを返す理由を説明していません。これは、NotImplementedが実際には真実の値だからです。

bool(None.__eq__("a"))
# True

と同じ、

bool(NotImplemented)
# True

どの値が真偽であると見なされるかについての詳細は、真理値テストのドキュメントセクションとこの回答を参照してください。ここでNotImplemented真実であることは注目に値しますが、クラスが、またはをそれぞれ返す、__bool__または__len__メソッドを定義した場合は、別の話になります。False0


==演算子と同等の機能が必要な場合は、次を使用しますoperator.eq

import operator
operator.eq(1, 'a')
# False

ただし、前述したように、この特定のシナリオでは、次のことを確認しNoneますis

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

これに相当する機能は次を使用していoperator.is_ます:

operator.is_(var2, None)
# True

Noneは特別なオブジェクトであり、いつでもメモリに存在するバージョンは1つだけです。IOW、それはNoneTypeクラスの唯一のシングルトンです(ただし、同じオブジェクトは任意の数の参照を持つことができます)。PEP8ガイドラインは、これは明示的に行います。

のようなシングルトンとの比較Noneは、常にisまたはを 使用して行う必要がありますis not。等価演算子は使用しないでください。

要約すると、のようなシングルトンのためにNone、と参照チェックはis両方が、より適切である==isうまく動作します。


33

あなたが見ている結果は、

None.__eq__("a") # evaluates to NotImplemented

はに評価されNotImplementedNotImplementedの真理値は次のように文書化されますTrue

https://docs.python.org/3/library/constants.html

(例えば、バイナリ特別なメソッドによって返さなければならない特別な値__eq__()__lt__()__add__()__rsub__()、など)の動作は他のタイプに対して実装されていないことを示すために。インプレースバイナリ特殊な方法(例えばによって返されることがあり__imul__()__iand__()同じ目的のために、など)。その真理値は真実です。

__eq()__だけを使用するのではなく、手動でメソッドを呼び出す場合は、メソッド==が返す可能性とNotImplementedその真の値がtrue である可能性に対処する準備をする必要があります。


16

あなたがすでに考えたNone.__eq__("a")ように、NotImplementedしかしあなたが

if NotImplemented:
    print("Yes")
else:
    print("No")

結果は

はい

これは、 NotImplemented true

したがって、質問の結果は明白です。

None.__eq__(something) 収量 NotImplemented

そしてbool(NotImplemented)真と評価する

だから、if None.__eq__("a")常に真であります


1

どうして?

それはNotImplementedええを返します:

>>> None.__eq__('a')
NotImplemented
>>> 

しかし、これを見ると:

>>> bool(NotImplemented)
True
>>> 

NotImplementedtruthy値が、実際にそれを返す理由のようにb、あるものTrueで通過し、何もFalseないでしょう。

それを解決するには?

それがTrueであるかどうかを確認する必要があるため、次のように、より疑わしいものにします。

>>> NotImplemented == True
False
>>> 

だからあなたはそうするでしょう:

>>> if None.__eq__('a') == True:
    print('b')


>>> 

ご覧のとおり、何も返されません。


1
最も視覚的に明確な答え-v価値のある追加-ありがとうございました
scharfmn

1
:)「価値のある追加」は、私が言おうとしていた内容を完全には捉えていません(ご覧のとおり)-多分、「遅ればせながら優秀」が私が望んでいたことです
乾杯-scharfmn

@scharfmnはい?この回答で、これまでに取り上げられていないことについて、あなたが何を考えているかを知りたいと思います。
cs95

どういうわけか、ここでの視覚的/複製物は明確さを追加します-完全なデモ
scharfmn

@scharfmn ...プロンプトが削除されているにもかかわらず、受け入れられた回答。ターミナルプロンプトが遅延して残されたという理由だけで賛成票を投じましたか?
cs95
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.