头阵时间:201八-0二-2叁 壹伍:2八

python之with语句的原理,pythonwith语句

首发时间:201八-0二-二三 一五:2八


前面看来1篇博客说博主python面试时境遇面试官提问with的规律,而那位博主的博文未有谈到with原理,故有此文。

 

 

contextlib — Context Manager Utilities

简单易行,with 语句是独立的程序块 “try catch
finally”的一种形式抽取。python的撰稿人在PEP3四3中写道


有关with语句,官方文档中是那般描述的:

The with statement is used to wrap the execution of a block with
methods defined by a context manager (see section With Statement Context
Managers). This allows common tryexceptfinally usage
patterns to be encapsulated for convenient reuse.

 with_stmt ::= "with" with_item ("," with_item)* ":" suite  with_item ::= expression ["as" target] 

The execution of the with statement with one “item” proceeds as follows:

The context expression (the expression given in the with_item) is
evaluated to obtain a context manager.

The context manager’s __exit__() is loaded for later use.

The context manager’s __enter__() method is invoked.

If a target was included in the with statement, the return value from
__enter__() is assigned to it.

Note
The with statement guarantees that if the __enter__() method returns
without an error, then __exit__() will always be called. Thus, if an
error occurs during the assignment to the target list, it will be
treated the same as an error occurring within the suite would be. See
step 6 below.

The suite is executed.

The context manager’s __exit__() method is invoked. If an exception
caused the suite to be exited, its type, value, and traceback are passed
as arguments to __exit__(). Otherwise, three None arguments are
supplied.

 

contextlib – 上下文物管理理器套件

Purpose: Utilities for creating and working with context managers.
The contextlib module contains utilities for working with context
managers and the with statement.
指标:
用于创立和行使上下文物管理理器的套件。contextlib模块包蕴用于选取上下文物管理理器即with语句的套件。

Context Manager API
上下文物管理理器API

A context manager is responsible for a resource within a code block,
possibly creating it when the block is entered and then cleaning it up
after the block is exited. For example, files support the context
manager API to make it easy to ensure they are closed after all reading
or writing is done.
上下文物管理理器重要用于拍卖三个代码块中的资源:或许在进入代码块时成立,然后在代码块执行完成后清理。例如:如下所示的代码,使用扶助上下文管理器API的文本打开药情势将很随便的保证在读写完毕之后,文件都将被关门。

contextlib_file.py

with open('/tmp/pymotw.txt', 'wt') as f:
    f.write('contents go here')

“ This PEP adds a new statement “with” to the Python language to make it
possible to factor out standard uses of try/finally statements.”

事先看到一篇博客说博主python面试时遇下边试官提问with的规律,而那位博主的博文未有聊起with原理,故有此文。

谷歌(谷歌)翻译成普通话便是:

with语句用于采取由上下文物管理理器定义的不二法门来封装块的推行(请参见使用语句上下文物管理理器1节)。
那允许通用的try…except…finally使用格局被封装以便于重用【那句话大致意思就是“with语句”类似于try…except…finally封装之后的的情形】。

python之with语句的原理,Python中的上下文物管理理器。含蓄三个“项目”的with语句的执行进程如下:
一.上下文表达式(在with_item中付出的表达式)被评估以获得上下文物管理理器。【会有别体系来处理,如文件,进度等都可以运用with语句】
2.上下文管理器的__exit
__()被加载供之后选取。【负责上下文的脱离】
三.上下文物管理理器的__enter __()方法被调用。【负责上下文的进去】
四.假使在with语句中含有目的,则将__enter
__()的再次来到值分配给它。【假设with前面跟着as 对象(如with open() as
f),那么此目的获得with上下文对象的__enter__()的再次回到值,(附:应该是近似操作数据库时的接连对象和游标的差距)】

注意
with语句保证,如果__enter __()方法返回时没有错误,那么将始终调用__exit __()。 因此,如果在分配给目标列表期间发生错误,它将被视为与套件内发生的错误相同。 请参阅下面的第6步。

伍.该套件已施行。【意思正是语句体中的进程执行达成,执行实现就到第陆步–调用__exit__()来退出】
6.上下文物管理理器的__exit __()方法被调用。
纵然不行导致套件退出,则其体系,值和回想作为参数字传送递给__exit
__()。 否则,将提供多少个无参数。

关于退出返回值:

If the suite was exited due to an exception, and the return value from the __exit__() method was false, the exception is reraised. If the return value was true, the exception is suppressed, and execution continues with the statement following the with statement.

If the suite was exited for any reason other than an exception, the return value from __exit__() is ignored, and execution proceeds at the normal location for the kind of exit that was taken.

中文:
如果套件由于异常而退出,并且__exit __()方法的返回值为false,则会重新对异常进行重新评估。 如果返回值为true,则异常被抑制,并继续执行with语句后面的语句。

如果套件由于除了异常之外的任何原因而退出,则__exit __()的返回值将被忽略,并且执行将在正常位置继续进行。

 

 

 

意思就是:

如果是异常退出,那么会返回false,(根据文档中的exit的描述“that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.”,大概意思就是exit()不会处理异常,会重新抛出异常抛出给外面,由调用者处理,因为这是调用者的责任)

 

如果返回 True,则忽略异常,不再对异常进行处理【(在exit内部处理完异常后,可以让”__exit__()”方法返回True,此时该异常就会不会再被抛出,with会认为它的执行体没有发生异常)】

 

 

(with会识别再次来到值,依据再次回到值来拍卖,假诺是False,那么with会将执行体中的非常抛出,固然是True,那么with会认为尚未发出尤其(忽略极度),而继续执行外面的说话,但由于当中调用的了__exit__(),所以在10分之后的言语是不会运作的)

 

附上2个文书档案中提供的2个关于with中运用锁的事例:

 

file is automatically closed

这是Python追求语言简化做出的一种语法糖。

 

多少个测试:

文件自动被关闭

A context manager is enabled by the with statement, and the API involves
two methods. The enter() method is run when execution flow enters
the code block inside the with. It returns an object to be used within
the context. When execution flow leaves the with block, the exit()
method of the context manager is called to clean up any resources being
used.
上下文物管理理器能够由with语句开启,他的API包罗八个章程:当程序进入with语句块时,就运行
enter()方法。所重回的靶子能够在左右文中使用。当执行语句流要离开with语句块时,调用上下文物管理理器的
exit()方法将清理所使用的漫天财富。

contextlib_api.py

class Context:

    def __init__(self):
        print('__init__()')

    def __enter__(self):
        print('__enter__()')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__()')

with Context():
    print('Doing work in the context')

Combining a context manager and the with statement is a more compact way
of writing a try:finally block, since the context manager’s exit()
method is always called, even if an exception is raised.
将上下文物管理理器和with语句结合起来使用,比编写try:finally语句块显得更简短,那是因为上下文物管理理器的
exit()方法会自动调用,甚至在出现十分的情景下。

  • python3 contextlib_api.py

__init__()
__enter__()
Doing work in the context
__exit__()

The enter() method can return any object to be associated with a
name specified in the as clause of the with statement. In this example,
the Context returns an object that uses the open context.
enter()方法可以回到任何在with语句中的as子句所钦定名称相关联的靶子。
在本示例中,Context类重回一个选择打开上下文的目的。

contextlib_api_other_object.py

class WithinContext:

    def __init__(self, context):
        print('WithinContext.__init__({})'.format(context))

    def do_something(self):
        print('WithinContext.do_something()')

    def __del__(self):
        print('WithinContext.__del__')


class Context:

    def __init__(self):
        print('Context.__init__()')

    def __enter__(self):
        print('Context.__enter__()')
        return WithinContext(self)

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('Context.__exit__()')

with Context() as c:
    c.do_something()

The value associated with the variable c is the object returned by
enter(), which is not necessarily the Context instance created in
the with statement.
与变量c相关联的值是由enter()所重临的对象,并不是必须在with语句中开创的Context实例。

$ python3 contextlib_api_other_object.py

Context.init()
Context.enter()
WithinContext.init(<main.Context object at 0x1007b1c50>)
WithinContext.do_something()
Context.exit()
WithinContext.del

The exit() method receives arguments containing details of any
exception raised in the with block.
exit()方法收到的参数,包涵with语句块中所抛出的其他相当信息的细节。

contextlib_api_error.py

class Context:

    def __init__(self, handle_error):
        print('__init__({})'.format(handle_error))
        self.handle_error = handle_error

    def __enter__(self):
        print('__enter__()')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__()')
        print('  exc_type =', exc_type)
        print('  exc_val  =', exc_val)
        print('  exc_tb   =', exc_tb)
        return self.handle_error

with Context(True):
    raise RuntimeError('error message handled')

print()

with Context(False):
    raise RuntimeError('error message propagated')

If the context manager can handle the exception, exit() should
return a true value to indicate that the exception does not need to be
propagated. Returning false causes the exception to be re-raised after
exit() returns.
一旦上下文物管理理器可以处理卓殊,exit()将赶回三个true,指明极度不须求被传出。假诺回到false将导致很是在
exit()在回到之后重新抛出。

$ python3 contextlib_api_error.py

init(True)
enter()
exit()
exc_type = <class ‘RuntimeError’>
exc_val = error message handled
exc_tb = <traceback object at 0x10115cc88>

init(False)
enter()
exit()
exc_type = <class ‘RuntimeError’>
exc_val = error message propagated
exc_tb = <traceback object at 0x10115cc88>
Traceback (most recent call last):
File “contextlib_api_error.py”, line 33, in <module>
raise RuntimeError(‘error message propagated’)
RuntimeError: error message propagated

with 能够成效于类的某部方法,不过那么些类必须贯彻__enter__和 
__exit__三个方法。调用类的某部方法时,首先会调用__enter__方法,再调用方法本人,最终调用__exit__方法。

 

1.执行体中发生尤其:

import time
class myContextDemo(object):
    def __init__(self,gen):
        self.gen = gen
    def __enter__(self):
        print("enter in ")
        return self.gen
    def __exit__(self, exc_type, exc_val, exc_tb):
#exc_type是exception_type  exc_val是exception_value  exc_tb是exception_trackback
        print("exit in ")
        if exc_type is None:#如果是None 则继续执行
            print("None:",exc_type, exc_val, exc_tb)

        else: #异常不为空时执行,这一步,如果with语句体中发生异常,那么也会执行
            print("exception:", exc_type, exc_val, exc_tb)
            print("all done")



if __name__=="__main__":
    gen=(i for i in range(5,10))
    G=myContextDemo(gen)
    with G as f :
        print("hello")
        for i in f:
            print(i,end="\t")
        #测试1:执行体中发生异常
        raise Exception("母鸡啊")
    print("main continue")

结果展现:

一.抛出万分后,前边main continue不再执行

2.__exit__()中的else会执行

 

Context Managers as Function Decorators

比如说上边代码:

至于with语句,官方文书档案中是这么描述的:

The with statement is used to wrap the execution of a block with
methods defined by a context manager (see section With Statement Context
Managers). This allows common tryexceptfinally usage
patterns to be encapsulated for convenient reuse.

with_stmt ::= “with” with_item (“,” with_item)* “:” suite

with_item ::= expression [“as” target]

The execution of the with statement with one “item” proceeds as follows:

The context expression (the expression given in the with_item) is
evaluated to obtain a context manager.

The context manager’s __exit__() is loaded for later use.

The context manager’s __enter__() method is invoked.

If a target was included in the with statement, the return value from
__enter__() is assigned to it.

Note
The with statement guarantees that if the __enter__() method returns
without an error, then __exit__() will always be called. Thus, if an
error occurs during the assignment to the target list, it will be
treated the same as an error occurring within the suite would be. See
step 6 below.

The suite is executed.

The context manager’s __exit__() method is invoked. If an exception
caused the suite to be exited, its type, value, and traceback are passed
as arguments to __exit__(). Otherwise, three None arguments are
supplied.

 

测试二:当else中强制重返为True时:

 

import time
class myContextDemo(object):
    def __init__(self,gen):
        self.gen = gen
    def __enter__(self):
        print("enter in ")
        return self.gen
    def __exit__(self, exc_type, exc_val, exc_tb):
#exc_type是exception_type  exc_val是exception_value  exc_tb是exception_trackback
        print("exit in ")
        if exc_type is None:#如果是None 则继续执行
            print("None:",exc_type, exc_val, exc_tb)

        else: #异常不为空时执行,这一步,如果with语句体中发生异常,那么也会执行
            print("exception:", exc_type, exc_val, exc_tb)
            print("all done")
            return True #这里如果返回true可以看到发生异常后,main continue可以执行
            #即,如果exc_type是true,那么会继续执行,实际上,也可以在这里处理一下异常再返回true


if __name__=="__main__":
    gen=(i for i in range(5,10))
    G=myContextDemo(gen)
    with G as f :
        print("hello")
        for i in f:
            print(i,end="\t")
        raise Exception("母鸡啊")
        # print("continue")#这里不会执行
    print("main continue")

结果显示:

一.回去True之后,with会忽略卓殊,继续执行,所以那边“main continue”能实行

二.纵然忽略十分,在with体中特出之后的话语照旧不会实施

附:理论上得以在回来True此前处理一下百般

 

 

 

 

 

 

 

PS:假若大家想要精晓得更详尽,能够协调尝试去读一下合法文书档案。

依附关于with语句的详细介绍官方文书档案:


头阵时间:201八-02-二3 一伍:2八此前看来1篇博客说博主python面试时遇会合试官提问with的规律,而那位博主的…

上下文物管理理器作为函数装饰器

The class ContextDecorator adds support to regular context manager
classes to let them be used as function decorators as well as context
managers.
ContextDecorator类扩展了对于一般的上下文管理器类的协理,允许它们既作为函数装饰器,又作为上下文物管理理器使用。

contextlib_decorator.py

import contextlib

class Context(contextlib.ContextDecorator):

    def __init__(self, how_used):
        self.how_used = how_used
        print('__init__({})'.format(how_used))

    def __enter__(self):
        print('__enter__({})'.format(self.how_used))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__({})'.format(self.how_used))


@Context('as decorator')
def func(message):
    print(message)

print()
with Context('as context manager'):
    print('Doing work in the context')

print()
func('Doing work in the wrapped function')

One difference with using the context manager as a decorator is that the
value returned by enter() is not available inside the function being
decorated, unlike when using with and as. Arguments passed to the
decorated function are available in the usual way.
运用上下文物管理理器作为装饰器的一个组别在于,通过
enter()再次回到的值并不设有于被点缀的函数之中,那与使用with和as区别。传入装饰函数的参数与往年一律存在。

$ python3 contextlib_decorator.py

init(as decorator)

init(as context manager)
enter(as context manager)
Doing work in the context
exit(as context manager)

enter(as decorator)
Doing work in the wrapped function
exit(as decorator)
From Generator to Context Manager

Creating context managers the traditional way, by writing a class with
enter() and exit() methods, is not difficult. But sometimes
writing everything out fully is extra overhead for a trivial bit of
context. In those sorts of situations, use the contextmanager()
decorator to convert a generator function into a context manager.
始建上下文物管理理器的思想意识方式,正是通过在类中编辑 enter() 和
exit(),这并不困难。然而有时为了一点并不重大的上下文物管理理,就编写全体全部的始末,显得毫无要求。在那类境况下,使用contextmanager()装饰器,将一个生成器函数转换为2个上下文物管理理器函数。

contextlib_contextmanager.py

import contextlib

@contextlib.contextmanager
def make_context():
    print('  entering')
    try:
        yield {}
    except RuntimeError as err:
        print('  ERROR:', err)
    finally:
        print('  exiting')

print('Normal:')
with make_context() as value:
    print('  inside with statement:', value)

print('\nHandled error:')
with make_context() as value:
    raise RuntimeError('showing example of handling an error')

print('\nUnhandled error:')
with make_context() as value:
    raise ValueError('this exception is not handled')

The generator should initialize the context, yield exactly one time,
then clean up the context. The value yielded, if any, is bound to the
variable in the as clause of the with statement. Exceptions from within
the with block are re-raised inside the generator, so they can be
handled there.
生成器函数将开首化上下文管理器函数,仅仅使用yield二次,然后清理上下文。若是有别的被yield的值,它们都被限制与with语句的as子句的变量之中。来自于with语句块的不胜会在生成器内部重新抛出,由此,它们能够在此被拍卖。

$ python3 contextlib_contextmanager.py

Normal:
  entering
  inside with statement: {}
  exiting

Handled error:
  entering
  ERROR: showing example of handling an error
  exiting

Unhandled error:
  entering
  exiting
Traceback (most recent call last):
  File "contextlib_contextmanager.py", line 32, in <module>
    raise ValueError('this exception is not handled')
ValueError: this exception is not handled

The context manager returned by contextmanager() is derived from
ContextDecorator, so it also works as a function decorator.
由contextmanager()再次回到的上下文物管理理器是由ContextDecorator衍生出的,由此它以函数装饰器的不二等秘书籍工作。

contextlib_contextmanager_decorator.py

import contextlib

@contextlib.contextmanager
def make_context():
    print('  entering')
    try:
        # Yield control, but not a value, because any value
        # yielded is not available when the context manager
        # is used as a decorator.
        yield
    except RuntimeError as err:
        print('  ERROR:', err)
    finally:
        print('  exiting')


@make_context()
def normal():
    print('  inside with statement')


@make_context()
def throw_error(err):
    raise err


print('Normal:')
normal()

print('\nHandled error:')
throw_error(RuntimeError('showing example of handling an error'))

print('\nUnhandled error:')
throw_error(ValueError('this exception is not handled'))

As in the ContextDecorator example above, when the context manager is
used as a decorator the value yielded by the generator is not available
inside the function being decorated. Arguments passed to the decorated
function are still available, as demonstrated by throw_error() in this
example.
比较以上ContextDecorator示例所呈现,当context管理器当作装饰器使用时,通过生成器yield的值并不存在于棉被服装饰的函数之中。传递至装饰器函数的参数如故有效,正如在本示例中通过throw_error()所出示的。

$ python3 contextlib_contextmanager_decorator.py

Normal:
  entering
  inside with statement
  exiting

Handled error:
  entering
  ERROR: showing example of handling an error
  exiting

Unhandled error:
  entering
  exiting
Traceback (most recent call last):
  File "contextlib_contextmanager_decorator.py", line 43, in
<module>
    throw_error(ValueError('this exception is not handled'))
  File ".../lib/python3.5/contextlib.py", line 30, in inner
    return func(\*args, \*\*kwds)
  File "contextlib_contextmanager_decorator.py", line 33, in
throw_error
    raise err
ValueError: this exception is not handled

Closing Open Handles
关门打开操作

The file class supports the context manager API directly, but some other
objects that represent open handles do not. The example given in the
standard library documentation for contextlib is the object returned
from urllib.urlopen(). There are other legacy classes that use a close()
method but do not support the context manager API. To ensure that a
handle is closed, use closing() to create a context manager for it.
file类能够间接帮助上下文物管理理器,然则某些表示打开文件操作的别的对象并不援助。本示例由正规库contextlib的文书档案所提供,是urllib.urlopen()所重临的指标。还有其余的残存代码的类,使用close()方法,不过并不协助上下文物管理理器API。想要确定保障句柄是关闭的,为其应用closing()来创设多少个上下文物管理理器。

contextlib_closing.py

import contextlib


class Door:

    def __init__(self):
        print('  __init__()')
        self.status = 'open'

    def close(self):
        print('  close()')
        self.status = 'closed'

print('Normal Example:')
with contextlib.closing(Door()) as door:
    print('  inside with statement: {}'.format(door.status))
print('  outside with statement: {}'.format(door.status))

print('\nError handling example:')
try:
    with contextlib.closing(Door()) as door:
        print('  raising from inside with statement')
        raise RuntimeError('error message')
except Exception as err:
    print('  Had an error:', err)

The handle is closed whether there is an error in the with block or
not.
句柄都将被关闭,无论是或不是在with语句中带有错误。

$ python3 contextlib_closing.py

Normal Example:
init()
inside with statement: open
close()
outside with statement: closed

Error handling example:
init()
raising from inside with statement
close()
Had an error: error message
Ignoring Exceptions

It is frequently useful to ignore exceptions raised by libraries,
because the error indicates that the desired state has already been
achieved, or it can otherwise be ignored. The most common way to ignore
exceptions is with a try:except statement with only a pass statement in
the except block.
常常情况下忽略由库函数抛出的足够音信是很有必不可缺的,因为这一个错误表明了所期望的场所已经落成,或许某种情状下能够被忽视。忽略卓殊最常用的章程就是运用try:except语句,而且在except语句块中只放入八个pass语句。

contextlib_ignore_error.py

import contextlib


class NonFatalError(Exception):
    pass


def non_idempotent_operation():
    raise NonFatalError(
        'The operation failed because of existing state'
    )


try:
    print('trying non-idempotent operation')
    non_idempotent_operation()
    print('succeeded!')
except NonFatalError:
    pass

print('done')

In this case, the operation fails and the error is ignored.
在那种情状下,操作失利,并且忽略错误。

$ python3 contextlib_ignore_error.py

trying non-idempotent operation

done

The try:except form can be replaced with contextlib.suppress() to more
explicitly suppress a class of exceptions happening anywhere in the with
block.
try:except情势能够运用contextlib.suppress()替换,那样就足以更显式的遏制语句块中出今后任何岗位的那一个。

contextlib_suppress.py

import contextlib


class NonFatalError(Exception):
    pass


def non_idempotent_operation():
    raise NonFatalError(
        'The operation failed because of existing state'
    )


with contextlib.suppress(NonFatalError):
    print('trying non-idempotent operation')
    non_idempotent_operation()
    print('succeeded!')

print('done')

In this updated version, the exception is discarded entirely.
在那几个立异的版本中,万分已经完全被撤除了。

$ python3 contextlib_suppress.py

trying non-idempotent operation
done

Redirecting Output Streams
重定向输出流

Poorly designed library code may write directly to sys.stdout or
sys.stderr, without providing arguments to configure different output
destinations. The redirect_stdout() and redirect_stderr() context
managers can be used to capture output from functions like this, for
which the source cannot be changed to accept a new output argument.
设计糟糕的库代码恐怕回直接将出口写入sys.stdout只怕sys.stderr,而没有提供参数来布局不一致的输出情势。redirect_stdout()和redirect_stderr()上下文物管理理器能够被用于捕获类似于那样的函数输出:输入源不能够被转移来接受四个新的输出参数。

contextlib_redirect.py

from contextlib import redirect_stdout, redirect_stderr
import io
import sys


def misbehaving_function(a):
    sys.stdout.write('(stdout) A: {!r}\n'.format(a))
    sys.stderr.write('(stderr) A: {!r}\n'.format(a))


capture = io.StringIO()
with redirect_stdout(capture), redirect_stderr(capture):
    misbehaving_function(5)

print(capture.getvalue())

In this example, misbehaving_function() writes to both stdout and
stderr, but the two context managers send that output to the same
io.StringIO instance where it is saved to be used later.
在本示例中,misbehaving_function()同时写入stdout和stderr,然而多个上下文管理器将出口发送至同贰个io.StringIO实例,保存以备后续使用。

$ python3 contextlib_redirect.py

(stdout) A: 5
(stderr) A: 5

Note
注意

Both redirect_stdout() and redirect_stderr() modify global state by
replacing objects in the sys module, and should be used with care. The
functions are not thread-safe, and may interfere with other operations
that expect the standard output streams to be attached to terminal
devices.
redirect_stdout()和redirect_stderr()函数通过覆盖sys模块中的对象来修改全局对象,使用时必定要十分小心。而且这么些函数不是线程安全,而且大概影响别的期望将正式输出流附加到顶点的操作。

Dynamic Context Manager Stacks
动态上下文物管理理器栈

Most context managers operate on one object at a time, such as a single
file or database handle. In these cases, the object is known in advance
and the code using the context manager can be built around that one
object. In other cases, a program may need to create an unknown number
of objects in a context, while wanting all of them to be cleaned up when
control flow exits the context. ExitStack was created to handle these
more dynamic cases.
大部分上下文物管理理器一遍操作三个对象,例如二个文书恐怕一个数据库句柄。在这几个处境下,对象是预先通晓的,而且使用上下文物管理理器的代码能够被用于基于3个对象营造;在别的意况下,程序恐怕须求在内外文中创立一定未知数量的对象,当程控流退出上下文时,就将具有的这一个指标清理。成立ExitStack处理这个动态类型。

An ExitStack instance maintains a stack data structure of cleanup
callbacks. The callbacks are populated explicitly within the context,
and any registered callbacks are called in the reverse order when
control flow exits the context. The result is like having multple nested
with statements, except they are established dynamically.
ExitStack示例维护了一个清理回调函数当数码结构栈。回调函数显式的流入上下文,然后当控制流退出上下文时,全部已登记的回调函数依次根据逆序调用。结果就好像有多个放置的言辞,除非他们是自行创立。

Stacking Context Managers
上下文管理器栈

There are several ways to populate the ExitStack. This example uses
enter_context() to add a new context manager to the stack.
由很多样办法为ExitStack填入数据,本示例使用enter_context()方法为栈添加一个新的上下文物管理理器。

contextlib_exitstack_enter_context.py

import contextlib


@contextlib.contextmanager
def make_context(i):
    print('{} entering'.format(i))
    yield {}
    print('{} exiting'.format(i))


def variable_stack(n, msg):
    with contextlib.ExitStack() as stack:
        for i in range(n):
            stack.enter_context(make_context(i))
        print(msg)


variable_stack(2, 'inside context')

enter_context() first calls enter() on the context manager, and
then registers its exit() method as a callback to be invoked as the
stack is undone.
enter_context()首先在上下文物管理理器上调用 enter(),然后将其登记到
exit() 方法上,作为一旦栈被撤除,就调用的回调函数。

$ python3 contextlib_金沙注册送58,exitstack_enter_context.py

0 entering
1 entering
inside context
1 exiting
0 exiting

The context managers given to ExitStack are treated as though they are
in a series of nested with statements. Errors that happen anywhere
within the context propagate through the normal error handling of the
context managers. These context manager classes illustrate the way
errors propagate.
提须要ExitStack的上下文物管理理器,嵌套在1密密麻麻的with语句之中。在这之中上下文中任何岗位产生的荒谬,都将透过平常错误处理的上下文物管理理器传播。那些上下文物管理理器类体现了传播错误的艺术。

contextlib_context_managers.py

import contextlib

class Tracker:
    "Base class for noisy context managers."

    def __init__(self, i):
        self.i = i

    def msg(self, s):
        print('  {}({}): {}'.format(
            self.__class__.__name__, self.i, s))

    def __enter__(self):
        self.msg('entering')


class HandleError(Tracker):
    "If an exception is received, treat it as handled."

    def __exit__(self, \*exc_details):
        received_exc = exc_details[1] is not None
        if received_exc:
            self.msg('handling exception {!r}'.format(
                exc_details[1]))
        self.msg('exiting {}'.format(received_exc))
        # Return Boolean value indicating whether the exception
        # was handled.
        return received_exc


class PassError(Tracker):
    "If an exception is received, propagate it."

    def __exit__(self, \*exc_details):
        received_exc = exc_details[1] is not None
        if received_exc:
            self.msg('passing exception {!r}'.format(
                exc_details[1]))
        self.msg('exiting')
        # Return False, indicating any exception was not handled.
        return False


class ErrorOnExit(Tracker):
    "Cause an exception."

    def __exit__(self, \*exc_details):
        self.msg('throwing error')
        raise RuntimeError('from {}'.format(self.i))


class ErrorOnEnter(Tracker):
    "Cause an exception."

    def __enter__(self):
        self.msg('throwing error on enter')
        raise RuntimeError('from {}'.format(self.i))

    def __exit__(self, \*exc_info):
        self.msg('exiting')

The examples using these classes are based around variable_stack(),
which uses the context managers passed to construct an ExitStack,
building up the overall context one by one. The examples below pass
different context managers to explore the error handling behavior.
First, the normal case of no exceptions.
本示例使用那些类都以基于variable_stack()方法,该方法应用传入上下文物管理理器营造一个ExitStack,稳步营造完全的上下文。以下示例传递区别的上下文管理器,来测试错误处理行为。首先传入没有1二分的符合规律化意况:

print('No errors:')
variable_stack([
    HandleError(1),
    PassError(2),
])

Then, an example of handling exceptions within the context managers at
the end of the stack, in which all of the open contexts are closed as
the stack is unwound.
下一场,使用上下文物管理理器处理栈末尾十分的言传身教,在该栈中,一旦栈展开,全数打开的上下文物管理理器都关闭。

print('\nError at the end of the context stack:')
variable_stack([
    HandleError(1),
    HandleError(2),
    ErrorOnExit(3),
])

Next, an example of handling exceptions within the context managers in
the middle of the stack, in which the error does not occur until some
contexts are already closed, so those contexts do not see the error.
下一步,使用上下文物管理理器处理栈中间卓殊的演示,在该栈中,直到上下文管理器已经关门,栈中的错误都不会发出,由此,上下文物管理理器将看不到这几个错误。

print('\nError in the middle of the context stack:')
variable_stack([
    HandleError(1),
    PassError(2),
    ErrorOnExit(3),
    HandleError(4),
])

Finally, an example of the exception remaining unhandled and propagating
up to the calling code.
提起底,遗留的不胜代码未处理,并且传播到调用代码。

try:
    print('\nError ignored:')
    variable_stack([
        PassError(1),
        ErrorOnExit(2),
    ])
except RuntimeError:
    print('error handled outside of context')

If any context manager in the stack receives an exception and returns a
True value, it prevents that exception from propagating up to any other
context managers.
借使栈中的任何上下文物管理理器收到十分并且重临True,就拦截了向别的上下文物管理理器传播极度。

$ python3 contextlib_exitstack_enter_context_errors.py

No errors:
HandleError(1): entering
PassError(2): entering
PassError(2): exiting
HandleError(1): exiting False
outside of stack, any errors were handled

Error at the end of the context stack:
HandleError(1): entering
HandleError(2): entering
ErrorOnExit(3): entering
ErrorOnExit(3): throwing error
HandleError(2): handling exception RuntimeError(‘from 3’,)
HandleError(2): exiting True
HandleError(1): exiting False
outside of stack, any errors were handled

Error in the middle of the context stack:
HandleError(1): entering
PassError(2): entering
ErrorOnExit(3): entering
HandleError(4): entering
HandleError(4): exiting False
ErrorOnExit(3): throwing error
PassError(2): passing exception RuntimeError(‘from 3’,)
PassError(2): exiting
HandleError(1): handling exception RuntimeError(‘from 3’,)
HandleError(1): exiting True
outside of stack, any errors were handled

Error ignored:
PassError(1): entering
ErrorOnExit(2): entering
ErrorOnExit(2): throwing error
PassError(1): passing exception RuntimeError(‘from 2’,)
PassError(1): exiting
error handled outside of context
Arbitrary Context Callbacks

ExitStack also supports arbitrary callbacks for closing a context,
making it easy to clean up resources that are not controlled via a
context manager.
为了关闭2个上下文物管理理器,ExitStack
也支撑任何款式的回调,使不被上下文物管理理器控制的能源清理变的简练。

contextlib_exitstack_callbacks.py

import contextlib


def callback(\*args, \*\*kwds):
    print('closing callback({}, {})'.format(args, kwds))


with contextlib.ExitStack() as stack:
    stack.callback(callback, 'arg1', 'arg2')
    stack.callback(callback, arg3='val3')

Just as with the exit() methods of full context managers, the
callbacks are invoked in the reverse order that they are registered.
正如总体上下文物管理理器的 exit() 方法,回调函数以登记的逆序被调用。

$ python3 contextlib_exitstack_callbacks.py

closing callback((), {‘arg3’: ‘val3’})
closing callback((‘arg1’, ‘arg2’), {})

The callbacks are invoked regardless of whether an error occurred, and
they are not given any information about whether an error occurred.
Their return value is ignored.
无论是还是不是产生错误,都会调用回调函数,而且不会提供任何关于是还是不是有错误发生的信息,间接忽略再次回到值。

contextlib_exitstack_callbacks_error.py

import contextlib


def callback(\*args, \*\*kwds):
    print('closing callback({}, {})'.format(args, kwds))


try:
    with contextlib.ExitStack() as stack:
        stack.callback(callback, 'arg1', 'arg2')
        stack.callback(callback, arg3='val3')
        raise RuntimeError('thrown error')
except RuntimeError as err:
    print('ERROR: {}'.format(err))

Because they do not have access to the error, callbacks are unable to
suppress exceptions from propagating through the rest of the stack of
context managers.
鉴于并未有接触到错误,回调函数就无法抑制来自于经过上下文管理器别的栈传播的足够。

$ python3 contextlib_exitstack_callbacks_error.py

closing callback((), {‘arg3’: ‘val3’})
closing callback((‘arg1’, ‘arg2’), {})
ERROR: thrown error

Callbacks make a convenient way to clearly define cleanup logic without
the overhead of creating a new context manager class. To improve code
readability, that logic can be encapsulated in an inline function, and
callback() can be used as a decorator.
回调函数使定义清理函数逻辑非常清晰并且有利于,而且防止了创办一个新的上下文物管理理器类的付出。想要证西楚码的可读性,能够将逻辑封装在贰个内嵌函数中,将回调函数作为装饰器。

contextlib_exitstack_callbacks_decorator.py

import contextlib

with contextlib.ExitStack() as stack:

    @stack.callback
    def inline_cleanup():
        print('inline_cleanup()')
        print('local_resource = {!r}'.format(local_resource))

    local_resource = 'resource created in context'
    print('within the context')

There is no way to specify the arguments for functions registered using
the decorator form of callback(). However, if the cleanup callback is
defined inline, scope rules give it access to variables defined in the
calling code.
对于使用装饰器情势的callback()函数注册的函数未有艺术钦赐参数。但是,假若清理回调函数以内嵌的法子定义,作用域管理将能够予以访问在调用代码中定义变量的权能。

$ python3 contextlib_exitstack_callbacks_decorator.py

within the context
inline_cleanup()
local_resource = ‘resource created in context’
Partial Stacks

Sometimes when building complex contexts it is useful to be able to
abort an operation if the context cannot be completely constructed, but
to delay the cleanup of all resources until a later time if they can all
be set up properly. For example, if an operation needs several
long-lived network connections, it may be best to not start the
operation if one connection fails. However, if all of the connections
can be opened they need to stay open longer than the duration of a
single context manager. The pop_all() method of ExitStack can be used
in this scenario.
有时候,当营造复杂上下文时,假诺上下文未有完好的营造,退出机制就老大管用。但是倘使能够百分百适龄的布局好,直到后续某些时刻再清理全部的财富。例如:假设操作须求供给壹些长日子的互连网连接,壹旦某贰个总是退步,就最棒不用开首操作。然则,假设全部的总是都能开拓,那么连接就需求的开辟时间比单个上下文物管理理器的生命周期要长。ExitStack的pop_all()方法能够在这么的地方下利用。

pop_all() clears all of the context managers and callbacks from the
stack on which it is called, and returns a new stack pre-populated with
those same context managers and callbacks. The close() method of the new
stack can be invoked later, after the original stack is gone, to clean
up the resources.
pop_all()函数清理了栈中所调用的具备上下文物管理理器和回调函数,然后回来一个新的栈,栈中预先填入了这几个相同的上下文物管理理器和回调函数。新栈的close()方法可以接二连三被调用,开始的栈被清空后,就将回收全体的财富。

contextlib_exitstack_pop_all.py

import contextlib

from contextlib_context_managers import *


def variable_stack(contexts):
    with contextlib.ExitStack() as stack:
        for c in contexts:
            stack.enter_context(c)
        # Return the close() method of a new stack as a clean-up
        # function.
        return stack.pop_all().close
    # Explicitly return None, indicating that the ExitStack could
    # not be initialized cleanly but that cleanup has already
    # occurred.
    return None


print('No errors:')
cleaner = variable_stack([
    HandleError(1),
    HandleError(2),
])
cleaner()

print('\nHandled error building context manager stack:')
try:
    cleaner = variable_stack([
        HandleError(1),
        ErrorOnEnter(2),
    ])
except RuntimeError as err:
    print('caught error {}'.format(err))
else:
    if cleaner is not None:
        cleaner()
    else:
        print('no cleaner returned')

print('\nUnhandled error building context manager stack:')
try:
    cleaner = variable_stack([
        PassError(1),
        ErrorOnEnter(2),
    ])
except RuntimeError as err:
    print('caught error {}'.format(err))
else:
    if cleaner is not None:
        cleaner()
    else:
        print('no cleaner returned')

This example uses the same context manager classes defined earlier, with
the difference that ErrorOnEnter produces an error on enter()
instead of exit(). Inside variable_stack(), if all of the contexts
are entered without error then the close() method of a new ExitStack is
returned. If a handled error occurs, variable_stack() returns None to
indicate that the cleanup work is already done. And if an unhandled
error occurs, the partial stack is cleaned up and the error is
propagated.
本示例使用在此之前定义的壹样的上下文管理器类,使用分歧的 ErrorOnEnter
生成3个在 enter() 的错误,而不是
exit()。在variable_stack()内部,假使进入全部的上下文物管理理器,而且从不错误,就重临新的ExitStack中的close()方法。假使发生错误,variable_stack()就回来None,表东汉理工科作早已做到。借使有啥未处理的失实发生,栈的壹局地就被清理,并且将错误抛出

$ python3 contextlib_exitstack_pop_all.py

No errors:
HandleError(1): entering
HandleError(2): entering
HandleError(2): exiting False
HandleError(1): exiting False

Handled error building context manager stack:
HandleError(1): entering
ErrorOnEnter(2): throwing error on enter
HandleError(1): handling exception RuntimeError(‘from 2’,)
HandleError(1): exiting True
no cleaner returned

Unhandled error building context manager stack:
PassError(1): entering
ErrorOnEnter(2): throwing error on enter
PassError(1): passing exception RuntimeError(‘from 2’,)
PassError(1): exiting
caught error from 2

with file(r'D:\a.txt','r') as f :
    print f.read()

谷歌翻译成中文便是:

with语句用于选用由上下文物管理理器定义的艺术来封装块的执行(请参见使用语句上下文物管理理器一节)。
那允许通用的try…except…finally使用方式被封装以便于重用【那句话大约意思就是“with语句”类似于try…except…finally封装之后的的情景】。

涵盖3个“项目”的with语句的实施进程如下:
一.上下文表明式(在with_item中提交的表达式)被评估以获取上下文物管理理器。【会区分连串来拍卖,如文件,进度等都能够应用with语句】
2.上下文物管理理器的__exit
__()被加载供今后选择。【负责上下文的脱离】
三.上下文物管理理器的__enter __()方法被调用。【负责上下文的进去】
4.只要在with语句中包涵指标,则将__enter
__()的重回值分配给它。【若是with后边跟着as 对象(如with open() as
f),那么此目的得到with上下文对象的__enter__()的再次来到值,(附:应该是相仿操作数据库时的连年对象和游标的界别)】

注意
with语句保险,假若__enter
__()方法再次回到时并没错误,那么将始终调用__exit __()。
因而,倘使在分配给指标列表时期爆发错误,它将被视为与套件内产生的不当相同。
请参阅上边包车型地铁第六步。

5.该套件已执行。【意思正是语句体中的进程举行完结,执行完结就到第5步–调用__exit__()来退出】
陆.上下文物管理理器的__exit __()方法被调用。
假如那多少个导致套件退出,则其品种,值和追忆作为参数传递给__exit
__()。 否则,将提供七个无参数。

关于退出返回值:

If the suite was exited due to an exception, and the return value from the __exit__() method was false, the exception is reraised. If the return value was true, the exception is suppressed, and execution continues with the statement following the with statement.

If the suite was exited for any reason other than an exception, the return value from __exit__() is ignored, and execution proceeds at the normal location for the kind of exit that was taken.

中文:
如果套件由于异常而退出,并且__exit __()方法的返回值为false,则会重新对异常进行重新评估。 如果返回值为true,则异常被抑制,并继续执行with语句后面的语句。

如果套件由于除了异常之外的任何原因而退出,则__exit __()的返回值将被忽略,并且执行将在正常位置继续进行。

 

 

 

意思就是:

如果是异常退出,那么会返回false,(根据文档中的exit的描述“that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.”,大概意思就是exit()不会处理异常,会重新抛出异常抛出给外面,由调用者处理,因为这是调用者的责任)

 

如果返回 True,则忽略异常,不再对异常进行处理【(在exit内部处理完异常后,可以让”__exit__()”方法返回True,此时该异常就会不会再被抛出,with会认为它的执行体没有发生异常)】

 

 

(with会识别再次来到值,依据重返值来处理,假使是False,那么with会将执行体中的很是抛出,假如是True,那么with会认为未有发生越发(忽略非凡),而继续执行外面包车型大巴语句,但鉴于内部调用的了__exit__(),所以在10分之后的讲话是不会运转的)

 

屈居贰个文书档案中提供的贰个有关with中选拔锁的例证:

金沙注册送58 1

 

诸如此类,大家落到实处了读取a.txt全数剧情。读取后又关闭了文件。

多少个测试:

我们得以查看file方法所在的__builtin_模块,你能够搜寻对应file类到__enter__和__exit__的办法(这些事例不佳,因为file是c完成的,那里只有壳,但能支持您了解是什么样意思)

一.执行体中爆发尤其:

import time
class myContextDemo(object):
    def __init__(self,gen):
        self.gen = gen
    def __enter__(self):
        print("enter in ")
        return self.gen
    def __exit__(self, exc_type, exc_val, exc_tb):
#exc_type是exception_type  exc_val是exception_value  exc_tb是exception_trackback
        print("exit in ")
        if exc_type is None:#如果是None 则继续执行
            print("None:",exc_type, exc_val, exc_tb)

        else: #异常不为空时执行,这一步,如果with语句体中发生异常,那么也会执行
            print("exception:", exc_type, exc_val, exc_tb)
            print("all done")



if __name__=="__main__":
    gen=(i for i in range(5,10))
    G=myContextDemo(gen)
    with G as f :
        print("hello")
        for i in f:
            print(i,end="\t")
        #测试1:执行体中发生异常
        raise Exception("母鸡啊")
    print("main continue")

结果呈现:金沙注册送58 2

一.抛出极度后,前边main continue不再执行

2.__exit__()中的else会执行

 

 

测试二:当else中强制重临为True时:

 

import time
class myContextDemo(object):
    def __init__(self,gen):
        self.gen = gen
    def __enter__(self):
        print("enter in ")
        return self.gen
    def __exit__(self, exc_type, exc_val, exc_tb):
#exc_type是exception_type  exc_val是exception_value  exc_tb是exception_trackback
        print("exit in ")
        if exc_type is None:#如果是None 则继续执行
            print("None:",exc_type, exc_val, exc_tb)

        else: #异常不为空时执行,这一步,如果with语句体中发生异常,那么也会执行
            print("exception:", exc_type, exc_val, exc_tb)
            print("all done")
            return True #这里如果返回true可以看到发生异常后,main continue可以执行
            #即,如果exc_type是true,那么会继续执行,实际上,也可以在这里处理一下异常再返回true


if __name__=="__main__":
    gen=(i for i in range(5,10))
    G=myContextDemo(gen)
    with G as f :
        print("hello")
        for i in f:
            print(i,end="\t")
        raise Exception("母鸡啊")
        # print("continue")#这里不会执行
    print("main continue")

结果展现:金沙注册送58 3

1.赶回True之后,with会忽略非凡,继续执行,所以那边“main continue”能履行

2.固然忽略卓殊,在with体中越发之后的讲话依旧不会履行

附:理论上可以在回来True以前处理一下不胜

 

 

 

 

 

 

 

PS:如若大家想要通晓得更详实,能够团结尝试去读一下法定文书档案。

沾满关于with语句的详实介绍官方文书档案:


下边出自python的法定文档,详细表明了with的用法。with还扶助嵌套。

 

with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::=  expression ["as" target]
  1. The context expression (the expression given in the with_item)
    is evaluated to obtain a context manager.

  2. The context manager’s __exit__() is loaded for later use.

  3. The context manager’s __enter__() method is invoked.

  4. If a target was included in the with statement, the return value
    from __enter__() is assigned to it.

    Note

     
    The with statement guarantees that if
    the __enter__() method returns without an
    error, then __exit__() will always be called. Thus,
    if an error occurs during the assignment to the target list, it will
    be treated the same as an error occurring within the suite would be.
    See step 6 below.

  5. The suite is executed.

  6. The context manager’s __exit__() method is invoked. If an
    exception caused the suite to be exited, its type, value, and
    traceback are passed as arguments to __exit__().
    Otherwise, three Nonearguments
    are supplied.

    If the suite was exited due to an exception, and the return value
    from the __exit__() method was false, the
    exception is reraised. If the return value was true, the exception
    is suppressed, and execution continues with the statement following
    the with statement.

    If the suite was exited for any reason other than an exception, the
    return value from __exit__() is ignored, and execution
    proceeds at the normal location for the kind of exit that was taken.

With more than one item, the context managers are processed as if
multiple with statements were nested:

with A() as a, B() as b:
    suite

is equivalent to

with A() as a:
    with B() as b:
        suite

相关文章

网站地图xml地图