Python进阶特性(上下文管理器、With语句)

1.3 Python进阶特性(上下文管理器)

在Python中,有些操作可能会出现错误,但是开发者希望出现错误后也能够进行一些必要地收尾操作,比如:文件的关闭、断开数据库连接、相关数据的清理等等。这时,我们可以通过try…except语句中的finally子语句进行处理。
其实在Python中,with语句可以更高效的处理这种事情。with语句,with语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文。通过上下文管理器,可以实现自动分配并释放资源的功能。
上下文管理器对象用来管理 with语句,这就像迭代器管理 for 语句一样。
with 语句的语法格式如下

with 表达式 [as变量名]:
	代码块

上面[]内的可以省略,如果未省略,则代表将前面的表达式的结果(即:上下文管理器对象的值)绑定到变量名中,方面在代码块中调用。
上下文管理器这一功能最常见的用法是在操作文件中。在没有使用上下文管理器时,操作文件的代码如下:

1.3.1 未使用上下文管理器操作文件

from icecream import ic

file = open('text.txt', mode='r', encoding='utf-8')
try:
ic(file.read())
	...
finally:
    file.close()

ic| file.read(): ‘i love python!’

1.3.2 使用上下文管理器操作文件

from icecream import ic

with open('text.txt', mode='r', encoding='utf-8') as f:
ic(f.read())
...

ic| file.read(): ‘i love python!’

上面的文件操作是上下文管理器最常见的用法,其实,我们可以自己创建一个支持上下文管理器的类,通过该类,我们可以进一步了解上下文管理器的工作原理。
要让类能实现上下文管理器,只需要在创建的类中实现__enter____exit__两个方法即可。
进入上下文管理器时,调用__enter__方法,并将该方法的返回值绑定到as后的变量上。一般会直接返回管理器对象,方便后续调用。
不管以何种方式(遇到异常、with语句块代码运行完毕)退出上下文管理器,在退出时都会先调用__exit__方法。该方法有3个参数,这些参数用于异常处理,参数含义如下:
type:异常类
value:异常值
trace:调用栈信息
上下文管理中,如果出现异常,则可以通过该方法捕获,如果捕获后,该方法返回False,则这个异常还会向上抛出,如果返回True,则不会再向上抛出该异常。

1.3.3 创建上下文管理器对象

from icecream import ic
import time


class Person:
    def __init__(self):
        time.sleep(1)
        ic('__init__ 方法被调用啦!')

    def __enter__(self):
        time.sleep(1)
        ic("__enter__ 方法被调用啦!")
        return self

    def __exit__(self, err_of_type, err_of_value, err_of_trace):
        time.sleep(1)
        ic("__exit__ 方法被调用啦!")
        ic(err_of_type, err_of_value, err_of_trace)
        if err_of_type is ZeroDivisionError:
            ic("Zero value Error!")
        elif issubclass(err_of_type, Exception):
            pass
        return True


p = Person()
ic(type(p), p)
with Person() as person:
    time.sleep(1)
    ic(type(person), person)
    raise ZeroDivisionError("zero error!")
    ic('done')
ic('end') 

16:59:39|> t1.py:8 in init()- ‘init 方法被调用啦!’
16:59:39|> t1.py:27 in
type§: <class ‘main.Person’>
p: <main.Person object at 0x000001C2FA2E0790>
16:59:40|> t1.py:8 in init()- ‘init 方法被调用啦!’
16:59:41|> t1.py:12 in enter()- ‘enter 方法被调用啦!’
16:59:42|> t1.py:30 in
type(person): <class ‘main.Person’>
person: <main.Person object at 0x000001C2FB1E2C40>
16:59:43|> t1.py:17 in exit()- ‘exit 方法被调用啦!’
16:59:43|> t1.py:18 in exit()
err_of_type: <class ‘ZeroDivisionError’>
err_of_value: ZeroDivisionError(‘zero error!’)
err_of_trace: <traceback object at 0x000001C2FB1DBE80>
16:59:43|> t1.py:20 in exit()- ‘Zero value Error!’
16:59:43|> t1.py:33 in - ‘end’

在with语句块中,如果有异常,那么会先调用上下文管理器中的__exit__方法,在这个方法中我们可以捕获异常并处理,但是,with语句块后续的代码则不会被运行了,也就是结束了整个with语句。所以,with语句块在可能出现异常的后续代码可以写在__exit__方法中捕获相应的异常之后。

1.3.4 上下文管理器相关工具

在Python标准库中,contextlib这个库可以提供一些上下文管理器相关工具。

1.3.4.1 @closing函数

如果对象提供了 close() 方法,但没有实现__enter____exit__方法,那么可以使用这个函数构建上下文管理器。

from icecream import ic
from contextlib import closing
import time


class Person:
    def __init__(self):
        time.sleep(1)
        ic('__init__ 方法被调用啦!')

    def close(self):
        time.sleep(1)
        ic("__exit__ 方法被调用啦!")
        return True


with closing(Person()) as person:
    time.sleep(1)
    ic(type(person), person)
    raise ZeroDivisionError("zero error!")
    ic('done')
ic('end')

17:08:31|> test.py:9 in init()- ‘init 方法被调用啦!’
17:08:32|> test.py:19 in
type(person): <class ‘main.Person’>
person: <main.Person object at 0x0000025D0C9C0790>
17:08:33|> test.py:13 in close()- ‘exit 方法被调用啦!’
Traceback (most recent call last):
File “E:/BaiduNetdiskWorkspace/FrbPythonFiles/test.py”, line 20, in
raise ZeroDivisionError(“zero error!”)
ZeroDivisionError: zero error!

对比正常的上下文管理器,其跳过了__enter__方法,在退出时调用了close方法。

1.3.4.2 @contexmanager装饰器

这个装饰器可以把简单的生成器函数装饰成上下文管理器,这样就不用单独去创建上下文管理器类了。

from icecream import ic
from contextlib import contextmanager
import time


@contextmanager
def myfun():
    ic("begin")
    yield 1
    ic("end")


with myfun() as mf:
    time.sleep(1)
    ic(type(mf), mf)
    ic('done')
ic('ending')

17:21:39|> test.py:8 in myfun()- ‘begin’
17:21:41|> test.py:15 in - type(mf): <class ‘int’>, mf: 1
17:21:41|> test.py:16 in - ‘done’
17:21:41|> test.py:10 in myfun()- ‘end’
17:21:41|> test.py:17 in - ‘ending’

1.3.4.3 ContextDecorator基类

ContextDecorator用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。

from icecream import ic
from contextlib import ContextDecorator
import time


class Person(ContextDecorator):
    def __enter__(self):
        ic('begin')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        ic('exit')
        return True


@Person()
def myfun():
    ic('myfun')


myfun()

17:29:28|> test.py:8 in enter()- ‘begin’
17:29:29|> test.py:18 in myfun()- ‘myfun’
17:29:29|> test.py:12 in exit()- ‘exit’

1.3.5 带括号的上下文管理器

Python1.10已支持使用外层圆括号来使多个上下文管理器可以连续多行地书写。 这允许将过长的上下文管理器集能够以与之前 import 语句类似的方式格式化为多行的形式。 例如,以下这些示例写法现在都是有效的:

with (CtxManager() as example):
    pass

with (
    CtxManager1(),
    CtxManager2()
):
    pass

with (CtxManager1() as example,
      CtxManager2()):
    pass

with (CtxManager1(),
      CtxManager2() as example):
    pass

with (
    CtxManager1() as example1,
    CtxManager2() as example2
):
    pass

在被包含的分组末尾过可以使用一个逗号作为结束:

with (
    CtxManager1() as example1,
    CtxManager2() as example2,
    CtxManager3() as example3,
):
    pass

猜你喜欢

转载自blog.csdn.net/crleep/article/details/130030005
今日推荐