Bắt một ngoại lệ trong khi sử dụng câu lệnh Python 'with'


293

Để xấu hổ, tôi không thể tìm ra cách xử lý ngoại lệ cho câu lệnh python 'with'. Nếu tôi có một mã:

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

Tôi thực sự muốn xử lý 'tập tin không tìm thấy ngoại lệ' để thực hiện một số thao tác. Nhưng tôi không thể viết

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

và không thể viết

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

kèm theo 'với' trong một câu lệnh thử / ngoại trừ không hoạt động khác: ngoại lệ không được nêu ra. Tôi có thể làm gì để xử lý thất bại bên trong câu lệnh 'với' theo cách Pythonic?


Bạn có nghĩa là "kèm theo 'với' trong một lần thử / ngoại trừ câu lệnh không hoạt động khác: ngoại lệ không được nêu ra" ? Một withtuyên bố không kỳ diệu phá vỡ một try...excepttuyên bố xung quanh .
Aran-Fey

4
Điều thú vị là câu lệnh try-với-nguồn của Java không hỗ trợ chính xác trường hợp sử dụng này mà bạn muốn. docs.oracle.com/javase/tutorial/essential/exceptions/ từ
Nayuki

Câu trả lời:


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'

Nếu bạn muốn xử lý các lỗi khác nhau từ cuộc gọi mở so với mã làm việc, bạn có thể làm:

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

3
Như đã lưu ý trong stackoverflow.com/questions/5205811/ , khối thử ở đây thực sự quá rộng. Không có sự phân biệt nào được thực hiện giữa các ngoại lệ trong khi tạo trình quản lý bối cảnh và các phần tử trong phần thân của câu lệnh with, vì vậy nó có thể không phải là một giải pháp hợp lệ cho tất cả các trường hợp sử dụng.
ncoghlan

@ncoghlan Nhưng bạn có thể thêm các try...exceptkhối bổ sung bên trong withđể gần với nguồn của một ngoại lệ không liên quan gì open().
rbaleksandar

1
@rbaleksandar Nếu tôi nhớ lại một cách chính xác, bình luận của tôi hoàn toàn đề cập đến ví dụ đầu tiên trong câu trả lời, trong đó toàn bộ câu lệnh nằm trong khối try / trừ (vì vậy ngay cả khi bạn có khối thử / mong đợi bên trong, bất kỳ trường hợp ngoại lệ nào họ sẽ thoát vẫn đánh cái bên ngoài). Douglas sau đó đã thêm ví dụ thứ hai để giải quyết các trường hợp có sự khác biệt đó.
ncoghlan

3
Tập tin sẽ được đóng lại trong ví dụ này? Tôi yêu cầu bởi vì nó đã được mở ra ngoài phạm vi "với".
Mike Collins

6
@MikeCollins Thoát khỏi 'với' sẽ đóng tệp đang mở ngay cả khi tệp được mở trước 'với'.
dùng7938784

75

Cách "Pythonic" tốt nhất để làm điều này, khai thác withcâu lệnh, được liệt kê như Ví dụ # 6 trong PEP 343 , đưa ra nền tảng của câu lệnh.

@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()

Được sử dụng như sau:

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
Tôi thích nó nhưng cảm giác như có quá nhiều ma thuật đen. Nó không hoàn toàn rõ ràng cho người đọc
Paul Seeb

5
@PaulSeeb Tại sao bạn không xác định nó và tự cứu mình khỏi việc đó mỗi khi bạn cần? Nó được xác định ở cấp ứng dụng của bạn và nó cũng kỳ diệu như bất kỳ bối cảnh nào khác. Tôi nghĩ rằng ai đó sử dụng câu lệnh with sẽ hiểu rõ ràng (tên của hàm cũng có thể biểu cảm hơn nếu bạn không thích nó). Bản thân câu lệnh "with" đã được thiết kế để hoạt động theo cách này, để xác định khối mã "an toàn" và ủy thác các chức năng kiểm tra cho các trình quản lý ngữ cảnh (để làm cho mã rõ ràng hơn).

9
Tất cả những rắc rối này chỉ vì không viết khối cuối cùng trong mã người dùng. Tôi bắt đầu nghĩ rằng tất cả chúng ta đau khổ vì một triệu chứng cường điệu dài trên tuyên bố với.
jgomo3

1
Cách tốt nhất để xử lý các ngoại lệ trong python là viết một hàm bắt và trả về chúng? Nghiêm túc? Cách pythonic để xử lý các trường hợp ngoại lệ là sử dụng một try...excepttuyên bố.
Aran-Fey

58

Bắt một ngoại lệ trong khi sử dụng câu lệnh Python 'with'

Câu lệnh with đã có sẵn mà không cần __future__nhập kể từ Python 2.6 . Bạn có thể tải nó sớm nhất là Python 2.5 (nhưng tại thời điểm này đã đến lúc nâng cấp!) Với:

from __future__ import with_statement

Đây là điều gần nhất để sửa mà bạn có. Bạn sắp ở đó, nhưng withkhông có một exceptđiều khoản:

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

__exit__Phương thức của trình quản lý bối cảnh , nếu nó trả về Falsesẽ khắc phục lỗi khi kết thúc. Nếu nó trở lại True, nó sẽ đàn áp nó. Các nhà xây opendựng __exit__không quay trở lại True, vì vậy bạn chỉ cần lồng nó trong một lần thử, ngoại trừ khối:

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

Và nồi hơi tiêu chuẩn: không sử dụng trần except:để bắt BaseExceptionvà mọi ngoại lệ và cảnh báo có thể khác. Ít nhất là cụ thể như Exception, và đối với lỗi này, có lẽ bắt IOError. Chỉ bắt lỗi bạn chuẩn bị xử lý.

Vì vậy, trong trường hợp này, bạn sẽ làm:

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

2

Phân biệt giữa nguồn gốc có thể có của các ngoại lệ được nêu ra từ một withtuyên bố ghép

Phân biệt giữa các trường hợp ngoại lệ xảy ra trong một withtuyên bố là khó khăn bởi vì chúng có thể bắt nguồn từ những nơi khác nhau. Các ngoại lệ có thể được nêu ra từ một trong những vị trí sau (hoặc các hàm được gọi là trong đó):

  • ContextManager.__init__
  • ContextManager.__enter__
  • cơ thể của with
  • ContextManager.__exit__

Để biết thêm chi tiết, xem tài liệu về Các loại Trình quản lý bối cảnh .

Nếu chúng ta muốn phân biệt giữa các trường hợp khác nhau, chỉ cần bọc withvào một try .. exceptlà không đủ. Xem xét ví dụ sau (sử dụng ValueErrorlàm ví dụ nhưng tất nhiên nó có thể được thay thế bằng bất kỳ loại ngoại lệ nào khác):

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

Ở đây, exceptsẽ bắt các ngoại lệ bắt nguồn từ tất cả bốn nơi khác nhau và do đó không cho phép phân biệt giữa chúng. Nếu chúng ta di chuyển việc khởi tạo đối tượng quản lý bối cảnh bên ngoài with, chúng ta có thể phân biệt giữa __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

Thực tế, điều này chỉ giúp với __init__phần nhưng chúng ta có thể thêm một biến sentinel bổ sung để kiểm tra xem phần thân của phần withbắt đầu có thực thi hay không (nghĩa là phân biệt giữa __enter__phần khác và phần khác):

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

Phần khó khăn là phân biệt giữa các ngoại lệ bắt nguồn từ BLOCK__exit__bởi vì một ngoại lệ thoát khỏi cơ thể withsẽ được chuyển qua để __exit__có thể quyết định cách xử lý nó (xem tài liệu ). Tuy nhiên, nếu __exit__tăng chính nó, ngoại lệ ban đầu sẽ được thay thế bằng cái mới. Để giải quyết những trường hợp này, chúng ta có thể thêm một exceptmệnh đề chung trong phần thân withđể lưu trữ bất kỳ ngoại lệ tiềm năng nào có thể thoát khỏi không được chú ý và so sánh nó với một mệnh đề được bắt ở ngoài cùng exceptsau này - nếu chúng giống nhau thì điều này có nghĩa là nguồn gốc BLOCKhoặc nếu không nó là __exit__(trong trường hợp __exit__ngăn chặn ngoại lệ bằng cách trả về một giá trị thực ngoài cùngexcept đơn giản là sẽ không được thực thi).

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)

Phương pháp thay thế bằng cách sử dụng mẫu tương đương được đề cập trong PEP 343

PEP 343 - Tuyên bố "với" chỉ định phiên bản tương đương "không với" của withtuyên bố. Ở đây chúng ta có thể dễ dàng bọc các phần khác nhau try ... exceptvà do đó phân biệt giữa các nguồn lỗi tiềm ẩn khác nhau:

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)

Thông thường một cách tiếp cận đơn giản hơn sẽ làm tốt

Nhu cầu xử lý ngoại lệ đặc biệt như vậy nên khá hiếm và thông thường gói toàn bộ withtrong một try ... exceptkhối sẽ là đủ. Đặc biệt nếu các nguồn lỗi khác nhau được biểu thị bằng các loại ngoại lệ (tùy chỉnh) khác nhau (trình quản lý bối cảnh cần được thiết kế phù hợp), chúng ta có thể dễ dàng phân biệt giữa chúng. Ví dụ:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.