上下文管理器

[TOC]

第二章 上下文管理器

2.1 上下文管理器的定义

  • 上下文管理器是一个包装任意代码块的对象

  • 上下文管理器保证进入上下文管理器时, 每次代码执行的一致性

  • 当退出上下文管理器时, 相关的资源会被正确回收

  • 上下文管理器保证退出步骤的执行

2.1.1 与装饰器的异同

与装饰器类似, 它们都是包装其他代码的工具. 装饰器包装用于定义的代码块(函数/类), 上下文管理器可以包装任意格式的代码块.

2.1.2 用途

  • 作为确保资源被正确清理的一种方式

2.2 语法

2.2.1 with语句

使用with语句可以进入上下文管理器.

两段功能相同的代码

# 代码段一
try:
    my_file = open('filename', 'r')
    contents = my_file.read()
finally:
    my_file.close()

# 代码段二
with open('filename', 'r') as my_file:
    contents = my_file.read()
  • 前提: open()支持with语法

  • 实际上是with语句对其后代码进行求值(在代码段二中, 就是调用open函数). 该表达式会返回一个对象, 该对象包含两个特殊方法: __enter____exit__. __enter__方法返回的结果被复制给as关键字之后的变量

  • 使用上下文管理器重要原因是避免代码重复

2.2.2 __enter____exit__方法

__enter__方法

__enter__方法只接受self参数. 当对象返回时该方法立即执行.

__exit__方法

__exit__方法带有3个位置参数(不包括self):

  • 一个异常类型

  • 一个异常实例

  • 一个回溯

如果没有异常, 三个参数全被设置为None, 如果出现异常, 则参数被赋值.

简单示例

代码

# coding: utf-8
import sys

print("Python版本信息: ", sys.version)


class ContextManger(object):
    def __init__(self):
        self.entered = False
        pass

    def __enter__(self):
        self.entered = True
        return self
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.enabled = False
    pass

cm = ContextManger()
print(cm.entered)        # False

with cm:
    print(cm.entered)    # True

with ContextManger() as cm2:
    print(cm2.entered)    # True

输出

Python版本信息:  3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
False
True
True

2.2.3 异常处理

  • 上下文管理器必须定义__exit__方法, 该方法可以选择性地处理包装代码块中出现的异常, 或者处理其他需要关闭- 上下文管理器状态的事情.

  • __exit__方法必须定义三个位置参数(不包括self参数)

  • 三种处理方法:

    • 传播异常: 让__exit__方法返回False

    • 终止异常: 让__exit__方法返回True

    • 抛出不同异常

2.3 编写上下文管理器的集中情形

2.3.1 资源清理

确保出现异常时正确关闭资源可以避免很多僵尸进程.

2.3.2 异常处理

传播异常

代码

# coding: utf-8
import sys

print("Python版本信息: ", sys.version)


class BubbleExceptions(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val:
            print('Bubble up exception: %s.' % exc_val)
            pass
        return False
    pass


with BubbleExceptions():
    print(5 + 5)

with BubbleExceptions():
    print(5 / 0)

输出

Python版本信息:  3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
10
Bubble up exception: division by zero.
Traceback (most recent call last):
  File "D:/workspace/Pycharm/PurePythonProject/Test.py", line 23, in <module>
    print(5 / 0)
ZeroDivisionError: division by zero

终止异常

代码

# coding: utf-8
import sys

print("Python版本信息: ", sys.version)


class BubbleExceptions(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val:
            print('Suppressing exception: %s.' % exc_val)
            pass
        return True
    pass


with BubbleExceptions():
    print(5 + 5)

with BubbleExceptions():
    print(5 / 0)

输出

Python版本信息:  3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
10
Suppressing exception: division by zero.

注意

  • 没有返回表达式5 / 0的值, 异常在计算该值的过程中引发, 从而触发__exit__的运行.

处理特定异常

代码

# coding: utf-8
import sys

print("Python版本信息: ", sys.version)


class HandleValueError(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_type:
            return True

        if issubclass(exc_type, ValueError):
            print('Handling ValueError: %s.' % exc_val)
            pass

        return False
    pass

with HandleValueError():
    raise ValueError('Value Error')

with HandleValueError():
    raise TypeError('Type Error')

输出

Python版本信息:  3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
Handling ValueError: Value Error.
Traceback (most recent call last):
  File "D:/workspace/Pycharm/PurePythonProject/Test.py", line 23, in <module>
    raise ValueError('Value Error')
ValueError: Value Error

基于属性的异常处理

上下文管理器可以根据异常的类型决定是否处理异常, 也可以根据异常的属性来决定是否处理异常.

示例

# coding: utf-8
import sys

import subprocess

print("Python版本信息: ", sys.version)


class ShellException(Exception):
    def __init__(self, code, stdout='', stderr=''):
        self.code = code
        self.stdout = stdout
        self.stderr = stderr
        pass

    def __str__(self):
        return 'exit code %d - %s' % (self.code, self.stderr)
    pass


class AcceptableErrorCodes(object):
    def __init__(self, *error_codes):
        self.error_codes = error_codes
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # print(exc_val)
        if not exc_type:
            return True

        if not issubclass(exc_type, ShellException):
            return False

        if exc_val.code in self.error_codes:
            print('Handled exception:', exc_val)
            return True
        else:
            print('Throw Exception:', exc_val)
            return False


def run_command(command):
    proc = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    proc.wait()

    stdout, stderr = proc.communicate()
    # print(proc.returncode)
    if proc.returncode > 0:
        raise ShellException(proc.returncode, str(stdout), str(stderr))

    return stdout

with AcceptableErrorCodes(1):
    print(run_command('rm notExistFile'))

with AcceptableErrorCodes(2):
    print(run_command('rm -m notExistFile'))

输出

Python版本信息:  3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
Handled exception: exit code 1 - b"rm: cannot remove 'notExistFile': No such file or directory\n"
Throw Exception: exit code 1 - b"rm: unknown option -- m\nTry 'rm --help' for more information.\n"
Traceback (most recent call last):
  File "D:/workspace/Pycharm/PurePythonProject/Test.py", line 60, in <module>
    print(run_command('rm -m notExistFile'))
  File "D:/workspace/Pycharm/PurePythonProject/Test.py", line 52, in run_command
    raise ShellException(proc.returncode, str(stdout), str(stderr))
__main__.ShellException: exit code 1 - b"rm: unknown option -- m\nTry 'rm --help' for more information.\n"

说明

  • 根据命令执行返回代码判断是否处理异常

最后更新于

这有帮助吗?