3

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?

Maggyero
  • 3,120
  • 2
  • 24
  • 43
  • 3
    No, the context closing will always happen, not only in case of an exception. Therefore I see no significant overlap between these constructs. – guidot Dec 13 '19 at 12:30
  • 1
    The docs you link to explicitly mention `finally`, they're not equivalent at the minute since exit will not be performed unless there's an error – Sayse Dec 13 '19 at 12:30
  • @guidot Good catch, I forgot to include the case where there is no exception. Let me update the question. – Maggyero Dec 13 '19 at 12:33
  • To put it as plainly as possible, `with` is similar to `try\except\finally` but not `try\except`. – maestro.inc Dec 13 '19 at 12:35
  • @Sayse Then could you provide an answer using `finally`? – Maggyero Dec 13 '19 at 12:37
  • @maestro.inc Then could you provide an answer using `finally`? – Maggyero Dec 13 '19 at 12:37
  • 3
    [*"`with` is actually slightly more powerful than a `try...finally` in that the with can introspect and silence errors."*](https://stackoverflow.com/a/26096914/476) – deceze Dec 13 '19 at 12:38
  • `try: \n f = open('file.txt', 'r') \n\r except Exception: print('error working on file') \n\r finally: f.close()` – maestro.inc Dec 13 '19 at 13:08
  • @maestro.inc I meant a general answer, not a particular example. – Maggyero Dec 13 '19 at 13:34
  • @Sayse I have updated my question. Any idea? – Maggyero Dec 13 '19 at 15:57

1 Answers1

2

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)
Maggyero
  • 3,120
  • 2
  • 24
  • 43
  • 2
    And thanks to @Maggyero's PR, this expansion will now appear in the main CPython documentation: https://github.com/python/cpython/commit/226e6e7d4326cf91ef37e13528eb1f62de1bb832 :) – ncoghlan Dec 30 '19 at 05:28