เหตุใดนิพจน์ 0 <0 == 0 จึงส่งคืน False ใน Python


138

เมื่อมองเข้าไปใน Queue.py ใน Python 2.6 ฉันพบโครงสร้างนี้ซึ่งฉันพบว่าแปลกเล็กน้อย:

def full(self):
    """Return True if the queue is full, False otherwise
    (not reliable!)."""
    self.mutex.acquire()
    n = 0 < self.maxsize == self._qsize()
    self.mutex.release()
    return n

ถ้าmaxsizeเป็น 0 คิวจะไม่เต็ม

คำถามของฉันคือมันทำงานอย่างไรสำหรับกรณีนี้? วิธี0 < 0 == 0ถือว่าเป็นเท็จ?

>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True

0 <True เท่ากับ False ใน python หรือไม่?
Marino Šimić

3
@Marino Šimić: จากตัวอย่างที่สองที่แสดงในคำถามของ OP >>> (0) < (0 == 0)เห็นได้ชัดว่าไม่ใช่
martineau

3
เหตุผลหนึ่งที่คุณไม่ควรเขียนโค้ดเหมือนn = 0 < self.maxsize == self._qsize()ในตอนแรกไม่ว่าจะเป็นภาษาใด ๆ หากดวงตาของคุณต้องพุ่งไปมาข้ามเส้นหลาย ๆ ครั้งเพื่อดูว่าเกิดอะไรขึ้นนั่นไม่ใช่เส้นที่เขียนได้ดี เพียงแค่แยกออกเป็นหลายบรรทัด
BlueRaja - Danny Pflughoeft

2
@Blue: ฉันเห็นด้วยกับการไม่เขียนการเปรียบเทียบแบบนั้น แต่การแยกมันออกเป็นบรรทัดแยกกันจะเป็นการเปรียบเทียบกันเล็กน้อยสำหรับการเปรียบเทียบสองครั้ง ฉันหวังว่าคุณจะหมายถึงแยกมันออกเป็นการเปรียบเทียบแยกกัน ;)
Jeff Mercado

2
@Blue: ฉันไม่ได้เขียนมันอยู่ใน Python 2.6 ฉันแค่พยายามเข้าใจว่าเกิดอะไรขึ้น
Marcelo Santos

คำตอบ:


114

ฉันเชื่อว่า Python มีการจัดการกรณีพิเศษสำหรับลำดับของตัวดำเนินการเชิงสัมพันธ์เพื่อให้การเปรียบเทียบช่วงง่ายต่อการแสดง มันเป็นเรื่องที่ดีกว่ามากที่จะสามารถที่จะพูดมากไปกว่าการพูด0 < x <= 5(0 < x) and (x <= 5)

เหล่านี้เรียกว่ารถที่ถูกล่ามโซ่ และนั่นคือลิงค์ไปยังเอกสารสำหรับพวกเขา

ในกรณีอื่น ๆ ที่คุณพูดถึงวงเล็บจะบังคับให้ใช้ตัวดำเนินการเชิงสัมพันธ์ตัวหนึ่งก่อนอีกตัวจึงไม่ใช่การเปรียบเทียบแบบผูกมัดอีกต่อไป และตั้งแต่นั้นเป็นต้นมาTrueและFalseมีค่าเป็นจำนวนเต็มคุณจะได้รับคำตอบจากเวอร์ชันในวงเล็บ


มันน่าสนใจที่จะลองใช้การเปรียบเทียบเหล่านี้และระบุ int () และ bool () ฉันตระหนักว่าบูล () ของใด ๆ ที่ไม่ใช่ศูนย์คือ 1 เดาว่าฉันไม่เคยแม้แต่จะพยายามระบุสิ่งอื่นใดเลยนอกจากบูล (0) หรือบูล (1) ก่อนการทดลองทางความคิดนี้
j_syk

คุณตั้งใจจะลิงก์ไปยังส่วนนี้แทนหรือไม่ docs.python.org/2/reference/expressions.html#comparisons
tavnab

@tavnab - ครับ ฉันจะพยายามอย่าลืมแก้ไข ฉันจะตรวจสอบประวัติการแก้ไขด้วย ดูเหมือนจะไม่ใช่ความผิดพลาดที่ฉันจะทำ 😕
มักง่าย

42

เพราะ

(0 < 0) and (0 == 0)

คือFalse. คุณสามารถเชื่อมโยงตัวดำเนินการเปรียบเทียบเข้าด้วยกันและจะขยายออกไปสู่การเปรียบเทียบแบบคู่โดยอัตโนมัติ


แก้ไข - คำชี้แจงเกี่ยวกับ True และ False ใน Python

ใน Python TrueและFalseเป็นเพียงอินสแตนซ์boolซึ่งเป็นคลาสย่อยของint. กล่าวอีกนัยหนึ่งTrueจริงๆเป็นเพียง 1

ประเด็นคือคุณสามารถใช้ผลลัพธ์ของการเปรียบเทียบบูลีนเหมือนกับจำนวนเต็ม สิ่งนี้นำไปสู่ความสับสนเช่น

>>> (1==1)+(1==1)
2
>>> (2<1)<1
True

แต่สิ่งเหล่านี้จะเกิดขึ้นก็ต่อเมื่อคุณวงเล็บการเปรียบเทียบเพื่อให้ได้รับการประเมินก่อน มิฉะนั้น Python จะขยายตัวดำเนินการเปรียบเทียบออก


2
ฉันเห็นการใช้ที่น่าสนใจสำหรับค่าบูลีนที่ใช้เป็นจำนวนเต็มเมื่อวานนี้ นิพจน์'success' if result_code == 0 else 'failure'สามารถเขียนใหม่ได้('error', 'success')[result_code == 0]ก่อนหน้านี้ฉันไม่เคยเห็นบูลีนที่ใช้เพื่อเลือกรายการในรายการ / ทูเพิล
Andrew Clark

มีการเพิ่ม 'bool' ในช่วงเวลาประมาณ Python 2.2
MRAB

18

พฤติกรรมแปลก ๆ ที่คุณประสบมาจากความสามารถของงูเหลือมในการล่ามโซ่ เนื่องจากพบว่า 0 ไม่น้อยกว่า 0 จึงตัดสินใจว่านิพจน์ทั้งหมดจะประเมินเป็นเท็จ ทันทีที่คุณแยกสิ่งนี้ออกเป็นเงื่อนไขแยกต่างหากคุณกำลังเปลี่ยนฟังก์ชันการทำงาน ในขั้นต้นเป็นการทดสอบโดยพื้นฐานa < b && b == cสำหรับคำสั่งเดิมของa < b == cคุณ

ตัวอย่างอื่น:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True

1
OMG a < b && b == cเหมือนกับa < b == cOO
Kiril Kirov

9
>>> 0 < 0 == 0
False

นี่คือการเปรียบเทียบแบบล่ามโซ่ มันจะคืนค่าเป็นจริงหากการเปรียบเทียบแต่ละคู่ในทางกลับกันเป็นจริง มันเทียบเท่ากับ(0 < 0) and (0 == 0)

>>> (0) < (0 == 0)
True

ซึ่งเทียบเท่ากับ0 < Trueที่ประเมินเป็น True

>>> (0 < 0) == 0
True

ซึ่งเทียบเท่ากับFalse == 0ที่ประเมินเป็น True

>>> 0 < (0 == 0)
True

เทียบเท่ากับ0 < Trueที่ข้างต้นประเมินเป็น True


7

กำลังมองหาที่ถอดชิ้นส่วน (ไบท์รหัส) ก็เป็นที่ชัดเจนว่าทำไมเป็น0 < 0 == 0False

นี่คือการวิเคราะห์นิพจน์นี้:

>>>import dis

>>>def f():
...    0 < 0 == 0

>>>dis.dis(f)
  2      0 LOAD_CONST               1 (0)
         3 LOAD_CONST               1 (0)
         6 DUP_TOP
         7 ROT_THREE
         8 COMPARE_OP               0 (<)
        11 JUMP_IF_FALSE_OR_POP    23
        14 LOAD_CONST               1 (0)
        17 COMPARE_OP               2 (==)
        20 JUMP_FORWARD             2 (to 25)
   >>   23 ROT_TWO
        24 POP_TOP
   >>   25 POP_TOP
        26 LOAD_CONST               0 (None)
        29 RETURN_VALUE

ข้อสังเกตบรรทัดที่ 0-8: เส้นเหล่านี้ตรวจสอบว่า0 < 0สิ่งใดกลับFalseเข้าสู่ python stack อย่างชัดเจน

ตอนนี้ให้สังเกตบรรทัดที่ 11: JUMP_IF_FALSE_OR_POP 23 ซึ่งหมายความว่าหาก0 < 0ผลตอบแทนFalseให้ข้ามไปที่บรรทัด 23

ทีนี้0 < 0ก็คือFalseดังนั้นจึงใช้การกระโดดซึ่งจะออกจากสแต็กโดยมีFalseค่าที่ส่งคืนสำหรับนิพจน์ทั้งหมด0 < 0 == 0แม้ว่า== 0ส่วนนั้นจะไม่ได้ตรวจสอบด้วยซ้ำ

ดังนั้นเพื่อสรุปคำตอบก็เหมือนกับที่กล่าวไว้ในคำตอบอื่น ๆ ของคำถามนี้ 0 < 0 == 0มีความหมายพิเศษ คอมไพเลอร์ประเมินสิ่งนี้เป็นสองเงื่อนไข: 0 < 0และ0 == 0. เช่นเดียวกับนิพจน์บูลีนที่ซับซ้อนใด ๆ ที่มีandระหว่างพวกเขาหากครั้งแรกล้มเหลวก็จะไม่ได้ตรวจสอบนิพจน์ที่สอง

หวังว่าสิ่งนี้จะทำให้กระจ่างขึ้นเล็กน้อยและฉันหวังเป็นอย่างยิ่งว่าวิธีที่ฉันใช้ในการวิเคราะห์พฤติกรรมที่ไม่คาดคิดนี้จะกระตุ้นให้ผู้อื่นลองทำเช่นเดียวกันในอนาคต


มันจะง่ายกว่าไหมที่จะทำงานจากข้อมูลจำเพาะแทนที่จะเป็นวิศวกรรมย้อนกลับการใช้งานโดยเฉพาะ
David Heffernan

ไม่นั่นคือคำตอบสั้น ๆ ฉันเชื่อว่ามันขึ้นอยู่กับบุคลิกของคุณ หากคุณเกี่ยวข้องกับมุมมอง "กล่องดำ" และต้องการรับคำตอบจากข้อกำหนดและเอกสารคำตอบนี้จะทำให้คุณสับสนเท่านั้น หากคุณชอบที่จะขุดค้นและเปิดเผยสิ่งที่อยู่ภายในคำตอบนี้เหมาะสำหรับคุณ ข้อสังเกตของคุณว่าการทำวิศวกรรมย้อนกลับนี้เกี่ยวข้องกับการนำไปใช้งานเฉพาะอย่างเดียวเท่านั้นที่ถูกต้องและต้องได้รับการชี้ให้เห็น แต่นั่นไม่ใช่ประเด็นของคำตอบนี้ เป็นการสาธิตวิธีง่ายๆในการดู "ใต้ประทุน" สำหรับคนที่อยากรู้อยากเห็น
SATA

1
ปัญหาเกี่ยวกับวิศวกรรมย้อนกลับคือการขาดอำนาจในการทำนาย ไม่ใช่วิธีการเรียนรู้ภาษาใหม่
David Heffernan

อีกสิ่งหนึ่งที่ฉันต้องเพิ่มคือข้อกำหนดและเอกสารอาจไม่สมบูรณ์เสมอไปและในกรณีส่วนใหญ่จะไม่ให้คำตอบสำหรับกรณีเฉพาะดังกล่าว จากนั้นฉันเชื่อว่าเราต้องไม่กลัวที่จะสำรวจตรวจสอบและเจาะลึกลงไปมากเท่าที่จำเป็นเพื่อให้ได้คำตอบ
SATA

2

ดังที่กล่าวมาแล้วx comparison_operator y comparison_operator zคือน้ำตาลสังเคราะห์สำหรับ(x comparison_operator y) and (y comparison_operator z)โบนัสที่ y ได้รับการประเมินเพียงครั้งเดียว

ดังนั้นการแสดงออกของคุณ0 < 0 == 0เป็นจริง(0 < 0) and (0 == 0)ซึ่งประเมินซึ่งเป็นเพียงFalse and TrueFalse


2

บางทีข้อความที่ตัดตอนมาจากเอกสารนี้อาจช่วยได้:

วิธีเหล่านี้เรียกว่าวิธีการ "การเปรียบเทียบที่สมบูรณ์" และเรียกว่าตัวดำเนินการเปรียบเทียบตามความต้องการ__cmp__()ด้านล่าง จดหมายระหว่างสัญลักษณ์ประกอบการและชื่อวิธีการดังนี้x<yสาย x.__lt__(y), x<=yสายx.__le__(y), x==yสายx.__eq__(y), x!=yและx<>y การโทรx.__ne__(y), x>yสาย x.__gt__(y)และโทรออกx>=y x.__ge__(y)

วิธีการเปรียบเทียบที่สมบูรณ์อาจส่งคืนซิงเกิลตันNotImplementedหากไม่ใช้การดำเนินการสำหรับคู่ของอาร์กิวเมนต์ที่กำหนด ตามแบบแผนFalseและTrueจะถูกส่งกลับเพื่อการเปรียบเทียบที่ประสบความสำเร็จ อย่างไรก็ตามวิธีการเหล่านี้สามารถส่งคืนค่าใด ๆ ดังนั้นหากใช้ตัวดำเนินการเปรียบเทียบในบริบทบูลีน (เช่นในเงื่อนไขของคำสั่ง if) Python จะเรียกbool()ค่าเพื่อตรวจสอบว่าผลลัพธ์เป็นจริงหรือเท็จ

ไม่มีความสัมพันธ์โดยนัยระหว่างตัวดำเนินการเปรียบเทียบ ความจริงx==yไม่ได้หมายความว่าx!=y เป็นเท็จ ดังนั้นเมื่อกำหนด __eq__()ควรกำหนดด้วย__ne__()เพื่อให้ตัวดำเนินการทำงานตามที่คาดไว้ ดูย่อหน้า__hash__()สำหรับหมายเหตุที่สำคัญบางประการเกี่ยวกับการสร้างวัตถุที่แฮชได้ซึ่งสนับสนุนการดำเนินการเปรียบเทียบแบบกำหนดเองและสามารถใช้เป็นคีย์พจนานุกรมได้

ไม่มีเวอร์ชันแลกเปลี่ยนอาร์กิวเมนต์ของวิธีการเหล่านี้ (ที่จะใช้เมื่ออาร์กิวเมนต์ด้านซ้ายไม่สนับสนุนการดำเนินการ แต่อาร์กิวเมนต์ด้านขวาทำ) ค่อนข้าง__lt__()และ__gt__() เป็นภาพสะท้อนของกันและกัน__le__() และ__ge__()มีการสะท้อนของกันและกันและ__eq__()และ__ne__() เป็นภาพสะท้อนของตัวเอง

ข้อโต้แย้งของวิธีการเปรียบเทียบที่สมบูรณ์จะไม่ถูกบังคับ

นี่เป็นการเปรียบเทียบ แต่เนื่องจากคุณกำลังเชื่อมโยงการเปรียบเทียบคุณควรรู้ว่า:

การเปรียบเทียบสามารถผูกมัดได้ตามอำเภอใจเช่นx < y <= zเทียบเท่ากับx < y and y <= zยกเว้นว่า y จะได้รับการประเมินเพียงครั้งเดียว (แต่ในทั้งสองกรณี z จะไม่ได้รับการประเมินเลยเมื่อ x <y พบว่าเป็นเท็จ)

โดยปกติถ้า a, b, c, ... , y, z เป็นนิพจน์และ op1, op2, ... , opN เป็นตัวดำเนินการเปรียบเทียบดังนั้น op1 b op2 c ... y opN z จะเทียบเท่ากับ op1 b และ b op2 c และ ... y opN z ยกเว้นว่าแต่ละนิพจน์จะได้รับการประเมินไม่เกินหนึ่งครั้ง


1

นี่คือความรุ่งโรจน์ของมัน

>>> class showme(object):
...   def __init__(self, name, value):
...     self.name, self.value = name, value
...   def __repr__(self):
...     return "<showme %s:%s>" % (self.name, self.value)
...   def __cmp__(self, other):
...     print "cmp(%r, %r)" % (self, other)
...     if type(other) == showme:
...       return cmp(self.value, other.value)
...     else:
...       return cmp(self.value, other)
... 
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>> 

0

ฉันคิดว่า Python กำลังทำมันแปลก ๆ ระหว่างเวทมนตร์ เหมือนกับ1 < 2 < 3หมายถึง 2 อยู่ระหว่าง 1 ถึง 3

ในกรณีนี้ฉันคิดว่ากำลังทำ [กลาง 0] มากกว่า [ซ้าย 0] และเท่ากับ [ขวา 0] กลาง 0 ไม่มากกว่า 0 ซ้ายดังนั้นจึงประเมินเป็นเท็จ

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