Mengapa `jika Tidak ada .__ eq __ (“ a ”)` tampaknya mengevaluasi ke True (tetapi tidak sepenuhnya)?

146

Jika Anda menjalankan pernyataan berikut dalam Python 3.7, itu akan (dari pengujian saya) dicetak b:

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

Namun, None.__eq__("a")dievaluasi menjadi NotImplemented.

Secara alami, "a".__eq__("a")mengevaluasi ke True, dan "b".__eq__("a")mengevaluasi ke False.

Saya awalnya menemukan ini ketika menguji nilai kembali suatu fungsi, tetapi tidak mengembalikan apa pun dalam kasus kedua - jadi, fungsi kembali None.

Apa yang terjadi di sini?

Arsitek AI
sumber

Jawaban:

178

Ini adalah contoh yang bagus tentang mengapa __dunder__metode tidak boleh digunakan secara langsung karena mereka sering tidak sesuai untuk operator mereka yang setara; Anda harus menggunakan ==operator sebagai gantinya untuk perbandingan kesetaraan, atau dalam kasus khusus ini, saat memeriksa None, gunakan is(lewati ke bagian bawah jawaban untuk informasi lebih lanjut).

Anda sudah selesai

None.__eq__('a')
# NotImplemented

Yang kembali NotImplementedkarena jenis yang dibandingkan berbeda. Pertimbangkan contoh lain di mana dua objek dengan jenis yang berbeda dibandingkan dengan cara ini, seperti 1dan 'a'. Melakukannya (1).__eq__('a')juga tidak benar, dan akan kembali NotImplemented. Cara yang tepat untuk membandingkan kedua nilai ini untuk persamaan adalah

1 == 'a'
# False

Yang terjadi di sini adalah

  1. Pertama, (1).__eq__('a')dicoba, yang mengembalikan NotImplemented. Ini menunjukkan bahwa operasi tidak didukung, jadi
  2. 'a'.__eq__(1)disebut, yang juga mengembalikan yang sama NotImplemented. Begitu,
  3. Objek diperlakukan seolah-olah tidak sama, dan Falsedikembalikan.

Berikut adalah MCVE kecil yang menyenangkan menggunakan beberapa kelas khusus untuk menggambarkan bagaimana ini terjadi:

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

Tentu saja, itu tidak menjelaskan mengapa operasi itu kembali benar. Ini karena NotImplementedsebenarnya adalah nilai kebenaran:

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

Sama dengan,

bool(NotImplemented)
# True

Untuk informasi lebih lanjut tentang nilai apa yang dianggap benar dan salah, lihat bagian dokumen tentang Pengujian Nilai Kebenaran , serta jawaban ini . Perlu dicatat di sini bahwa itu NotImplementedadalah kebenaran, tetapi itu akan menjadi cerita yang berbeda seandainya kelas mendefinisikan suatu __bool__atau __len__metode yang kembali Falseatau 0masing - masing.


Jika Anda menginginkan fungsional yang setara dengan ==operator, gunakan operator.eq:

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

Namun, seperti disebutkan sebelumnya, untuk skenario khusus ini , tempat Anda memeriksa None, gunakan is:

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

Setara fungsional ini menggunakan operator.is_:

operator.is_(var2, None)
# True

Noneadalah objek khusus, dan hanya 1 versi yang ada di memori pada suatu titik waktu. TKI, itu adalah singleton tunggal dari NoneTypekelas (tetapi objek yang sama dapat memiliki sejumlah referensi). The pedoman PEP8 membuat eksplisit:

Perbandingan dengan orang lajang seperti Noneharus selalu dilakukan dengan isatau is not, tidak pernah dengan operator kesetaraan.

Singkatnya, untuk lajang suka None, cek referensi dengan islebih tepat, meskipun keduanya ==dan isakan berfungsi dengan baik.

cs95
sumber
33

Hasil yang Anda lihat disebabkan oleh fakta itu

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

mengevaluasi ke NotImplemented, dan NotImplementednilai kebenaran didokumentasikan menjadiTrue :

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

Nilai khusus yang harus dikembalikan oleh metode khusus biner (misalnya __eq__(), __lt__(), __add__(), __rsub__(), dll) untuk menunjukkan bahwa operasi tidak dilaksanakan sehubungan dengan jenis lain; dapat dikembalikan dengan metode khusus biner di tempat (mis __imul__(). __iand__(), dll.) untuk tujuan yang sama. Nilai kebenarannya benar.

Jika Anda memanggil __eq()__metode secara manual daripada hanya menggunakan ==, Anda harus siap untuk menghadapi kemungkinan itu kembali NotImplementeddan bahwa nilai kebenarannya benar.

Mark Meyer
sumber
16

Seperti yang sudah Anda pikirkan, None.__eq__("a")akan NotImplementedtetapi jika Anda mencoba sesuatu seperti

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

hasilnya adalah

Iya

ini berarti bahwa nilai kebenaran NotImplemented true

Karenanya hasil dari pertanyaannya jelas:

None.__eq__(something) hasil panen NotImplemented

Dan bool(NotImplemented)mengevaluasi ke True

Begitu if None.__eq__("a")juga selalu Benar

Kanjiu
sumber
1

Mengapa?

Ia mengembalikan a NotImplemented, yeah:

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

Tetapi jika Anda melihat ini:

>>> bool(NotImplemented)
True
>>> 

NotImplemented sebenarnya adalah nilai kebenaran, jadi itu sebabnya ia kembali b , apa pun yang Trueakan berlalu, apa pun yang Falsetidak.

Bagaimana cara mengatasinya?

Anda harus memeriksa apakah itu True, jadi lebih curiga, seperti yang Anda lihat:

>>> NotImplemented == True
False
>>> 

Jadi, Anda akan melakukan:

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


>>> 

Dan seperti yang Anda lihat, itu tidak akan mengembalikan apa pun.

U10-Maju
sumber
1
jawaban yang paling jelas secara visual - v penambahan yang bermanfaat - terima kasih
scharfmn
1
:) "penambahan yang berharga" tidak cukup menangkap apa yang saya coba katakan (seperti yang Anda lihat) - mungkin "keunggulan terlambat" adalah apa yang saya inginkan - tepuk tangan
scharfmn
@ scharfmn ya? Saya ingin tahu apa pendapat Anda dari jawaban ini yang belum pernah dibahas sebelumnya.
cs95
entah bagaimana visual / ganti hal-hal di sini menambah kejelasan - demo lengkap
scharfmn
@ scharfmn ... Yang mana jawaban yang diterima juga telah meskipun permintaannya telah dihapus. Apakah Anda hanya memilih karena permintaan terminal malas dibiarkan?
cs95