ทำไม "ถ้าไม่ใช่ someobj:" ดีกว่า "ถ้า someobj == ไม่มี:" ใน Python


127

ฉันเคยเห็นตัวอย่างของรหัสเช่นนี้:

if not someobj:
    #do something

แต่ฉันสงสัยว่าทำไมไม่ทำ:

if someobj == None:
    #do something

มีความแตกต่างหรือไม่? คนหนึ่งได้เปรียบกว่าอีกคนหนึ่งหรือเปล่า?


13
โดยทั่วไป 'someobj is None' จะดีกว่า 'someobj == ไม่มี'
Aaron Maenpaa

pseudocode ถ้า X ไม่ใช่ None? หรือX != None?
Charlie Parker

คำถามนี้เป็น verbose มากกว่าคนอื่น ๆ ... รุ่งโรจน์ ...
alamin

คำตอบ:


187

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

  1. ถ้าวัตถุนั้นมี__nonzero__วิธีพิเศษ (เช่นเดียวกับในตัวตัวเลขintและfloat) มันเรียกวิธีนี้ โดยจะต้องส่งคืนboolค่าที่ใช้โดยตรงหรือintค่าที่พิจารณาFalseว่าเท่ากับศูนย์

  2. มิฉะนั้นถ้าวัตถุที่มี__len__วิธีการพิเศษ (เช่นการทำภาชนะสร้างอินlist, dict, set, tuple, ... ) มันเรียกวิธีการนี้เมื่อพิจารณาจากภาชนะFalseถ้ามันเป็นที่ว่างเปล่า (ความยาวเป็นศูนย์)

  3. มิฉะนั้นวัตถุถือว่าTrueเว้นแต่เป็นซึ่งในกรณีนี้ก็ถือว่าNoneFalse

Noneในการทดสอบที่สองวัตถุที่ถูกเมื่อเทียบเพื่อความเท่าเทียมกันในการ ที่นี่เราจะถามวัตถุ "คุณเท่ากับค่าอื่นนี้หรือไม่" สิ่งนี้ทำได้โดยใช้อัลกอริทึมต่อไปนี้:

  1. ถ้าวัตถุมี__eq__วิธีการที่เรียกว่าและค่าตอบแทนจะถูกแปลงไปแล้วคุ้มค่าและใช้ในการกำหนดผลที่ได้จากboolif

  2. มิฉะนั้นถ้าวัตถุนั้นมี__cmp__วิธีการก็จะถูกเรียกว่า ฟังก์ชั่นนี้จะต้องกลับintแสดงให้เห็นคำสั่งของสองวัตถุ ( -1ถ้าself < other, 0ถ้าself == other, +1ถ้าself > other)

  3. มิฉะนั้นวัตถุจะถูกนำมาเปรียบเทียบเพื่อระบุตัวตน (เช่นพวกเขาจะอ้างอิงกับวัตถุเดียวกันที่สามารถทดสอบได้โดยisผู้ปฏิบัติงาน)

มีการทดสอบอื่นที่เป็นไปได้โดยใช้isโอเปอเรเตอร์ เราจะถามวัตถุ "คุณเป็นวัตถุเฉพาะหรือไม่"

โดยทั่วไปฉันขอแนะนำให้ใช้การทดสอบครั้งแรกกับค่าที่ไม่ใช่ตัวเลขเพื่อใช้การทดสอบเพื่อความเท่าเทียมกันเมื่อคุณต้องการเปรียบเทียบวัตถุที่มีลักษณะเดียวกัน (สองสาย, สองหมายเลข, ... ) และตรวจสอบตัวตนเฉพาะเมื่อ ใช้ค่า Sentinel ( Noneหมายถึงไม่ได้เริ่มต้นสำหรับเขตข้อมูลสมาชิกสำหรับตัวอย่างหรือเมื่อใช้getattrหรือ__getitem__วิธีการ)

เพื่อสรุปเรามี:

>>> class A(object):
...    def __repr__(self):
...        return 'A()'
...    def __nonzero__(self):
...        return False

>>> class B(object):
...    def __repr__(self):
...        return 'B()'
...    def __len__(self):
...        return 0

>>> class C(object):
...    def __repr__(self):
...        return 'C()'
...    def __cmp__(self, other):
...        return 0

>>> class D(object):
...    def __repr__(self):
...        return 'D()'
...    def __eq__(self, other):
...        return True

>>> for obj in ['', (), [], {}, 0, 0., A(), B(), C(), D(), None]:
...     print '%4s: bool(obj) -> %5s, obj == None -> %5s, obj is None -> %5s' % \
...         (repr(obj), bool(obj), obj == None, obj is None)
  '': bool(obj) -> False, obj == None -> False, obj is None -> False
  (): bool(obj) -> False, obj == None -> False, obj is None -> False
  []: bool(obj) -> False, obj == None -> False, obj is None -> False
  {}: bool(obj) -> False, obj == None -> False, obj is None -> False
   0: bool(obj) -> False, obj == None -> False, obj is None -> False
 0.0: bool(obj) -> False, obj == None -> False, obj is None -> False
 A(): bool(obj) -> False, obj == None -> False, obj is None -> False
 B(): bool(obj) -> False, obj == None -> False, obj is None -> False
 C(): bool(obj) ->  True, obj == None ->  True, obj is None -> False
 D(): bool(obj) ->  True, obj == None ->  True, obj is None -> False
None: bool(obj) -> False, obj == None ->  True, obj is None ->  True

4
แม้ว่าเทคนิคที่ถูกต้องนี้ไม่ได้อธิบายว่า tuples, รายการ, dicts, STRs, unicodes, ints ลอย ฯลฯ มีภัณฑ์ มันเป็นเรื่องธรรมดามากที่จะพึ่งพาค่าความจริงของประเภทในตัวมากกว่าที่จะพึ่งพาวิธีการที่ไม่ใช่ศูนย์แบบกำหนดเอง
ddaa

50

อันที่จริงแล้วทั้งสองวิธีปฏิบัติที่ไม่ดี กาลครั้งหนึ่งมันก็โอเคที่จะถือว่าไม่มีทางและไม่มีเท็จเหมือนกัน อย่างไรก็ตามเนื่องจาก Python 2.2 นี่ไม่ใช่นโยบายที่ดีที่สุด

ครั้งแรกเมื่อคุณทำการทดสอบif xหรือif not xชนิด Python จะต้องแปลงxเป็นบูลีนโดยปริยาย กฎสำหรับboolฟังก์ชั่นอธิบายแพของสิ่งต่าง ๆ ซึ่งเป็นเท็จ ทุกสิ่งทุกอย่างเป็นจริง หากค่าของ x ไม่บูลีนเริ่มต้นอย่างเหมาะสมการแปลงโดยนัยนี้ไม่ใช่วิธีที่ชัดเจนที่สุดในการพูดสิ่งต่าง ๆ

ก่อน Python 2.2 ไม่มีฟังก์ชั่นบูลจึงชัดเจนน้อยกว่า

== Noneประการที่สองคุณไม่ควรจริงๆทดสอบด้วย คุณควรใช้และis Noneis not None

ดู PEP 8 คู่มือสไตล์สำหรับงูหลามรหัส

- Comparisons to singletons like None should always be done with
  'is' or 'is not', never the equality operators.

  Also, beware of writing "if x" when you really mean "if x is not None"
  -- e.g. when testing whether a variable or argument that defaults to
  None was set to some other value.  The other value might have a type
  (such as a container) that could be false in a boolean context!

มีกี่ตันเดี่ยว? ห้า: None, True, False, และNotImplemented Ellipsisเนื่องจากคุณไม่น่าจะใช้จริง ๆNotImplementedหรือEllipsisและคุณจะไม่มีทางพูดif x is True(เพราะif xมีความชัดเจนมากขึ้น) คุณจะต้องทดสอบNoneเท่านั้น


3
รูปแบบที่สองคือการปฏิบัติที่ไม่เลว PEP 8 แนะนำให้ใช้ถ้า x เป็นครั้งที่สอง ลำดับแรกสำหรับลำดับ (แทนที่จะใช้ len) และจากนั้นสำหรับ True และ False (แทนที่จะใช้เป็น) จริง ๆ แล้วรหัส Python ทั้งหมดที่ฉันเห็นจะใช้ถ้า x และไม่ใช่ x
Antti Rasinen

35

เพราะNoneไม่ใช่สิ่งเดียวที่ถือว่าเป็นเท็จ

if not False:
    print "False is false."
if not 0:
    print "0 is false."
if not []:
    print "An empty list is false."
if not ():
    print "An empty tuple is false."
if not {}:
    print "An empty dict is false."
if not "":
    print "An empty string is false."

False, 0, (), [], {}และ""มีความแตกต่างกันทั้งหมดจากNoneดังนั้นโค้ดสองของคุณจะไม่เทียบเท่า

นอกจากนี้ให้พิจารณาสิ่งต่อไปนี้:

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

if object:คือไม่ได้ตรวจสอบความเท่าเทียมกัน 0, (), [], None, {}ฯลฯมีทั้งหมดแตกต่างจากคนอื่น ๆ แต่พวกเขาทั้งหมดประเมินการเท็จ

นี่คือ "เวทมนต์" ที่อยู่เบื้องหลังการแสดงออกทางลัดเช่น:

foo = bar and spam or eggs

ซึ่งจดชวเลขสำหรับ:

if bar:
    foo = spam
else:
    foo = eggs

แม้ว่าคุณควรเขียนจริง ๆ :

foo = spam if bar else egg

เกี่ยวกับคำถามสุดท้ายของคุณพวกเขาเทียบเท่า
Sylvain Defresne

และทั้งคู่ไม่ถูกต้องเพราะ "" เป็นเท็จ รายการที่สองควรอ่าน '("",) หรือ ("s",)' อย่างไรก็ตามไพ ธ อนรุ่นใหม่มีโอเปอร์เรเตอร์ที่เหมาะสม แฮ็คที่มีข้อผิดพลาดนี้ควรถูกเนรเทศ
ddaa

4

PEP ที่ 8 - คู่มือสไตล์สำหรับงูหลามรหัสแนะนำให้ใช้เป็นหรือไม่ถ้าคุณกำลังทดสอบไม่มี-Ness

- Comparisons to singletons like None should always be done with
  'is' or 'is not', never the equality operators.

ในทางกลับกันหากคุณกำลังทดสอบมากกว่า None-ness คุณควรใช้โอเปอเรเตอร์บูลีน


3

หากคุณถาม

if not spam:
    print "Sorry. No SPAM."

__nonzero__วิธีการสแปมได้รับเรียกว่า จากคู่มือ Python:

__nonzero__ ( ตัวเอง ) ถูกเรียกให้ทำการทดสอบค่าความจริงและบูลการดำเนินการในตัว (); ควรกลับเท็จหรือจริงหรือจำนวนเต็มเทียบเท่า 0 หรือ 1 เมื่อไม่ได้กำหนดวิธีนี้ __len __ () จะถูกเรียกถ้ามันถูกกำหนด (ดูด้านล่าง) หากคลาสไม่ได้กำหนด __len __ () หรือ __nonzero __ () คลาสทั้งหมดจะถือว่าเป็นจริง

หากคุณถาม

if spam == None:
    print "Sorry. No SPAM here either."

__eq__วิธีการสแปมได้รับการเรียกว่ามีอาร์กิวเมนต์ไม่มี

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความเป็นไปได้ในการปรับแต่งดูที่ Python documenation ที่https://docs.python.org/reference/datamodel.html#basic-customization


2

การเปรียบเทียบทั้งสองนี้มีวัตถุประสงค์ที่แตกต่างกัน อดีตตรวจสอบหาค่าบูลีนของบางอย่างที่สองตรวจสอบตัวตนด้วยไม่มีค่า


1
เพื่อให้ถูกต้องอย่างแน่นอนการตรวจสอบครั้งที่สองสำหรับ -equality- ด้วยค่า None ... "someobj is None" เป็นการตรวจสอบตัวตน
Matthew Trevor

0

สำหรับตัวอย่างแรกนั้นสั้นกว่าและดูดีกว่า ตามโพสต์อื่น ๆ สิ่งที่คุณเลือกยังขึ้นอยู่กับสิ่งที่คุณต้องการจะทำกับการเปรียบเทียบ


0

คำตอบคือ "มันขึ้นอยู่กับ"

ฉันใช้ตัวอย่างแรกถ้าฉันพิจารณา 0, "", [] และเท็จ (รายการไม่ครบถ้วนสมบูรณ์) จะเทียบเท่ากับไม่มีในบริบทนี้


0

โดยส่วนตัวแล้วฉันเลือกวิธีที่สอดคล้องกันในหลายภาษา: ฉันทำif (var)(หรือเทียบเท่า) เฉพาะเมื่อ var ถูกประกาศว่าเป็นบูลีน (หรือกำหนดไว้เช่นนี้ใน C เราไม่มีประเภทเฉพาะ) ฉันยังนำหน้าตัวแปรเหล่านี้ด้วยb(ดังนั้นจึงเป็นbVarจริง) เพื่อให้แน่ใจว่าฉันจะไม่ใช้ชนิดอื่นที่นี่โดยไม่ตั้งใจ
ฉันไม่ชอบการชี้ขาดแบบบูลถึงแม้จะน้อยกว่าเมื่อมีกฎที่ซับซ้อนมากมาย

แน่นอนว่าคนจะไม่เห็นด้วย บางคนไปไกลกว่าฉันเห็นif (bVar == true)ในโค้ด Java ที่งานของฉัน (ซ้ำซ้อนเกินไปสำหรับรสนิยมของฉัน!) คนอื่น ๆ ชอบไวยากรณ์ขนาดกะทัดรัดมากเกินไปไปwhile (line = getNextLine())(คลุมเครือเกินไปสำหรับฉัน)

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