簡體   English   中英

Python *與*語句完全等同於嘗試 - (除外) - finally塊?

[英]Is Python *with* statement exactly equivalent to a try - (except) - finally block?

我知道這被廣泛討論,但我仍然找不到答案來證實這一點: with語句與在try - (除了)-finally塊中調用相同代碼相同,其中無論在__exit__函數中定義了什么上下文管理器放在finally塊中?

例如 - 這兩個代碼片段完全相同嗎?

import sys
from contextlib import contextmanager

@contextmanager
def open_input(fpath):
    fd = open(fpath) if fpath else sys.stdin
    try:
        yield fd
    finally:
        fd.close()

with open_input("/path/to/file"):
    print "starting to read from file..."

同樣如下:

def open_input(fpath):
    try:
        fd = open(fpath) if fpath else sys.stdin
        print "starting to read from file..."
    finally:
        fd.close()

open_input("/path/to/file")

謝謝!

我將暫時提到范圍,因為它真的不太相關。

根據PEP 343

with EXPR as VAR:
    BLOCK

翻譯成

mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        BLOCK
    except:
        # The exceptional case is handled here
        exc = False
        if not exit(mgr, *sys.exc_info()):
            raise
        # The exception is swallowed if exit() returns true
finally:
    # The normal and non-local-goto cases are handled here
    if exc:
        exit(mgr, None, None, None)

如您所見, type(mgr).__enter__會按預期調用,但不會在try調用。

在退出時調用type(mgr).__exit__ 唯一的區別是, 當存在異常時, if not exit(mgr, *sys.exc_info())路徑被采用 這提供with內省和沉默錯誤的能力,這with finally子句可以做的不同。


contextmanager並不復雜這么多 只是:

def contextmanager(func):
    @wraps(func)
    def helper(*args, **kwds):
        return _GeneratorContextManager(func, *args, **kwds)
    return helper

然后看看有問題的課程:

class _GeneratorContextManager(ContextDecorator):
    def __init__(self, func, *args, **kwds):
        self.gen = func(*args, **kwds)

    def __enter__(self):
        try:
            return next(self.gen)
        except StopIteration:
            raise RuntimeError("generator didn't yield") from None

    def __exit__(self, type, value, traceback):
        if type is None:
            try:
                next(self.gen)
            except StopIteration:
                return
            else:
                raise RuntimeError("generator didn't stop")
        else:
            if value is None:
                value = type()
            try:
                self.gen.throw(type, value, traceback)
                raise RuntimeError("generator didn't stop after throw()")
            except StopIteration as exc:
                return exc is not value
            except:
                if sys.exc_info()[1] is not value:
                    raise

已經省略了不重要的代碼。

首先要注意的是,如果有多個yield ,則此代碼將出錯。

這不會明顯影響控制流程。

考慮__enter__

try:
    return next(self.gen)
except StopIteration:
    raise RuntimeError("generator didn't yield") from None

如果上下文管理器編寫得很好,那么這將永遠不會超出預期。

一個區別是,如果生成器拋出StopIteration ,將產生不同的錯誤( RuntimeError )。 這意味着該行為是不完全相同的正常with ,如果你正在運行的完全任意代碼。

考慮一個沒有錯誤的__exit__

if type is None:
    try:
        next(self.gen)
    except StopIteration:
        return
    else:
        raise RuntimeError("generator didn't stop")

唯一的區別是和以前一樣; 如果你的代碼拋出StopIteration ,它將影響生成器,因此contextmanager裝飾器會誤解它。

這意味着:

from contextlib import contextmanager

@contextmanager
def with_cleanup(func):
    try:
        yield
    finally:
        func()

def good_cleanup():
    print("cleaning")

with with_cleanup(good_cleanup):
    print("doing")
    1/0
#>>> doing
#>>> cleaning
#>>> Traceback (most recent call last):
#>>>   File "", line 15, in <module>
#>>> ZeroDivisionError: division by zero

def bad_cleanup():
    print("cleaning")
    raise StopIteration

with with_cleanup(bad_cleanup):
    print("doing")
    1/0
#>>> doing
#>>> cleaning

哪個不太重要,但可能。

最后:

else:
    if value is None:
        value = type()
    try:
        self.gen.throw(type, value, traceback)
        raise RuntimeError("generator didn't stop after throw()")
    except StopIteration as exc:
        return exc is not value
    except:
        if sys.exc_info()[1] is not value:
            raise

這提出了關於StopIteration的相同問題,但有趣的是要注意到最后一部分。

if sys.exc_info()[1] is not value:
    raise

這意味着如果未處理異常,則回溯將保持不變。 如果它已被處理但存在新的回溯,則會引發該回溯。

這完全符合規范。


TL; DR

  • with實際上要稍微強過一個try...finally ,該with能反思和沉默的錯誤。

  • 注意StopIteration ,但是你可以使用@contextmanager來創建上下文管理器。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM