python with语句与contextlib

枫铃3年前 (2021-09-30)Python186

with语句用于异常处理,适用于存在资源访问的场合,无论在资源访问的过程中是否发生异常,都会执行必要的清理操作,释放资源,比如文件打开后自动关闭、线程中锁的自动获取和释放

要明白with语句是怎样使用的,有必要引入一些上下文管理器的相关概念

上下文管理器、上下文管理协议、运行时上下文、上下文表达式、语句体

上下文管理器和上下文管理协议是两个相互关联的定义:具体来说,上下文管理协议是指两个方法:__enter__()__exit__(),支持该协议的对象必须实现这两个方法。而上下文管理器就是指支持上下文管理协议的对象。毫无疑问,这个对象实现了__enter__()__exit__()两个方法。

上下文管理器对象定义with语句运行时要建立的“运行时上下文”,负责with语句中上下文的进入和退出。通常使用with语句调用上下文管理器,也可以直接调用其方法来实现。

运行时上下文:__enter__()会在语句体执行之前进入“运行时上下文”,__exit__()会在语句体执行完毕后从运行时上下文退出

上下文表达式:跟在with后面的语句,是用来返回一个上下文管理器的

语句体:with语句中包裹的部分

with语句的语法格式:

with context_expression [as target(s)]:
    with-body

这里 context_expression 要返回一个上下文管理器对象,该对象并不赋值给 as 子句中的 target(s) ,如果指定了 as 子句的话,会将上下文管理器的 __enter__()方法的返回值赋值给 target(s)。

target(s) 可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)。

Python 对一些内建对象进行改进,加入了对上下文管理器的支持,可以用于 with 语句中,比如可以自动关闭文件、线程锁的自动获取和释放等。假设要对一个文件进行操作,使用 with 语句可以有如下代码:

with open(r'somefileName') as somefile:
    for line in somefile:
        print line
        # ...more code

这里使用了 with 语句,不管在处理文件过程中是否发生异常,都能保证 with 语句执行完毕后已经关闭了打开的文件句柄。如果使用传统的 try/finally 范式,则要使用类似如下代码:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
somefile = open(r'somefileName')
try:
    for line in somefile:
        print line
        # ...more code
finally:
    somefile.close()

比较起来,使用 with 语句可以减少编码量。已经加入对上下文管理协议支持的还有模块 threading、decimal 等。

contextlib

我们已经知道,要让一个对象用于with语句,就必须实现上下文管理,而实现上下文管理是通过__enter____exit__这两个方法实现的:

class Query(object):
 
    def __init__(self, name):
        self.name = name
 
    def __enter__(self):
        print('Begin')
        return self
     
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print('Error')
        else:
            print('End')
     
    def query(self):
 
with Query('Bob') as q:
    q.query()

但是这样有些麻烦,Python的内建模块contextlib能让我们的实现变得更简便

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
from contextlib import contextmanager
 
class Query(object):
 
    def __init__(self, name):
        self.name = name
 
    def query(self):
        print('Query info about %s...' % self.name)
 
@contextmanager
def create_query(name):
    print('Begin')
    q = Query(name)
    yield q
    print('End')
 
with create_query('Bob') as q:
    q.query()

contextlib提供的装饰器@contextmanger,这个装饰器接受一个generator,在生成器中用yield语句将想要用于with语句的变量输出出去。

加入我们想要在每次运行代码的前后都运行特定的代码,我们也可以选用@contextmanger这个装饰器实现

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield#这个yield调用会执行with语句中的所有语句,因此with语句中的所有内容都将会被运行
    print("</%s>" % name)
 
with tag("h1"):
    print("hello")
    print("world")
#这段代码的执行效果是:
<h1>
hello
world
</h1>

@closing

此外,如果一个对象没有实现运行时上下文,他就不能被应用到with语句当中,我们可以使用contextlib提供的@closing装饰器将其变为上下文对象

from contextlib import closing
from urllib.request import urlopen
 
with closing(urlopen('https://www.python.org')) as page:
    for line in page:
        print(line)

closing其实也是一个经过@contextmanger装饰的genterator,

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

它的作用就是将任意对象变为上下文对象,并支持with语句

奇怪的是,并不是所有的对象都能够通过closing()方法变为上下文对象:(错误是Query对象没有实现close()方法)

class Query(object):
    def __init__(self,name):
        self.name=name
...
    with closing(Query('bob')) as p:
        print(p.name)
#错误信息:
bob#仍然能输出
Traceback (most recent call last):
  File "c:\Users\Administrator.SC-201605202132\.vscode\extensions\ms-python.python-2019.9.34911\pythonFiles\ptvsd_launcher.py", line 43, in <module>
    main(ptvsdArgs)
  File "c:\Users\Administrator.SC-201605202132\.vscode\extensions\ms-python.python-2019.9.34911\pythonFiles\lib\python\ptvsd\__main__.py", line 432, in main
    run()
  File "c:\Users\Administrator.SC-201605202132\.vscode\extensions\ms-python.python-2019.9.34911\pythonFiles\lib\python\ptvsd\__main__.py", line 316, in run_file
    runpy.run_path(target, run_name='__main__')
  File "c:\users\administrator.sc-201605202132\appdata\local\programs\python\python36\Lib\runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "c:\users\administrator.sc-201605202132\appdata\local\programs\python\python36\Lib\runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "c:\users\administrator.sc-201605202132\appdata\local\programs\python\python36\Lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "c:\Users\Administrator.SC-201605202132\Envs\sort\app\forTest.py", line 26, in <module>
    print(p.name)
  File "c:\users\administrator.sc-201605202132\appdata\local\programs\python\python36\Lib\contextlib.py", line 185, in __exit__
    self.thing.close()
AttributeError: 'Query' object has no attribute 'close'
PS C:\Users\Administrator.SC-201605202132\Envs\sort>

相关文章

Python:赋值语句和布尔值

一、赋值语句 1、序列解包 多个赋值同时进行: >>> x,y,z = 1, 2, 3 >>>...

Python教程:hashlib加密

一、哈希 1. 什么是...

python函数调用的四种方式

第一种:参数按顺序从第一个参数往后排#标准调用 # -*- coding: UTF-8 -*- def normal_invoke(x,...

Python强大的格式化format

'{0},{1}'.format('kzc',18) 'kzc,18' '{},{}'....

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。