威尼斯人线上娱乐

python之with语句的原理,Python中的上下文物管理理器

12 4月 , 2019  

首发时间:2018-0二-23 15:2八

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

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


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

 

 

contextlib — Context Manager Utilities

简短,with 语句是超人的次第块 “try catch
finally”的一种情势抽取。python的撰稿人在PEP34三中写道


至于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.”

前边看来1篇博客说博主python面试时遇会晤试官提问with的法则,而那位博主的博文未有谈起with原理,故有此文。

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

with语句用于采纳由上下文物管理理器定义的方法来封装块的执行(请参见使用语句上下文物管理理器壹节)。
那允许通用的try…except…finally使用情势被封装以便于重用【那句话差不多意思就是“with语句”类似于try…except…finally封装之后的的状态】。

涵盖二个“项目”的with语句的执行进程如下:
1.上下文表明式(在with_item中付出的表明式)被评估以获得上下文物管理理器。【会有别于种类来拍卖,如文件,进度等都能够采纳with语句】
二.上下文物管理理器的__exit
__()被加载供之后使用。【负责上下文的淡出】
叁.上下文物管理理器的__enter __()方法被调用。【负责上下文的进去】
四.借使在with语句中蕴涵目的,则将__enter
__()的再次回到值分配给它。【如若with后边跟着as 对象(如with open() as
f),那么此目的获得with上下文对象的__enter__()的再次回到值,(附:应该是相仿操作数据库时的连日对象和游标的分歧)】

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

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__(),所以在很是之后的言辞是不会运作的)

 

沾满多个文书档案中提供的二个有关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类重临1个运用打开上下文的靶子。

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
python之with语句的原理,Python中的上下文物管理理器。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-2三 一5:28在此以前看来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区别。传入装饰函数的参数与往常同1存在。

$ 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()装饰器,将3个生成器函数转换为贰个上下文管理器函数。

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.
生成器函数将初阶化上下文管理器函数,仅仅使用yield1遍,然后清理上下文。要是有其余被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语句块中只放入2个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,但是多个上下文物管理理器将出口发送至同3个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_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的上下文物管理理器,嵌套在一雨后春笋的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,稳步创设完全的上下文。以下示例传递不一致的上下文物管理理器,来测试错误处理行为。首先传入没有特别的常规状态:

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.
为了关闭一个上下文物管理理器,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()函数清理了栈中所调用的兼具上下文物管理理器和回调函数,然后再次来到1个新的栈,栈中预先填入了那一个相同的上下文物管理理器和回调函数。新栈的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
生成2个在 enter() 的错误,而不是
exit()。在variable_stack()内部,假诺进入全体的上下文物管理理器,而且未有不当,就回到新的ExitStack中的close()方法。假诺产生错误,variable_stack()就赶回None,表西楚理工科作早就达成。假若有怎么样未处理的错误产生,栈的1有个别就被清理,并且将错误抛出

$ 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封装之后的的情形】。

富含二个“项目”的with语句的推行进程如下:
一.上下文表明式(在with_item中提交的表明式)被评估以取得上下文物管理理器。【会分别体系来处理,如文件,进程等都能够动用with语句】
2.上下文物管理理器的__exit
__()被加载供之后接纳。【负责上下文的淡出】
三.上下文物管理理器的__enter __()方法被调用。【负责上下文的进入】
肆.假诺在with语句中含有目的,则将__enter
__()的重临值分配给它。【如若with前边跟着as 对象(如with open() as
f),那么此指标获得with上下文对象的__enter__()的再次回到值,(附:应该是近乎操作数据库时的一连对象和游标的界别)】

注意
with语句保障,借使__enter
__()方法再次回到时不曾错误,那么将从来调用__exit __()。
由此,假如在分配给目的列表时期产生错误,它将被视为与套件内产生的荒谬相同。
请参阅上面包车型地铁第陆步。

伍.该套件已实施。【意思便是语句体中的进度实行完成,执行实现就到第六步–调用__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__(),所以在尤其之后的说话是不会运营的)

 

沾满八个文书档案中提供的三个有关with中应用锁的事例:

威尼斯人线上娱乐 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")

结果突显:威尼斯人线上娱乐 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")

结果显示:威尼斯人线上娱乐 3

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

二.即使忽略万分,在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地图