การจับข้อยกเว้นในขณะที่ใช้ Python 'กับ' คำสั่ง


293

เพื่อความอัปยศของฉันฉันไม่สามารถหาวิธีการจัดการข้อยกเว้นสำหรับหลาม 'กับ' คำสั่ง ถ้าฉันมีรหัส:

with open("a.txt") as f:
    print f.readlines()

ฉันต้องการจัดการ 'file not found exception' เพื่อที่จะทำบางอย่าง แต่ฉันเขียนไม่ได้

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'

และไม่สามารถเขียนได้

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'

การล้อมรอบ 'ด้วย' ในคำสั่งลอง / ยกเว้นไม่ทำงาน: ไม่มีการยกข้อยกเว้น ฉันจะทำอย่างไรเพื่อประมวลผลความล้มเหลวภายในคำสั่ง 'with' แบบ Pythonic?


คุณหมายถึงอะไร"ล้อมรอบด้วยในลอง / ยกเว้นคำสั่งไม่ทำงานอื่น: ยกเว้นจะไม่ยก" ? withคำสั่งไม่ได้อย่างน่าอัศจรรย์ทำลายรอบtry...exceptคำสั่ง
Aran-Fey

4
ที่น่าสนใจของ Java ลองคำสั่งที่มีทรัพยากรไม่สนับสนุนว่ากรณีการใช้งานนี้คุณต้องการ docs.oracle.com/javase/tutorial/essential/exceptions/...
Nayuki

คำตอบ:


256
from __future__ import with_statement

try:
    with open( "a.txt" ) as f :
        print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
    print 'oops'

หากคุณต้องการการจัดการข้อผิดพลาดที่แตกต่างจากการโทรเปิดกับรหัสการทำงานที่คุณสามารถทำได้:

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()

3
ดังที่ระบุไว้ในstackoverflow.com/questions/5205811/…บล็อกลองที่นี่กว้างเกินไป ไม่มีการแยกความแตกต่างระหว่างข้อยกเว้นขณะที่สร้างตัวจัดการบริบทและตัวเนื้อหาในคำสั่ง with ดังนั้นจึงอาจไม่ใช่โซลูชันที่ถูกต้องสำหรับทุกกรณีการใช้งาน
ncoghlan

@ncoghlan แต่คุณสามารถเพิ่มพิเศษtry...exceptบล็อกภายในที่จะได้ใกล้ชิดกับแหล่งที่มาของข้อยกเว้นที่ไม่ได้มีอะไรจะทำอย่างไรกับwith open()
rbaleksandar

1
@rbaleksandar หากฉันจำได้ถูกต้องความคิดเห็นของฉันอ้างอิงถึงตัวอย่างแรกในคำตอบโดยที่คำสั่งทั้งหมดที่มีอยู่ในบล็อกลอง / ยกเว้น (ดังนั้นแม้ว่าคุณจะมีบล็อกลอง / คาดหวังภายในข้อยกเว้นใด ๆ ที่พวกเขาปล่อยให้หนี ยังกดด้านนอกหนึ่ง) ดักลาสต่อมาเพิ่มตัวอย่างที่สองเพื่อจัดการกับกรณีที่ความแตกต่างสำคัญนั้น
ncoghlan

3
ไฟล์จะถูกปิดในตัวอย่างนี้หรือไม่? ฉันถามเพราะเปิดอยู่นอกขอบเขต "พร้อม"
Mike Collins

6
@MikeCollins การออก 'ด้วย' จะเป็นการปิดไฟล์ที่เปิดอยู่แม้ว่าไฟล์นั้นจะเปิดก่อนหน้า 'ด้วย'
user7938784

75

วิธี "Pythonic" ที่ดีที่สุดในการทำเช่นนี้โดยการใช้ประโยชน์จากwithคำสั่งนั้นแสดงอยู่ในตัวอย่าง # 6 ในPEP 343ซึ่งให้พื้นหลังของคำสั่ง

@contextmanager
def opened_w_error(filename, mode="r"):
    try:
        f = open(filename, mode)
    except IOError, err:
        yield None, err
    else:
        try:
            yield f, None
        finally:
            f.close()

ใช้ดังนี้:

with opened_w_error("/etc/passwd", "a") as (f, err):
    if err:
        print "IOError:", err
    else:
        f.write("guido::0:0::/:/bin/sh\n")

38
ฉันชอบมัน แต่มันให้ความรู้สึกเหมือนเวทมนต์ดำน้อยเกินไป มันไม่ชัดเจนทั้งหมดสำหรับผู้อ่าน
Paul Seeb

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

9
ปัญหาทั้งหมดนี้เพียง แต่ไม่ได้เขียนบล็อกในที่สุดในรหัสผู้ใช้ ฉันเริ่มที่จะคิดว่าเราทุกคนต้องทนทุกข์กับอาการโฆษณาเกินจริง
jgomo3

1
วิธีที่ดีที่สุดในการจัดการข้อยกเว้นในไพ ธ อนคือการเขียนฟังก์ชั่นที่จับและส่งคืนได้หรือไม่ อย่างจริงจัง? วิธี pythonic ในการจัดการข้อยกเว้นคือการใช้try...exceptคำสั่ง
Aran-Fey

58

การจับข้อยกเว้นในขณะที่ใช้ Python 'กับ' คำสั่ง

ที่มีคำสั่งได้รับการบริการได้โดยไม่ต้อง__future__นำเข้าตั้งแต่ Python 2.6 คุณสามารถรับได้เร็วเท่า Python 2.5 (แต่ ณ จุดนี้ถึงเวลาที่จะอัพเกรด!) ด้วย:

from __future__ import with_statement

นี่คือสิ่งที่ใกล้เคียงที่สุดที่คุณจะแก้ไขให้ถูกต้อง คุณเกือบจะอยู่ที่นั่น แต่withไม่มีexceptประโยค:

with open("a.txt") as f: 
    print(f.readlines())
except:                    # <- with doesn't have an except clause.
    print('oops')

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

try:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')

และมาตรฐานสำเร็จรูป: อย่าใช้ตัวเปล่าexcept:ที่จับBaseExceptionได้และข้อยกเว้นและคำเตือนอื่น ๆ ที่เป็นไปได้ มีอย่างน้อยเป็นเฉพาะและสำหรับข้อผิดพลาดนี้อาจจะจับException IOErrorตรวจจับข้อผิดพลาดที่คุณเตรียมไว้เท่านั้น

ดังนั้นในกรณีนี้คุณต้อง:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops

2

ความแตกต่างระหว่างต้นกำเนิดที่เป็นไปได้ของข้อยกเว้นที่ยกมาจากwithคำสั่งผสม

การแยกความแตกต่างระหว่างข้อยกเว้นที่เกิดขึ้นในwithคำสั่งนั้นเป็นเรื่องที่ยุ่งยากเพราะพวกมันสามารถเกิดขึ้นได้ในที่ต่าง ๆ สามารถยกข้อยกเว้นจากสถานที่ต่อไปนี้ (หรือฟังก์ชันที่เรียกว่า):

  • ContextManager.__init__
  • ContextManager.__enter__
  • ร่างกายของ with
  • ContextManager.__exit__

สำหรับรายละเอียดเพิ่มเติมโปรดดูเอกสารเกี่ยวกับประเภทผู้จัดการบริบท

หากเราต้องการแยกความแตกต่างระหว่างกรณีที่แตกต่างกันเพียงแค่การตัดคำwithใน a try .. exceptไม่เพียงพอ ลองพิจารณาตัวอย่างต่อไปนี้ (ใช้ValueErrorเป็นตัวอย่าง แต่แน่นอนว่ามันสามารถทดแทนด้วยประเภทข้อยกเว้นอื่น ๆ ):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

ที่นี่exceptจะจับข้อยกเว้นที่เกิดขึ้นในสถานที่ทั้งสี่ที่แตกต่างกันและดังนั้นจึงไม่อนุญาตให้มีความแตกต่างระหว่างพวกเขา หากเราย้ายอินสแตนซ์ของวัตถุตัวจัดการบริบทนอกwithเราสามารถแยกความแตกต่างระหว่าง__init__และBLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

สิ่งนี้ช่วยได้อย่างมีประสิทธิภาพ__init__แต่เราสามารถเพิ่มตัวแปรแมวมองพิเศษเพื่อตรวจสอบว่าร่างกายของผู้ที่withเริ่มต้นจะดำเนินการ (เช่นการแยกความแตกต่างระหว่าง__enter__และอื่น ๆ ):

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

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

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

วิธีการทางเลือกโดยใช้แบบฟอร์มที่เทียบเท่าที่กล่าวถึงใน PEP 343

PEP 343 - คำสั่ง "with"ระบุเวอร์ชันของwithคำสั่ง"non-with" ที่เทียบเท่า ที่นี่เราสามารถห่อชิ้นส่วนต่าง ๆ ได้อย่างง่ายดายด้วยtry ... exceptและทำให้แยกแยะระหว่างแหล่งที่มาของข้อผิดพลาดที่ต่างกัน:

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

โดยปกติแล้ววิธีการที่เรียบง่ายกว่าจะทำได้ดี

ความต้องการในการจัดการข้อยกเว้นพิเศษนั้นค่อนข้างหายากและโดยปกติแล้วการห่อหุ้มทั้งหมดwithในtry ... exceptบล็อกจะเพียงพอ โดยเฉพาะอย่างยิ่งถ้าแหล่งที่มาของข้อผิดพลาดต่าง ๆ ถูกระบุโดยประเภทข้อยกเว้นที่แตกต่างกัน (กำหนดเอง) (ผู้จัดการบริบทต้องได้รับการออกแบบตามลำดับ) เราสามารถแยกแยะความแตกต่างระหว่างพวกเขาได้อย่างง่ายดาย ตัวอย่างเช่น:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.