ดีกว่าที่จะ 'ลอง' บางอย่างและตรวจจับข้อยกเว้นหรือทดสอบว่าเป็นไปได้ก่อนเพื่อหลีกเลี่ยงข้อยกเว้นหรือไม่?


141

ฉันควรทดสอบifสิ่งที่ถูกต้องหรือเพียงtryเพื่อทำและตรวจจับข้อยกเว้น?

  • มีเอกสารประกอบที่ระบุว่าควรใช้วิธีเดียวหรือไม่?
  • pythonicทางเดียวมากกว่าหรือไม่?

ตัวอย่างเช่นฉันควร:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

หรือ:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

ความคิดบางอย่าง ...
PEP 20พูดว่า:

ข้อผิดพลาดไม่ควรผ่านไปอย่างเงียบ ๆ
เว้นแต่จะปิดเสียงไว้อย่างชัดเจน

ควรใช้ a tryแทนการifตีความว่าเป็นข้อผิดพลาดที่ผ่านไปอย่างเงียบ ๆ หรือไม่? และถ้าเป็นเช่นนั้นคุณจะปิดเสียงโดยใช้วิธีนี้อย่างชัดเจนหรือไม่


ฉันไม่ได้หมายถึงสถานการณ์ที่คุณสามารถทำได้เพียง 1 ทางเท่านั้น ตัวอย่างเช่น:

try:
    import foo
except ImportError:
    import baz

คำตอบ:


162

คุณควรเลือกtry/exceptมากกว่าif/elseหากผลลัพธ์นั้น

  • เพิ่มความเร็ว (ตัวอย่างเช่นโดยการป้องกันการค้นหาเพิ่มเติม)
  • รหัสทำความสะอาด (บรรทัดน้อยลง / อ่านง่ายขึ้น)

บ่อยครั้งสิ่งเหล่านี้ไปพร้อมกัน


เพิ่มความเร็ว

ในกรณีที่พยายามค้นหาองค์ประกอบในรายการยาวโดย:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

การลองยกเว้นเป็นตัวเลือกที่ดีที่สุดเมื่อindexอาจอยู่ในรายการและมักจะไม่เพิ่ม IndexError if index < len(my_list)วิธีนี้คุณหลีกเลี่ยงความจำเป็นสำหรับการค้นหาแบบพิเศษโดย

Python สนับสนุนการใช้ข้อยกเว้นซึ่งคุณจัดการคือวลีจากDive Into Pythonดำน้ำลงในหลามตัวอย่างของคุณไม่เพียง แต่จัดการข้อยกเว้น (อย่างสง่างาม) มากกว่าปล่อยให้มันเงียบ ๆ ผ่านยังข้อยกเว้นเกิดขึ้นเฉพาะในพิเศษกรณีของดัชนีไม่ได้ถูกพบ (เพราะฉะนั้นคำว่ายกเว้น !)


รหัสทำความสะอาด

เอกสาร Python อย่างเป็นทางการกล่าวถึงEAFP :ง่ายต่อการขอการให้อภัยมากกว่าการอนุญาตและRob Knightตั้งข้อสังเกตว่าการจับข้อผิดพลาดแทนที่จะหลีกเลี่ยงอาจส่งผลให้โค้ดสะอาดและอ่านง่ายขึ้น ตัวอย่างของเขากล่าวไว้ดังนี้:

แย่กว่านั้น(LBYL 'ดูก่อนกระโดด') :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

ดีกว่า(EAFP: ขอการให้อภัยได้ง่ายกว่าการอนุญาต) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None

if index in mylistการทดสอบดัชนี wether เป็นองค์ประกอบของ mylist ไม่ใช่ดัชนีที่เป็นไปได้ คุณจะต้องการif index < len(mylist)แทน
ychaouche

1
สิ่งนี้สมเหตุสมผล แต่ฉันจะหาเอกสารที่ทำให้ชัดเจนได้จากที่ใดที่สามารถโยนข้อยกเว้นที่เป็นไปได้สำหรับ int () docs.python.org/3/library/functions.html#intฉันไม่พบข้อมูลนี้ที่นี่
BrutalSimplicity

ในทางตรงข้ามคุณสามารถเพิ่มกรณีนี้ (from off. doc ) ในคำตอบของคุณได้เมื่อคุณควรชอบif/elseมากกว่าtry/catch
rappongy

21

ในกรณีนี้คุณควรใช้อย่างอื่นทั้งหมด:

x = myDict.get("ABC", "NO_ABC")

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


2
+1 สำหรับคำอธิบายใต้ตัวอย่างโค้ดซึ่งเป็นจุดที่
ktdrv

4
ฉันคิดว่ามันค่อนข้างชัดเจนว่านี่ไม่ใช่สิ่งที่เขาถามและตอนนี้เขาได้แก้ไขโพสต์เพื่อให้ชัดเจนยิ่งขึ้น
agf

9

การใช้tryและexceptโดยตรงมากกว่าภายในifยามควรเสมอทำได้หากมีความเป็นไปได้ของสภาพการแข่งขันใด ๆ ตัวอย่างเช่นหากคุณต้องการให้แน่ใจว่ามีไดเร็กทอรีอยู่อย่าทำสิ่งนี้:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

หากเธรดหรือกระบวนการอื่นสร้างไดเร็กทอรีระหว่างisdirและmkdirคุณจะออก ให้ทำสิ่งนี้แทน:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

ซึ่งจะออกก็ต่อเมื่อไม่สามารถสร้างไดเร็กทอรี 'foo' ได้


7

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

ควรใช้ข้อยกเว้นสำหรับ:

  1. สิ่งที่ไม่คาดคิดหรือ ...
  2. สิ่งที่คุณต้องกระโดดมากกว่าหนึ่งระดับของตรรกะ (เช่นที่breakไม่ทำให้คุณไปไกลพอ) หรือ ...
  3. สิ่งที่คุณไม่รู้แน่ชัดว่าจะจัดการข้อยกเว้นล่วงหน้าหรือ ...
  4. สิ่งที่การตรวจสอบความล้มเหลวล่วงหน้าก่อนเวลามีราคาแพง (เทียบกับการพยายามดำเนินการ)

โปรดทราบว่าบ่อยครั้งคำตอบที่แท้จริงคือ "ไม่" - ตัวอย่างเช่นในตัวอย่างแรกสิ่งที่คุณควรทำจริงๆคือใช้.get()เพื่อระบุค่าเริ่มต้น:

x = myDict.get('ABC', 'NO_ABC')

ยกเว้นว่าif 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'มักจะเร็วกว่าการใช้getงานจริง แต่น่าเสียดาย ไม่ได้บอกว่านี่เป็นเกณฑ์ที่สำคัญที่สุด แต่เป็นสิ่งที่ต้องระวัง
agf

@agf: เขียนโค้ดที่ชัดเจนและรัดกุมดีกว่า หากจำเป็นต้องปรับให้เหมาะสมในภายหลังการกลับมาเขียนใหม่ทำได้ง่าย แต่c2.com/cgi/wiki?PrematureOptimization
Amber

ฉันรู้ว่า; ประเด็นของฉันคือสิ่งนั้นif / elseและtry / exceptสามารถมีสถานที่ได้แม้ว่าจะมีทางเลือกเฉพาะกรณีเนื่องจากมีลักษณะการทำงานที่ต่างกัน
agf

@agf คุณรู้หรือไม่ว่าเมธอด get () จะได้รับการปรับปรุงในเวอร์ชันต่อ ๆ ไปให้เร็วเท่าที่ค้นหาอย่างชัดเจนหรือไม่? อย่างไรก็ตามเมื่อค้นหาสองครั้ง (เช่นเดียวกับ "ABC" ใน d: d ['ABC']) ให้ลอง: d ['ABC'] ยกเว้น KeyError: ... ไม่เร็วที่สุด?
Remi

2
@Remi ส่วนที่ช้า.get()คือการค้นหาแอตทริบิวต์และการเรียกใช้ฟังก์ชันเหนือศีรษะที่ระดับ Python การใช้คีย์เวิร์ดในบิวท์อินโดยทั่วไปจะเปลี่ยนไปทาง C ฉันไม่คิดว่ามันจะเร็วเกินไปเร็ว ๆ นี้ เท่าที่ifเทียบกับวิธีการtryอ่านdict.get () จะส่งกลับตัวชี้ที่มีข้อมูลประสิทธิภาพบางอย่าง อัตราส่วนของการเข้าชมต่อการพลาดสสาร ( tryอาจเร็วกว่านี้หากมีคีย์เกือบตลอดเวลา) เท่ากับขนาดของพจนานุกรม
ผ่านมา

6

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

  • รหัสในบล็อกลองอาจมีโอกาสสร้างความหายนะทุกประเภทก่อนที่ข้อยกเว้นจะถูกจับได้ - หากคุณตรวจสอบเชิงรุกล่วงหน้าด้วยคำสั่ง if คุณสามารถหลีกเลี่ยงสิ่งนี้ได้
  • หากรหัสที่เรียกใน try block ของคุณทำให้เกิดประเภทข้อยกเว้นทั่วไปเช่น TypeError หรือ ValueError คุณอาจไม่พบข้อยกเว้นเดียวกันกับที่คุณคาดว่าจะจับได้ - อาจเป็นอย่างอื่นที่เพิ่มระดับข้อยกเว้นเดียวกันก่อนหรือหลังการเข้าถึง บรรทัดที่ข้อยกเว้นของคุณอาจถูกยกขึ้น

เช่นสมมติว่าคุณมี:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError ไม่ได้ระบุว่าเกิดขึ้นเมื่อพยายามรับองค์ประกอบของ index_list หรือ my_list


4

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

การใช้tryเป็นการยอมรับว่าข้อผิดพลาดอาจผ่านซึ่งตรงข้ามกับการส่งผ่านไปอย่างเงียบ ๆ การใช้งานexceptทำให้ไม่ผ่านเลย

ใช้try: except:เป็นที่ต้องการในกรณีที่มีif: else:ตรรกะมีความซับซ้อนมากขึ้น ง่ายดีกว่าซับซ้อน ซับซ้อนดีกว่าซับซ้อน และการขอการให้อภัยนั้นง่ายกว่าการอนุญาต

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

อย่างไรก็ตามในตัวอย่างเฉพาะของคุณไม่เหมาะสม:

x = myDict.get('ABC', 'NO_ABC')

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


3

เมื่อใดก็ตามที่คุณใช้try/exceptสำหรับขั้นตอนการควบคุมให้ถามตัวเองว่า:

  1. เป็นเรื่องง่ายที่จะเห็นว่าเมื่อtryบล็อกสำเร็จและเมื่อล้มเหลว?
  2. คุณตระหนักถึงผลข้างเคียงทั้งหมดในtryบล็อกหรือไม่?
  3. คุณทราบทุกกรณีที่tryบล็อกเกิดข้อยกเว้นหรือไม่?
  4. หากการใช้งานtryบล็อกเปลี่ยนไปโฟลว์การควบคุมของคุณจะยังคงทำงานตามที่คาดไว้หรือไม่

หากคำตอบของคำถามเหล่านี้อย่างน้อยหนึ่งข้อคือ 'ไม่' อาจมีการให้อภัยอีกมาก มีแนวโน้มมากที่สุดจากตัวคุณในอนาคต


ตัวอย่าง. ฉันเพิ่งเห็นรหัสในโครงการขนาดใหญ่ที่มีลักษณะเช่นนี้:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

การพูดคุยกับโปรแกรมเมอร์พบว่าขั้นตอนการควบคุมที่ตั้งใจไว้คือ:

ถ้า x เป็นจำนวนเต็มให้ทำ y = foo (x)

ถ้า x เป็นรายการของจำนวนเต็มให้ทำ y = bar (x)

สิ่งนี้ใช้ได้ผลเพราะfooสร้างแบบสอบถามฐานข้อมูลและแบบสอบถามจะสำเร็จถ้าxเป็นจำนวนเต็มและโยนProgrammingErrorif xเป็นรายการ

การใช้try/exceptเป็นทางเลือกที่ไม่ดีที่นี่:

  1. ชื่อของข้อยกเว้นProgrammingErrorไม่ได้ให้ปัญหาที่แท้จริง (ซึ่งxไม่ใช่จำนวนเต็ม) ซึ่งทำให้ยากที่จะดูว่าเกิดอะไรขึ้น
  2. ProgrammingErrorจะเพิ่มขึ้นระหว่างการเรียกฐานข้อมูลซึ่งเสียเวลา สิ่งต่างๆจะน่ากลัวอย่างแท้จริงหากปรากฎว่าfooเขียนบางอย่างลงในฐานข้อมูลก่อนที่จะเกิดข้อยกเว้นหรือเปลี่ยนแปลงสถานะของระบบอื่น
  3. ไม่ชัดเจนว่าProgrammingErrorจะเพิ่มขึ้นเมื่อxรายการจำนวนเต็มหรือไม่ สมมติว่ามีการพิมพ์ผิดในการfooสืบค้นฐานข้อมูล นอกจากนี้ยังอาจเพิ่มไฟล์ProgrammingError. ผลที่ตามมาคือbar(x)ตอนนี้เรียกว่าเมื่อxเป็นจำนวนเต็ม ซึ่งอาจเพิ่มข้อยกเว้นที่เป็นความลับหรือให้ผลลัพธ์ที่ไม่อาจคาดเดาได้
  4. บล็อกเพิ่มความต้องการทุกการใช้งานในอนาคตของtry/except fooเมื่อใดก็ตามที่เราเปลี่ยนแปลงfooตอนนี้เราต้องคิดเกี่ยวกับวิธีจัดการรายการและตรวจสอบให้แน่ใจว่าProgrammingErrorมีAttributeErrorข้อผิดพลาดและไม่พูดหรือไม่มีเลย

0

สำหรับความหมายทั่วไปคุณอาจลองอ่านIdioms and Anti-Idioms ใน Python: exceptionsข้อยกเว้น

ในกรณีเฉพาะของคุณตามที่ผู้อื่นระบุไว้คุณควรใช้dict.get():

รับ (คีย์ [ค่าเริ่มต้น])

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


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

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