Python之contextlib模块的使用

 contextlib模块的作用

用于处理上下文管理器和with语句,上下文管理器:负现管理一个代码块中的资源,会在处理代码块时创建资源,然后在退出代码块后清理这个资源。
例如:文件就是支持上下文管理器API,可以确认完成文件读写后关闭文件。

 1、文件操作的with,使用到contextlib的示例

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

运行效果

2、__init__、__enter__、__exit__上下文管理器的使用示例

class Context(object):
    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.')
contextlib_api.py

运行效果

__init__
__enter__
Doing work in the context.
__exit__()

 3、__init__、__enter__、__exit__、__del__上下文管理器类对象相互调用的示例

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()
contextlib_api_other_object.py

运行效果

Context.__init__()
Context.__enter__()
WithinContext.__init__(<__main__.Context object at 0x00000299A5434048>)
WithinContext.do_something()
Context.__exit__()
WithinContext.__del__  
#总结
#对象的销毁是在__exit__之后

4、__exit__上下文管理器类异常错误的处理示例

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')
contextlib_api_error.py

运行效果

__init__(True)
__enter__()
__exit__()
  exc_type = <class 'RuntimeError'>
  exc_val  = error message handled
  exc_tb   = <traceback object at 0x0000021B90F9DE48>

__init__(False)
__enter__()
__exit__()
  exc_type = <class 'RuntimeError'>
  exc_val  = error message propagated
  exc_tb   = <traceback object at 0x0000021B90F9DDC8>
Traceback (most recent call last):
  File "D:/Program Files/JetBrains/PyCharmData/PythonStudent/tornado_Test/server.py", line 25, in <module>
    raise RuntimeError('error message propagated')
RuntimeError: error message propagated

#总结:
    当__exit__返回False的时候,会报异常错误

5、利用contextlib模块,实现上下文管理器的示例

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()
func('Doing work in the wrapped function')

print()
# with 实现上下文管理器运行
with Context('as context manager'):
    print('Doing work in the context')
contextlib_decorator.py

运行效果

__init__(as decorator)
__enter__(as decorator)
Doing work in the wrapped function
__exit__(as decorator)

__init__(as context manager)
__enter__(as context manager)
Doing work in the context
__exit__(as context manager)
#总结
contextlib模块实现装饰器运行上下文管理器,不会影响到传统的with的使用

6、@contextlib.contextmanager,将一个生成器函数转为上下文管理器

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')
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 "D:/Program Files/JetBrains/PyCharmData/PythonStudent/tornado_Test/server.py", line 25, in <module>
    raise ValueError('this exception is not handled')
ValueError: this exception is not handled

7、忽略异常的示例

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')
contextlib_ignore_error.py

运行效果

trying non-idempotent operation
done

8、利contextlib.suppress()抑制with抛出的异常示例

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')
contextlib_suppress.py

运行效果

trying non-idempotent operation
done

9、重定向标准输出流,错误输出流示例

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())
contextlib_redirect.py

运行效果

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

10、上下文管理器入栈,即(遇到循环的时候,代码一直排队运行,等到退出的时候,再排队退出)

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')
contextlib_exitstack_enter_context.py

运行效果

0 entering
1 entering
inside context
1 exiting
0 exiting

11、上下文管理器类展示错误是如何传播的示例

import contextlib

class Tracker(object):
    "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):
    "如果收到异常,请将其视为已处理."

    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 received_exc


class PassError(Tracker):
    "如果收到异常,则传播它."

    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')
        # 返回false,表示未处理任何异常。
        return False


class ErrorOnExit(Tracker):
    "引起异常."

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


class ErrorOnEnter(Tracker):
    "引起异常."

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

    def __exit__(self, *exc_info):
        self.msg('exiting')
contextlib_context_managers.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)
    print('在堆栈之外,任何错误都被处理')


print('没有错误:')
variable_stack([
    HandleError(1),
    PassError(2),
])

print('\n上下文堆栈末尾的错误:')
variable_stack([
    HandleError(1),
    HandleError(2),
    ErrorOnExit(3),
])

print('\n上下文堆栈中间的错误:')
variable_stack([
    HandleError(1),
    PassError(2),
    ErrorOnExit(3),
    HandleError(4),
])

try:
    print('\n错误忽略:')
    variable_stack([
        PassError(1),
        ErrorOnExit(2),
    ])
except RuntimeError:
    print('error handled outside of context')
contextlib_exitstack_enter_context_errors.py

运行效果

没有错误:
  HandleError(1): entering
  PassError(2): entering
  PassError(2): exiting
  HandleError(1): exiting False
在堆栈之外,任何错误都被处理

上下文堆栈末尾的错误:
  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
在堆栈之外,任何错误都被处理

上下文堆栈中间的错误:
  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
在堆栈之外,任何错误都被处理

错误忽略:
  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


总结:
     如果栈中的任何上下文管理吕接收到一个异常并返回一个True值,那么这会阻止该异常继续向上传播到任何其他上下文管理器。

12、任意上下文管理的回调示例

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')
contextlib_exitstack_callbacks.py

运行效果

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

 13、任意上下文管理的回调异常的处理示例

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))
contextlib_exitstack_callbacks_error.py

运行效果

closing callback((), {'arg3': 'val3'})
closing callback(('arg1', 'arg2'), {})
ERROR: thrown error
总结:
  由于回调不能访问错误,所以它们无法防止异常在上下文管理器栈中继续传播

14、装饰器实现上下文管理器的回调

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')
contextlib_exitstack_callbacks_decorator.py

运行效果

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

15、部分栈的清理的示例

import contextlib

from contextlib_context_managers import *


def variable_stack(contexts):
    with contextlib.ExitStack() as stack:
        for c in contexts:
            stack.enter_context(c)
        # 返回新堆栈的close()方法作为清理
        return stack.pop_all().close
    # 显式返回None,表示无法干净地初始化ExitStack,但已经发生了清除
    return None


print('没有错误:')
cleaner = variable_stack([
    HandleError(1),
    HandleError(2),
])
cleaner()

print('\n处理错误构建上下文管理器堆栈:')
try:
    cleaner = variable_stack([
        HandleError(1),
        ErrorOnEnter(2),
    ])
except RuntimeError as err:
    print(' 捕捉错误 {}'.format(err))
else:
    if cleaner is not None:
        cleaner()
    else:
        print('no cleaner returned')

print('\n未处理的错误构建上下文管理器堆栈:')
try:
    cleaner = variable_stack([
        PassError(1),
        ErrorOnEnter(2),
    ])
except RuntimeError as err:
    print('捕捉错误 {}'.format(err))
else:
    if cleaner is not None:
        cleaner()
    else:
        print('没有清洁工回来')
contextlib_exitstack_pop_all.py

 

 运行效果

没有错误:
  HandleError(1): entering
  HandleError(2): entering
  HandleError(2): exiting False
  HandleError(1): exiting False

处理错误构建上下文管理器堆栈:
  HandleError(1): entering
  ErrorOnEnter(2): throwing error on enter
  HandleError(1): handling exception RuntimeError('from 2')
  HandleError(1): exiting True
no cleaner returned

未处理的错误构建上下文管理器堆栈:
  PassError(1): entering
  ErrorOnEnter(2): throwing error on enter
  PassError(1): passing exception RuntimeError('from 2')
  PassError(1): exiting
捕捉错误 from 2
posted @ 2020-06-12 10:48  小粉优化大师  阅读(609)  评论(0编辑  收藏  举报