Search code examples
pythontry-catchwith-statement

What is the equivalent try statement of the with statement?


After reading the with statement section of the language documentation of Python, I was wondering if it is correct to state that this Python code:

with EXPRESSION as TARGET:
    SUITE

is equivalent to this one:

try:
    manager = (EXPRESSION)
    value = manager.__enter__()
    TARGET = value  # only if `as TARGET` is present in the with statement
    SUITE
except:
    import sys
    if not manager.__exit__(*sys.exc_info()):
        raise
else:
    manager.__exit__(None, None, None)

Edit

The correct equivalent Python code (the real CPython implementation is in C) was actually given by Guido van Rossum himself in PEP 343:

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)

Since Python 3.6, this has changed a little: now the __enter__ function is loaded before the __exit__ function (cf. https://bugs.python.org/issue27100).

So my equivalent Python code had three flaws:

  1. The __enter__ and __exit__ functions should be loaded before calling the __enter__ function.
  2. The __enter__ function should be called outside the try statement (cf. the note of point 4 in the language documentation).
  3. The else clause should instead be a finally clause, to handle the case when there is a non-local goto statement (break, continue, return) in suite.

However I don’t understand why the equivalent Python code in PEP 343 puts the finally clause in an outer try statement instead of in the inner try statement?


Solution

  • Nick Coghlan, the other author of PEP 343, answered on the Python bug tracker:

    It's a matter of historical timing: PEP 343 was written before try/except/finally was allowed, when try/finally and try/except were still distinct statements.

    However, PEP 341 was also accepted and implemented for Python 2.5, allowing for the modern try/except/finally form: https://docs.python.org/dev/whatsnew/2.5.html#pep-341-unified-try-except-finally

    So the modern try statement equivalent Python code of the with statement is this one:

    manager = (EXPRESSION)
    enter = type(manager).__enter__
    exit = type(manager).__exit__
    value = enter(manager)
    hit_except = False
    
    try:
        TARGET = value  # only if `as TARGET` is present in the with statement
        SUITE
    except:
        import sys
        hit_except = True
        if not exit(manager, *sys.exc_info()):
            raise
    finally:
        if not hit_except:
            exit(manager, None, None, None)