ทำไม "ถ้าไม่มี. __ 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

ผมเริ่มค้นพบนี้เมื่อการทดสอบค่าตอบแทนของฟังก์ชั่น แต่ไม่ได้กลับอะไรในกรณีที่สอง - Noneเพื่อฟังก์ชั่นที่ส่งกลับ

เกิดอะไรขึ้นที่นี่?

คำตอบ:


178

นี่เป็นตัวอย่างที่ดีว่าทำไม__dunder__ไม่ควรใช้วิธีการโดยตรงเนื่องจากเป็นวิธีที่ไม่เหมาะสมในการทดแทนตัวดำเนินการที่เทียบเท่า คุณควรใช้==โอเปอเรเตอร์แทนการเปรียบเทียบความเท่าเทียมกันหรือในกรณีพิเศษนี้เมื่อตรวจสอบNoneใช้is(ข้ามไปที่ด้านล่างของคำตอบสำหรับข้อมูลเพิ่มเติม)

คุณทำไปแล้ว

None.__eq__('a')
# NotImplemented

ซึ่งผลตอบแทนNotImplementedตั้งแต่ประเภทการเปรียบเทียบแตกต่างกัน พิจารณาอีกตัวอย่างหนึ่งที่วัตถุสองชนิดที่แตกต่างกันถูกเปรียบเทียบแบบนี้เช่นและ1 'a'การทำเช่นนี้ยังไม่ถูกต้องและจะกลับมา(1).__eq__('a') NotImplementedวิธีที่ถูกต้องในการเปรียบเทียบค่าทั้งสองนี้เพื่อความเท่าเทียมกันคือ

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

แน่นอนว่าไม่ได้อธิบายว่าทำไมการดำเนินการจึงกลับเป็นจริง เพราะนี่คือNotImplementedคุณค่าที่แท้จริง:

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

เหมือนกับ,

bool(NotImplemented)
# True

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับค่าใดที่ถือว่าเป็นความจริงและเป็นเท็จให้ดูส่วนเอกสารเกี่ยวกับการทดสอบความจริงและคำตอบนี้ มันคุ้มค่าที่จะสังเกตว่านี่NotImplementedคือความจริง แต่มันจะเป็นเรื่องที่แตกต่างกันเมื่อชั้นเรียนกำหนด__bool__หรือ__len__วิธีการที่กลับมาFalseหรือ0ตามลำดับ


หากคุณต้องการฟังก์ชั่นที่เทียบเท่ากับ==ผู้ปฏิบัติงานให้ใช้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ไม่ควรใช้ตัวดำเนินการที่เท่าเทียมกัน

โดยสรุปสำหรับ singletons เช่นNoneการตรวจสอบการอ้างอิงมีisความเหมาะสมมากขึ้นแม้ว่าทั้งสอง==และisจะทำงานได้ดี


33

ผลลัพธ์ที่คุณเห็นมีสาเหตุมาจากความจริงที่ว่า

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

ประเมินNotImplementedและNotImplementedมีการบันทึกค่าความจริงเป็นTrue:

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

ค่าพิเศษที่ควรจะส่งกลับโดยวิธีพิเศษไบนารี (เช่น__eq__(), __lt__(), __add__(), __rsub__()ฯลฯ ) เพื่อแสดงให้เห็นว่าการดำเนินการไม่ได้ดำเนินการที่เกี่ยวกับประเภทอื่น ๆ ; อาจจะกลับมาโดยวิธีการไบนารีพิเศษในสถานที่ (เช่น__imul__(), __iand__()ฯลฯ ) เพื่อจุดประสงค์เดียวกัน คุณค่าของความจริงนั้นเป็นจริง

หากคุณเรียกใช้__eq()__วิธีการด้วยตนเองแทนที่จะใช้เพียงอย่างเดียว==คุณจำเป็นต้องเตรียมรับมือกับความเป็นไปได้ที่จะส่งคืนNotImplementedและมูลค่าความจริงของมันนั้นเป็นจริง


16

ตามที่คุณคิดแล้วNone.__eq__("a")ประเมินNotImplementedว่าถ้าคุณลองอะไรเช่น

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

ผลลัพธ์ที่ได้คือ

ใช่

นี่หมายความว่าค่าความจริงของ NotImplemented true

ดังนั้นผลลัพธ์ของคำถามนั้นชัดเจน:

None.__eq__(something) อัตราผลตอบแทน NotImplemented

และbool(NotImplemented)ประเมินผลเป็น True

ดังนั้นif None.__eq__("a")จะเป็นจริงเสมอ


1

ทำไม?

มันจะส่งคืน a NotImplemented, ใช่:

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

แต่ถ้าคุณดูที่:

>>> bool(NotImplemented)
True
>>> 

NotImplementedเป็นค่าความจริงดังนั้นจึงส่งคืนbสิ่งใดก็ตามที่Trueจะผ่านไปสิ่งที่Falseไม่ผ่าน

วิธีแก้ปัญหา

คุณต้องตรวจสอบว่ามันเป็นTrueดังนั้นจึงน่าสงสัยมากขึ้นตามที่คุณเห็น:

>>> NotImplemented == True
False
>>> 

ดังนั้นคุณจะทำ:

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


>>> 

และอย่างที่คุณเห็นมันจะไม่ส่งคืนสิ่งใด


1
คำตอบที่ชัดเจนมากที่สุด - การเพิ่มที่คุ้มค่า - ขอบคุณ
scharfmn

1
:)“ การเพิ่มความคุ้มค่า” ไม่ได้จับสิ่งที่ฉันพยายามพูดมากเท่าที่คุณต้องการดู - บางที "ความล่าช้าเลิศ" เป็นสิ่งที่ฉันต้องการ - ไชโย
scharfmn

@scharfmn ใช่ไหม ฉันอยากรู้ว่าคุณคิดอย่างไรกับคำตอบนี้ซึ่งยังไม่ได้กล่าวถึงมาก่อน
cs95

ยัง
ไงก็ตาม

@scharfmn ... คำตอบที่ยอมรับก็เช่นกันแม้ว่าข้อความนั้นจะถูกลบไปแล้ว คุณไม่ได้ลงคะแนนเพียงอย่างเดียวเพราะเทอร์มินัลแจ้งเตือนถูกทิ้งไว้อย่างเกียจคร้าน
cs95
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.