Python: ฉันจะรู้ได้อย่างไรว่าข้อยกเว้นใดที่อาจเกิดขึ้นจากการเรียกใช้เมธอด


89

มีวิธีใดที่จะทราบได้ (ณ เวลาเข้ารหัส) ซึ่งคาดว่าจะมีข้อยกเว้นใดบ้างเมื่อเรียกใช้งานโค้ด python ฉันจบลงด้วยการจับคลาส Exception พื้นฐาน 90% ของเวลาเนื่องจากฉันไม่รู้ว่าประเภทข้อยกเว้นใดที่อาจถูกโยนทิ้ง (และอย่าบอกให้ฉันอ่านเอกสารประกอบหลายครั้งข้อยกเว้นสามารถแพร่กระจายจากส่วนลึกและหลาย ๆ ครั้งที่เอกสารไม่ได้รับการอัปเดตหรือถูกต้อง) มีเครื่องมือบางอย่างในการตรวจสอบสิ่งนี้หรือไม่? (เช่นการอ่านรหัส python และ libs)?


2
โปรดทราบว่าใน Python <2.6 คุณสามารถraiseสตริงได้เช่นกันไม่ใช่แค่BaseExceptionคลาสย่อย ดังนั้นหากคุณกำลังเรียกใช้รหัสไลบรารีที่อยู่นอกเหนือการควบคุมของคุณก็ยังexcept Exceptionไม่เพียงพอเนื่องจากจะไม่พบข้อยกเว้นสตริง ในขณะที่คนอื่น ๆ ชี้ให้เห็นว่าคุณกำลังเห่าต้นไม้ผิดที่นี่
Daniel Pryden

ฉันไม่รู้เรื่องนั้น ฉันคิดว่ายกเว้นข้อยกเว้น: .. จับเกือบทุกอย่าง
GabiMe

2
except Exceptionใช้งานได้ดีสำหรับการจับข้อยกเว้นสตริงใน Python 2.6 และใหม่กว่า
Jeffrey Harris

คำตอบ:


22

ฉันเดาว่าวิธีแก้ปัญหาอาจไม่ชัดเจนเพียงเพราะไม่มีกฎการพิมพ์แบบคงที่

ฉันไม่ทราบว่ามีเครื่องมือบางอย่างที่ตรวจสอบข้อยกเว้น แต่คุณสามารถสร้างเครื่องมือของคุณเองที่ตรงกับความต้องการของคุณได้ (โอกาสที่ดีที่จะเล่นด้วยการวิเคราะห์แบบคงที่)

ในความพยายามครั้งแรกคุณสามารถเขียนฟังก์ชันที่สร้าง AST ค้นหาRaiseโหนดทั้งหมดจากนั้นพยายามหารูปแบบทั่วไปของการเพิ่มข้อยกเว้น (เช่นการเรียกตัวสร้างโดยตรง)

ให้xเป็นโปรแกรมต่อไปนี้:

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

สร้าง AST โดยใช้compilerแพ็คเกจ:

tree = compiler.parse(x)

จากนั้นกำหนดRaiseระดับผู้เยี่ยมชม:

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

และเดินตามRaiseโหนดรวบรวม AST :

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

คุณสามารถดำเนินการต่อได้โดยการแก้ไขสัญลักษณ์โดยใช้ตารางสัญลักษณ์ของคอมไพเลอร์วิเคราะห์การอ้างอิงข้อมูล ฯลฯ หรือคุณอาจสรุปได้ว่าCallFunc(Name('IOError'), ...)"ควรหมายถึงการเพิ่มขึ้นอย่างแน่นอนIOError" ซึ่งค่อนข้างใช้ได้สำหรับผลลัพธ์ในทางปฏิบัติที่รวดเร็ว :)


ขอบคุณสำหรับคำตอบที่น่าสนใจนี้ ไม่เข้าใจว่าทำไมฉันต้องมองหาสิ่งอื่นที่ไม่ใช่โหนดเพิ่มทั้งหมด เหตุใดฉันจึงควร "แก้ไขสัญลักษณ์โดยใช้ตารางสัญลักษณ์คอมไพเลอร์วิเคราะห์การอ้างอิงข้อมูล" ไม่ใช่วิธีเดียวในการเพิ่มข้อยกเว้นคือการเพิ่ม ()?
GabiMe

1
ที่ได้รับความv.nodesคุ้มค่าข้างต้นคุณจะไม่ได้บอกว่าสิ่งที่สิ่งที่เป็นหรือName('IOError') Name('e')คุณไม่รู้ว่าค่าเหล่านี้IOErrorและeสามารถชี้ไปที่ค่าใดได้เนื่องจากเป็นสิ่งที่เรียกว่าตัวแปรอิสระ แม้ว่าบริบทการเชื่อมโยงจะเป็นที่รู้จัก (ในที่นี้จะมีการใช้ตารางสัญลักษณ์) คุณควรทำการวิเคราะห์การอ้างอิงข้อมูลบางประเภทเพื่อสรุปค่าที่แน่นอนของพวกเขา (ซึ่งควรจะยากใน Python)
Andrey Vlasovskikh

ในขณะที่คุณกำลังมองหาโซลูชันกึ่งอัตโนมัติที่ใช้งานได้จริงรายการที่['IOError(errno.ENOENT, "not found")', 'e']แสดงต่อผู้ใช้ก็ทำได้ดี แต่คุณไม่สามารถสรุปคลาสที่แท้จริงของค่าตัวแปรที่แสดงโดยสตริงได้ :) (ขออภัยสำหรับการโพสต์ใหม่)
Andrey Vlasovskikh

1
ใช่. วิธีนี้แม้ว่าจะฉลาด แต่ก็ไม่ได้ให้ความครอบคลุมที่สมบูรณ์ เนื่องจากลักษณะแบบไดนามิกของงูใหญ่ก็เป็นไปได้อย่างสมบูรณ์แบบ ( แต่เห็นได้ชัดว่าเป็นความคิดที่ไม่ดี) exc_class = raw_input(); exec "raise " + exc_classรหัสที่คุณโทรเข้ามาเพื่อทำสิ่งที่ชอบ ประเด็นก็คือการวิเคราะห์แบบคงที่แบบนี้ไม่สามารถทำได้อย่างแท้จริงในภาษาแบบไดนามิกเช่น Python
Daniel Pryden

7
อย่างไรก็ตามคุณสามารถfind /path/to/library -name '*.py' | grep 'raise 'ได้ผลลัพธ์ที่คล้ายกัน :)
Andrey Vlasovskikh

23

คุณควรจับเฉพาะข้อยกเว้นที่คุณจะจัดการเท่านั้น

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

หากคุณจัดการข้อยกเว้นทั้งหมดจริงๆและแน่ใจว่าไม่มีข้อใดที่เป็นอันตราย (ตัวอย่างเช่นหากคุณกำลังเรียกใช้โค้ดในสภาพแวดล้อมแบบแซนด์บ็อกซ์) วิธีการจับ BaseException ทั่วไปของคุณจะเหมาะกับเป้าหมาย

คุณอาจสนใจการอ้างอิงข้อยกเว้นภาษาไม่ใช่ข้อมูลอ้างอิงสำหรับไลบรารีที่คุณใช้

หากการอ้างอิงไลบรารีไม่ดีจริง ๆ และไม่ได้โยนข้อยกเว้นของตัวเองซ้ำเมื่อตรวจจับระบบวิธีเดียวที่มีประโยชน์คือเรียกใช้การทดสอบ (อาจเพิ่มลงในชุดทดสอบเพราะหากมีบางสิ่งที่ไม่มีเอกสารอาจมีการเปลี่ยนแปลง!) . ลบไฟล์ที่สำคัญสำหรับรหัสของคุณและตรวจสอบว่ามีข้อยกเว้นอะไรบ้าง จัดหาข้อมูลมากเกินไปและตรวจสอบว่าเกิดข้อผิดพลาดใด

คุณจะต้องเรียกใช้การทดสอบต่อไปเนื่องจากแม้ว่าจะมีวิธีการรับข้อยกเว้นด้วยซอร์สโค้ด แต่ก็ไม่ได้ให้ความคิดใด ๆ ว่าคุณควรจัดการกับสิ่งเหล่านี้อย่างไร บางทีคุณควรจะแสดงข้อความแสดงข้อผิดพลาด "File needful.txt is not found!" เมื่อคุณจับIndexError? การทดสอบเท่านั้นที่สามารถบอกได้


27
แน่นอน แต่เราจะตัดสินใจได้อย่างไรว่าเขาควรจัดการข้อยกเว้นใดหากเขาไม่รู้ว่าจะมีอะไรถูกโยน ??
GabiMe

@ bugspy.net แก้ไขคำตอบของฉันเพื่อสะท้อนเรื่องนี้
P Shved

อาจถึงเวลาสำหรับตัววิเคราะห์ซอร์สโค้ดที่สามารถค้นหาสิ่งนี้ได้หรือไม่? ไม่น่าจะยากเกินไปที่จะพัฒนาฉันคิดว่า
GabiMe

@ bugspy.net ฉันรวบรวมประโยคที่ว่าทำไมมันถึงยังไม่ถึงเวลา
P Shved

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

11

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

พิจารณาสิ่งนี้

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

เป็ดสามารถเป็นวัตถุใดก็ได้

เห็นได้ชัดว่าคุณสามารถมีAttributeErrorif duck ไม่มี quack TypeErrorถ้าเป็ดมี quack แต่เรียกไม่ได้ คุณไม่รู้ว่าอะไรduck.quack()จะเพิ่มขึ้นแม้จะเป็นDuckErrorหรือบางอย่าง

ตอนนี้สมมติว่าคุณมีรหัสเช่นนี้

arr[i] = get_something_from_database()

หากIndexErrorคุณไม่ทราบว่ามันมาจาก arr [i] หรือจากส่วนลึกของฟังก์ชันฐานข้อมูล โดยปกติแล้วมันจะไม่สำคัญมากนักเมื่อเกิดข้อยกเว้น แต่มีบางอย่างผิดพลาดและสิ่งที่คุณต้องการให้เกิดขึ้นไม่ได้

เทคนิคที่มีประโยชน์คือการจับและอาจปรับข้อยกเว้นเช่นนี้ใหม่

except Exception as e
    #inspect e, decide what to do
    raise

จับมันทำไมถ้าคุณจะ "reraise" มัน?
Tarnay Kálmán

คุณไม่จำเป็นต้องตั้งค่าใหม่นั่นคือความคิดเห็นที่ควรจะระบุ
John La Rooy

2
นอกจากนี้คุณยังสามารถเลือกที่จะบันทึกข้อยกเว้นไว้ที่ใดที่หนึ่งแล้วปรับใหม่
John La Rooy

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

6

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

def apl(f,arg):
   return f(arg)

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

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


5

ฉันพบสิ่งนี้เมื่อใช้ซ็อกเก็ตฉันต้องการค้นหาเงื่อนไขข้อผิดพลาดทั้งหมดที่ฉันจะพบ (แทนที่จะพยายามสร้างข้อผิดพลาดและคิดว่าซ็อกเก็ตใดที่ฉันต้องการรายการที่กระชับ ในที่สุดฉันก็ได้ grep'ing "/usr/lib64/python2.4/test/test_socket.py" สำหรับ "เพิ่ม":

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

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


4
สิ่งนี้ทำให้ข้อโต้แย้งของฉันแข็งแกร่งขึ้นการจัดการข้อยกเว้นใน Python เป็นปัญหามากหากเราจำเป็นต้องใช้ grep หรือตัววิเคราะห์ซอร์สเพื่อจัดการกับบางสิ่งที่เป็นพื้นฐาน (ซึ่งตัวอย่างเช่นใน java มีอยู่ตั้งแต่วันแรกบางครั้งการใช้คำฟุ่มเฟือยก็เป็นสิ่งที่ดี Java เป็น verbose แต่อย่างน้อยก็ไม่มีเซอร์ไพรส์ที่น่ารังเกียจ)
GabiMe

@GabiMe มันไม่เหมือนกับความสามารถนี้ (หรือการพิมพ์แบบคงที่โดยทั่วไป) เป็นกระสุนเงินเพื่อป้องกันข้อบกพร่องทั้งหมด Java เต็มไปด้วยความประหลาดใจที่น่ารังเกียจ นั่นเป็นสาเหตุที่คราสขัดข้องเป็นประจำ
John La Rooy

2

มีสองวิธีที่ฉันพบข้อมูล อันแรกรันโค้ดใน iPython ซึ่งจะแสดงประเภทข้อยกเว้น

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

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

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)

1

โดยปกติคุณจะต้องจับข้อยกเว้นเพียงไม่กี่บรรทัดของโค้ด คุณคงไม่ต้องการใส่mainฟังก์ชันทั้งหมดลงในtry exceptอนุประโยค สำหรับทุกๆสองสามบรรทัดตอนนี้คุณควรจะ (หรือตรวจสอบได้ง่ายๆ) ว่าจะมีข้อยกเว้นประเภทใดบ้าง

เอกสารมีรายการข้อยกเว้นในตัวโดยละเอียด อย่าพยายามยกเว้นข้อยกเว้นที่คุณไม่คาดคิดพวกเขาอาจได้รับการจัดการ / คาดหวังในรหัสการโทร

แก้ไข : สิ่งที่อาจถูกโยนขึ้นอยู่กับสิ่งที่คุณทำอย่างชัดเจน! การเข้าถึงองค์ประกอบแบบสุ่มของลำดับ: IndexErrorองค์ประกอบสุ่มของคำสั่ง: KeyErrorฯลฯ

เพียงแค่พยายามเรียกใช้สองสามบรรทัดใน IDLE และทำให้เกิดข้อยกเว้น แต่ unittest จะเป็นทางออกที่ดีกว่าโดยธรรมชาติ


1
นี่ไม่ตอบคำถามง่ายๆของฉัน ฉันไม่ถามเกี่ยวกับวิธีการออกแบบการจัดการข้อยกเว้นของฉันหรือเมื่อใดหรือจะจับได้อย่างไร ฉันถามว่าจะรู้ได้อย่างไรว่าอะไรที่อาจถูกโยน
GabiMe

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