เพิ่มข้อยกเว้นด้วยตนเองใน Python


2264

ฉันจะเพิ่มข้อยกเว้นใน Python เพื่อให้สามารถจับได้ในภายหลังผ่านทางexceptบล็อกได้อย่างไร

คำตอบ:


2937

ฉันจะโยน / ยกระดับข้อยกเว้นใน Python ด้วยตนเองได้อย่างไร

ใช้เฉพาะคอนสตรัคข้อยกเว้นความหมายมากที่สุดที่เหมาะกับปัญหาของคุณได้

เจาะจงข้อความของคุณเช่น:

raise ValueError('A very specific bad thing happened.')

อย่ายกข้อยกเว้นทั่วไป

Exceptionหลีกเลี่ยงการเลี้ยงทั่วไป ในการจับมันคุณจะต้องตรวจจับข้อยกเว้นเฉพาะอื่น ๆ ทั้งหมดที่ซับคลาสนั้น

ปัญหาที่ 1: การซ่อนข้อบกพร่อง

raise Exception('I know Python!') # Don't! If you catch, likely to hide bugs.

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

def demo_bad_catch():
    try:
        raise ValueError('Represents a hidden bug, do not catch this')
        raise Exception('This is the exception you expect to handle')
    except Exception as error:
        print('Caught this error: ' + repr(error))

>>> demo_bad_catch()
Caught this error: ValueError('Represents a hidden bug, do not catch this',)

ปัญหาที่ 2: จะไม่จับ

และการจับที่เฉพาะเจาะจงมากขึ้นจะไม่เป็นข้อยกเว้นทั่วไป:

def demo_no_catch():
    try:
        raise Exception('general exceptions not caught by specific handling')
    except ValueError as e:
        print('we will not catch exception: Exception')


>>> demo_no_catch()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in demo_no_catch
Exception: general exceptions not caught by specific handling

แนวทางปฏิบัติที่ดีที่สุด: raiseข้อความ

แต่ให้ใช้ตัวสร้างข้อยกเว้นเฉพาะเจาะจงมากที่สุดที่ความหมายเหมาะกับปัญหาของคุณได้

raise ValueError('A very specific bad thing happened')

ซึ่งคล่องแคล่วอนุญาตให้จำนวนอาร์กิวเมนต์โดยพลการจะถูกส่งผ่านไปยังตัวสร้าง:

raise ValueError('A very specific bad thing happened', 'foo', 'bar', 'baz') 

อาร์กิวเมนต์เหล่านี้เข้าถึงได้โดยargsแอตทริบิวต์บนExceptionวัตถุ ตัวอย่างเช่น:

try:
    some_code_that_may_raise_our_value_error()
except ValueError as err:
    print(err.args)

พิมพ์

('message', 'foo', 'bar', 'baz')    

ใน Python 2.5 จะมีการmessageเพิ่มแอตทริบิวต์จริงเพื่อBaseExceptionสนับสนุนผู้ใช้ให้ยกเว้นคลาสย่อยและหยุดใช้argsแต่การแนะนำmessageและการคัดค้าน args ดั้งเดิมได้ถูกถอนออก

แนวทางปฏิบัติที่ดีที่สุด: exceptข้อ

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

logger = logging.getLogger(__name__)

try:
    do_something_in_app_that_breaks_easily()
except AppError as error:
    logger.error(error)
    raise                 # just this!
    # raise AppError      # Don't do this, you'll lose the stack trace!

อย่าแก้ไขข้อผิดพลาดของคุณ ... แต่ถ้าคุณยืนยัน

คุณสามารถรักษา stacktrace (และค่าความผิดพลาด) ด้วยsys.exc_info()แต่นี่เป็นวิธีที่ผิดพลาดมากขึ้นและมีปัญหาความเข้ากันได้ระหว่าง Python 2 และ 3ชอบที่จะใช้เปล่าraiseเพื่อเพิ่มอีกครั้ง

เพื่ออธิบาย - การsys.exc_info()คืนค่าประเภทค่าและการติดตามกลับ

type, value, traceback = sys.exc_info()

นี่คือไวยากรณ์ใน Python 2 - โปรดทราบว่านี่ไม่เข้ากันกับ Python 3:

    raise AppError, error, sys.exc_info()[2] # avoid this.
    # Equivalently, as error *is* the second object:
    raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]

หากคุณต้องการคุณสามารถแก้ไขสิ่งที่เกิดขึ้นกับการเพิ่มใหม่ของคุณ - เช่นการตั้งค่าใหม่argsสำหรับอินสแตนซ์:

def error():
    raise ValueError('oops!')

def catch_error_modify_message():
    try:
        error()
    except ValueError:
        error_type, error_instance, traceback = sys.exc_info()
        error_instance.args = (error_instance.args[0] + ' <modification>',)
        raise error_type, error_instance, traceback

และเราได้เก็บการติดตามย้อนกลับทั้งหมดไว้ในขณะที่แก้ไข args โปรดทราบว่านี่ไม่ใช่วิธีปฏิบัติที่ดีที่สุดและเป็นไวยากรณ์ที่ไม่ถูกต้องใน Python 3 (ทำให้การทำงานร่วมกันได้ยากขึ้นมาก)

>>> catch_error_modify_message()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in catch_error_modify_message
  File "<stdin>", line 2, in error
ValueError: oops! <modification>

ในPython 3 :

    raise error.with_traceback(sys.exc_info()[2])

อีกครั้ง: หลีกเลี่ยงการจัดการการติดตามย้อนกลับด้วยตนเอง มันมีประสิทธิภาพน้อยกว่าและข้อผิดพลาดง่าย และถ้าคุณใช้เธรดและsys.exc_infoคุณอาจได้รับการติดตามย้อนกลับที่ไม่ถูกต้อง (โดยเฉพาะถ้าคุณใช้การจัดการข้อยกเว้นสำหรับโฟลว์การควบคุม - ซึ่งโดยส่วนตัวแล้วฉันมักจะหลีกเลี่ยง)

Python 3 ข้อยกเว้นการผูกมัด

ใน Python 3 คุณสามารถโยงข้อยกเว้นซึ่งเก็บการติดตามย้อนกลับไว้ได้:

    raise RuntimeError('specific message') from error

ระวัง:

  • สิ่งนี้จะช่วยให้การเปลี่ยนประเภทข้อผิดพลาดเกิดขึ้นและ
  • สิ่งนี้เข้ากันไม่ได้กับ Python 2

วิธีการเลิกใช้:

สิ่งเหล่านี้สามารถซ่อนและแม้แต่ในรหัสการผลิตได้อย่างง่ายดาย คุณต้องการที่จะเพิ่มข้อยกเว้นและทำพวกเขาจะยกข้อยกเว้นแต่ไม่ใช่คนที่ตั้งใจ!

ใช้ได้ใน Python 2 แต่ไม่ใช่ใน Python 3ดังต่อไปนี้:

raise ValueError, 'message' # Don't do this, it's deprecated!

ใช้งานได้เฉพาะใน Python เวอร์ชันเก่ากว่า (2.4 หรือต่ำกว่า) คุณยังอาจเห็นคนกำลังเพิ่มสตริง:

raise 'message' # really really wrong. don't do this.

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

ตัวอย่างการใช้งาน

ฉันเพิ่มข้อยกเว้นเพื่อเตือนผู้บริโภคเกี่ยวกับ API หากพวกเขาใช้งานไม่ถูกต้อง:

def api_func(foo):
    '''foo should be either 'baz' or 'bar'. returns something very useful.'''
    if foo not in _ALLOWED_ARGS:
        raise ValueError('{foo} wrong, use "baz" or "bar"'.format(foo=repr(foo)))

สร้างประเภทข้อผิดพลาดของคุณเองเมื่อมีข้อเสนอแนะ

"ฉันต้องการทำข้อผิดพลาดตามวัตถุประสงค์เพื่อที่จะเข้าสู่ข้อยกเว้น"

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

class MyAppLookupError(LookupError):
    '''raise this when there's a lookup error for my app'''

และการใช้งาน:

if important_key not in resource_dict and not ok_to_be_missing:
    raise MyAppLookupError('resource is missing, and that is not ok.')

19
ขอบคุณสำหรับสิ่งนี้มันเป็นสิ่งที่ฉันต้องการ The bare raiseคือสิ่งที่ฉันต้องการเพื่อให้สามารถทำการดีบักข้อผิดพลาดที่กำหนดเองได้หลายระดับของการเรียกใช้โค้ดโดยไม่ทำให้การติดตามสแต็กแตก
CaffeineConnoisseur

นี่คือคำตอบที่ดี แต่ฉันยังทำงานกับรหัส 2.7 จำนวนมากและฉันมักพบว่าตัวเองต้องการเพิ่มข้อมูลลงในข้อยกเว้นที่ไม่คาดคิดเช่นตำแหน่งไฟล์อินพุตหรือค่าของตัวแปรบางตัว แต่เก็บสแต็กและข้อยกเว้นดั้งเดิมไว้ ฉันสามารถเข้าสู่ระบบได้ แต่บางครั้งฉันไม่ต้องการให้มีการบันทึกเช่นในที่สุดถ้ารหัสผู้ปกครองจัดการกับมัน raise sys.exc_info()[0], (sys.exc_info()[1], my_extra_info), sys.exc_info()[2]ดูเหมือนว่าจะทำสิ่งที่ฉันต้องการและฉันไม่เคยเจอปัญหากับมัน แต่มันก็รู้สึกแฮ็คไม่ใช่การฝึกฝน มีวิธีที่ดีกว่า?
Michael Scheper

2
@brennanyoung ในบริบทนั้นฉันคิดว่ามันอาจสร้างความสับสนในการเพิ่ม SyntaxError - บางทีคุณควรเพิ่มข้อยกเว้นที่กำหนดเอง ฉันอธิบายวิธีที่นี่: stackoverflow.com/a/26938914/541136
Aaron Hall

2
โปรดทราบว่าเครื่องหมายคำพูดแบบเต็มคือ "ข้อยกเว้นในตัวทั้งหมดที่ไม่ได้ออกจากระบบมาจากคลาสนี้ข้อยกเว้นที่ผู้ใช้กำหนดเองทั้งหมดควรได้รับมาจากคลาสนี้" - ส่วนใหญ่หมายความว่าคุณไม่ควรใช้ข้อยกเว้นข้อใดข้อหนึ่งใน 4 ข้อที่ไม่ได้มาจากExceptionคลาสผู้ปกครองของคุณ - คุณสามารถทำคลาสย่อยให้เฉพาะเจาะจงมากขึ้นและควรทำเช่นนั้นหากเหมาะสม
Aaron Hall

1
ในตัวอย่างสำหรับ " วิธีปฏิบัติที่ดีที่สุด: ข้อยกเว้น " คุณใช้AppErrorข้อยกเว้นที่ไม่ได้กำหนด การใช้ข้อผิดพลาดในตัวอาจจะดีกว่าAttributeError
Stevoisiak

530

อย่าทำอย่างนี้ การเลี้ยงเปลือยเปล่าไม่ใช่สิ่งที่ถูกต้องExceptionอย่างแน่นอน ดูคำตอบที่ยอดเยี่ยมของ Aaron Hallแทน

ไม่สามารถรับ pythonic ได้มากกว่านี้:

raise Exception("I know python!")

ดูเอกสารการยกคำสั่งสำหรับไพ ธ อนหากคุณต้องการข้อมูลเพิ่มเติม


67
ไม่ได้โปรด! สิ่งนี้จะลบความเป็นไปได้ที่จะระบุเฉพาะสิ่งที่คุณจับ มันเป็นวิธีที่ผิดที่จะทำ ดูคำตอบที่ยอดเยี่ยมของ Aaron Hall แทนที่จะเป็นแบบนี้ เป็นเวลาเช่นนี้ฉันหวังว่าฉันจะให้มากกว่าหนึ่ง downvote ต่อคำตอบ
Dawood ibn Kareem

27
@PeterR มันน่ากลัวเท่าเทียมกันว่ามี downvotes น้อยมาก สำหรับผู้ที่กำลังอ่านคำตอบนี้อย่าทำแบบนี้เลย! คำตอบที่ถูกต้องคือคำตอบของ Aaron Hall
Dawood ibn Kareem

6
ฉันคิดว่าควรมีคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับสาเหตุที่ผิดหรือเลวร้าย
Charlie Parker

9
@CharlieParker มีอยู่ มันเป็นส่วนแรกของคำตอบที่อาโรนฮอลล์
Dinei

5
เหตุใดคำตอบนี้จึงไม่สามารถตั้งค่าสถานะเพื่อลบได้ มันมีคะแนนโหวต 93 แล้ว!
codeforester

54

ใน Python3 มี 4 รูปแบบที่แตกต่างกันสำหรับการยกเว้นยกเว้น:

1. raise exception 
2. raise exception (args) 
3. raise
4. raise exception (args) from original_exception

1. ยกข้อยกเว้นเทียบกับ 2. ยกข้อยกเว้น (args)

หากคุณใช้raise exception (args) เพื่อเพิ่มข้อยกเว้นแล้ว argsจะถูกพิมพ์เมื่อคุณพิมพ์วัตถุยกเว้น - ดังแสดงในตัวอย่างด้านล่าง

  #raise exception (args)
    try:
        raise ValueError("I have raised an Exception")
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error I have raised an Exception 



  #raise execption 
    try:
        raise ValueError
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error 

3.raise

raiseคำสั่งโดยไม่มีข้อโต้แย้งใด ๆ ยกข้อยกเว้นล่าสุด สิ่งนี้มีประโยชน์หากคุณจำเป็นต้องดำเนินการบางอย่างหลังจากตรวจพบข้อยกเว้นแล้วต้องการเพิ่มใหม่อีกครั้ง แต่ถ้าไม่มีข้อยกเว้นมาก่อนraiseคำสั่งจะเพิ่ม TypeErrorข้อยกเว้น

def somefunction():
    print("some cleaning")

a=10
b=0 
result=None

try:
    result=a/b
    print(result)

except Exception:            #Output ->
    somefunction()           #some cleaning
    raise                    #Traceback (most recent call last):
                             #File "python", line 8, in <module>
                             #ZeroDivisionError: division by zero

4. ยกข้อยกเว้น (args) จาก original_exception

คำสั่งนี้ใช้เพื่อสร้างการเชื่อมโยงข้อยกเว้นที่มีข้อยกเว้นที่เพิ่มขึ้นในการตอบสนองต่อข้อยกเว้นอื่นสามารถมีรายละเอียดของข้อยกเว้นเดิม - ดังแสดงในตัวอย่างด้านล่าง

class MyCustomException(Exception):
pass

a=10
b=0 
reuslt=None
try:
    try:
        result=a/b

    except ZeroDivisionError as exp:
        print("ZeroDivisionError -- ",exp)
        raise MyCustomException("Zero Division ") from exp

except MyCustomException as exp:
        print("MyException",exp)
        print(exp.__cause__)

เอาท์พุท:

ZeroDivisionError --  division by zero
MyException Zero Division 
division by zero

7
โปรดทราบว่า PEP8 ชอบexception(args)มากกว่าexception (args)
Gloweye

นอกจากนี้ยังมีraise exception(args) from Noneการกล่าวว่ามีการจัดการข้อยกเว้นที่ใช้งานอยู่ในปัจจุบันและไม่มีความสนใจอีกต่อไป มิฉะนั้นถ้าคุณยกข้อยกเว้นภายในexceptบล็อกและมันไม่ได้ถูกจัดการการสืบค้นกลับสำหรับข้อยกเว้นทั้งสองจะแสดงแยกจากกันด้วยข้อความ“ ในระหว่างการจัดการกับข้อยกเว้นข้างต้นจะมีข้อยกเว้นอื่นเกิดขึ้น”
cg909

35

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

if 0 < distance <= RADIUS:
    #Do something.
elif RADIUS < distance:
    #Do something.
else:
    raise AssertionError("Unexpected value of 'distance'!", distance)

19
นี่เป็นกรณีที่ดีValueErrorกว่าAssertionErrorเพราะไม่มีปัญหากับการยืนยัน (เพราะไม่มีการทำที่นี่) - ปัญหานั้นมีค่า หากคุณต้องการจริงๆในกรณีนี้การเขียนAssertionError assert distance > 0, 'Distance must be positive'แต่คุณไม่ควรตรวจสอบข้อผิดพลาดด้วยวิธีนี้เพราะสามารถปิดการยืนยันได้ ( python -O)
นักเล่นแร่แปรธาตุ Two-Bit

1
@ Two-BitAlchemist เป็นจุดที่ดี ความคิดนั้นหายไปจากการทำให้เข้าใจง่ายเมื่อฉันเขียนตัวอย่างง่าย ๆ ข้างต้น ในหลาย ๆ กรณีที่คล้ายกันเป็นเงื่อนไขที่ไม่เกี่ยวข้องกับค่าเฉพาะ แต่ความหมายก็คือ "การไหลของการควบคุมไม่ควรมาที่นี่"
Evgeni Sergeev

2
@ Two-BitAlchemist Assertions สามารถปิดได้ใช่แล้ว แต่คุณไม่ควรใช้มันเพื่อตรวจสอบข้อผิดพลาดเลยเหรอ?
Evgeni Sergeev

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

1
@ Two-BitAlchemist สำหรับฉันบทบาทของการยืนยันไม่ได้เป็นการตรวจสอบข้อผิดพลาดต่อ (ซึ่งเป็นสิ่งที่การทดสอบ) แต่พวกเขาตั้งรั้วภายในรหัสที่ข้อบกพร่องบางอย่างไม่สามารถผ่านได้ ดังนั้นจึงง่ายต่อการติดตามและแยกข้อบกพร่องซึ่งจะเกิดขึ้นอย่างหลีกเลี่ยงไม่ นี่เป็นเพียงนิสัยที่ดีที่ต้องใช้ความพยายามเพียงเล็กน้อยขณะที่การทดสอบต้องใช้ความพยายามและเวลามาก
Evgeni Sergeev

12

อ่านคำตอบที่มีอยู่ก่อนนี่เป็นเพียงข้อมูลเพิ่มเติม

ขอให้สังเกตว่าคุณสามารถเพิ่มข้อยกเว้นโดยมีหรือไม่มีข้อโต้แย้ง

ตัวอย่าง:

raise SystemExit

ออกจากโปรแกรม แต่คุณอาจต้องการรู้ว่าเกิดอะไรขึ้นคุณสามารถใช้สิ่งนี้ได้

raise SystemExit("program exited")

สิ่งนี้จะพิมพ์ "โปรแกรมออก" ไปยัง stderr ก่อนปิดโปรแกรม


2
นี่ไม่ใช่สิ่งที่ขัดกับกระบวนทัศน์ของ OOP ใช่หรือไม่ ฉันสมมติว่ากรณีแรกพ่นอ้างอิงระดับและกรณีที่สองเป็นตัวอย่างของ SystemExit จะไม่raise SystemExit()เป็นทางเลือกที่ดีกว่าใช่ไหม ทำไมคนแรกถึงได้ผล?
ไหม้

2

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

def avg(marks):
    assert len(marks) != 0,"List is empty."
    return sum(marks)/len(marks)

mark2 = [55,88,78,90,79]
print("Average of mark2:",avg(mark2))

mark1 = []
print("Average of mark1:",avg(mark1))

2

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

try:
    foo() 
except Exception as e:
    print(str(e)) # Print out handled error

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


0

คุณควรเรียนรู้คำสั่งเพิ่มของ python ควรเก็บไว้ในบล็อกลอง ตัวอย่าง -

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.