Python's with statement

  • 2020-04-02 13:47:08
  • OfStack

A list,

With is a new syntax introduced from Python 2.5, more specifically, a context management protocol to simplify the try... Except... The process flow for finally. With is initialized with the method with arbitration, then with arbitration and handling exceptions. For tasks that need to be pre-set and cleaned up afterwards, with provides a handy expression.

The basic syntax for with is that EXPR is an arbitrary expression, VAR is a single variable (which can be a tuple), and "as VAR" is optional.


with EXPR as VAR:
    BLOCK

According to PEP 343, with... The as... Will be translated into the following statement:

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)

Why is it so complicated? Notice the code in finally that the cleanup of finally does not take place until the BLOCK is executed, because when EXPR is executed, an exception is thrown and an error with AttributeError is reported to access mgr.exit.


Two, the realization way

As can be seen from the previous translation of with, the object evaluated with must have a method with and a method with with. Take a look at an example of a file read, and notice that here we have to solve two problems: the file read exception, and then close the file handle after reading. Using a try... Except is usually written like this:


f = open('/tmp/tmp.txt')
try:
    for line in f.readlines():
        print(line)
finally:
    f.close()

Note that we did not handle the IOError of file opening failure here, the above writing works fine, but for each open file, we have to manually close the file handle. If you want to use with to do this, you need a proxy class:

class opened(object):
    def __init__(self, name):
        self.handle = open(name)
    def __enter__(self):
        return self.handle
    def __exit__(self, type, value, trackback):
        self.handle.close()
with opened('/tmp/a.txt') as f:
    for line in f.readlines():
        print(line)

Note that we specify a helper class called opened and implement the s/s method with s/s method, s/s method with no arguments, s/s method with 3 arguments representing the type, value, and stack information of the exception.

If you don't like defining classes, you can also use contextlib provided by the Python standard library:


from contextlib import contextmanager
@contextmanager
def opened(name):
    f = open(name)
    try:
        yield f
    finally:
        f.close()
with opened('/tmp/a.txt') as f:
    for line in f.readlines():
        print(line)

With contextmanager's function, yield returns only one parameter, and the yield is followed by the code that handles the cleanup. In our example of reading a file, this is closing the file handle. This is in principle the same as the class opened that we implemented before. If you are interested, please refer to the source code of contextmanager.

Iii. Application scenarios

With all this nonsense, are there any good examples of situations where we should use with? Of course, what's the point of this article? The following is an excerpt from PEP 343.

A template that ensures that the code is locked before execution and released after execution:


@contextmanager
    def locked(lock):
        lock.acquire()
        try:
            yield
        finally:
            lock.release()
    with locked(myLock):
        # Code here executes with myLock held.  The lock is
        # guaranteed to be released when the block is left (even
        # if via return or by an uncaught exception).

Commit and rollback of database transactions:

@contextmanager
        def transaction(db):
            db.begin()
            try:
                yield None
            except:
                db.rollback()
                raise
            else:
                db.commit()

Redirect stdout:

@contextmanager
def stdout_redirected(new_stdout):
    save_stdout = sys.stdout
    sys.stdout = new_stdout
    try:
        yield None
    finally:
        sys.stdout = save_stdout
with opened(filename, "w") as f:
    with stdout_redirected(f):
        print "Hello world"

Note that the above example is not thread-safe and should be used with caution in multi-threaded environments.


Four,

With is to try... Expect... A simplification of the finally syntax and provides excellent handling of exceptions. There are two ways to implement with syntax in Python: class-based and decorator-based. The two ways are in principle equivalent and can be chosen on a case-by-case basis.

The with originated as a block... The as... However, this kind of grammar was despised by many people, and finally came into being with. For this history, you can still refer to pep-343 and pep-340


Related articles: