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?

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.


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.


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


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.


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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.