python笔记64 - with语法(__enter__和__exit__)
前言
with 语句适用于对资源进行访问的场景,在使用过程中如果发生异常需执行“清理”操作释放资源,比如常用的场景是with open打开文件操作。
with 打开文件场景
我们接触的第一个使用with的场景是用open函数对文件的读写操作,下面的代码是打开文件读取文件内容后用close关闭
fp = open('1.txt', 'r')
r = fp.read()
print(r)
fp.close()
上面的代码会有2个弊端:
1.如果在打开文件操作的过程中fp.read()出现异常,异常没处理
2.很多同学没用使用fp.close()关闭的操作的习惯,导致资源一直不会释放
所以推荐用下面这种with语法
with open('1.txt', 'r') as fp:
r = fp.read()
print(r)
学到这里我们都是死记硬背,只知道用with有这么2个好处,但不知道为什么这么用,也不知道其它的场景能不能用with?
with 使用原理
上下文管理协议(Context Management Protocol):包含方法 __enter__()
和__exit__()
,支持该协议的对象要实现这两个方法。
上下文管理器(Context Manager):
支持上下文管理协议的对象,这种对象实现了__enter__()
和__exit__()
方法。上下文管理器定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。
通常使用with语句调用上下文管理器,也可以通过直接调用其方法来使用。
with语句需当一个新的语法去学习,使用语法格式
with EXPR as Variable:
BLOCK
其中EXPR可以是任意表达式;as Variable是可选的,as的作用类似于=赋值。其一般的执行过程是这样的:
执行EXPR,生成上下文管理器context_manager;
获取上下文管理器的
__exit()__
方法,并保存起来用于之后的调用;调用上下文管理器的
__enter__()
方法;如果使用了as子句,则将
__enter__()
方法的返回值赋值给as子句中的Variable;执行 BLOCK 中的代码块;
先看一个示例,使用with语法的时候,先调用__enter__
方法,再执行 BLOCK 代码块,最后调用__exit__
退出代码
class WithDemo(object):
def __init__(self):
self.name = "yoyo"
def start(self):
print("start")
def __enter__(self):
print("enter---")
def __exit__(self, exc_type, exc_val, exc_tb):
print("quit 退出代码")
with WithDemo() as a:
print("11")
运行结果
enter---
11
quit 退出代码
不管是否执行过程中是否发生了异常,执行上下文管理器的__exit__()方法,__exit__()方法负责执行“清理”工作,如释放资源等。
出现异常时,如果`__exit__`(self, exc_type, exc_val, exc_tb)返回False,则会重新抛出异常,让with之外的语句逻辑来处理异常,这也是通用做法;
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
class WithDemo(object):
def __init__(self):
self.name = "yoyo"
def start(self):
print("start")
return self
def __enter__(self):
print("enter---")
def __exit__(self, exc_type, exc_val, exc_tb):
print("quit 退出代码")
return False
with WithDemo() as a:
print("11")
a.start("a")
调用start方法出现异常,也会执行__exit__
,此方法返回False,于是就会抛出异常
enter---
11
quit 退出代码
Traceback (most recent call last):
File "D:/wangyiyun_hrun3/demo/c.py", line 18, in <module>
a.start("a")
AttributeError: 'NoneType' object has no attribute 'start'
如果返回True,则忽略异常,不再对异常进行处理。
def __exit__(self, exc_type, exc_val, exc_tb):
print("quit 退出代码")
return True
返回True的时候,不会抛出异常
allure.step使用场景
在使用pytest+allure报告的时候,经常会看到在用例里面使用with allure.step方法
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import pytest
import allure
import requests
@pytest.fixture(scope="session")
def session():
s = requests.session()
yield s
s.close()
def test_sp(s):
with allure.step("step1:登录"):
s.post('/login', json={})
with allure.step("step2:添加商品"):
s.post('/goods', json={})
with allure.step("step3:查询商品id"):
s.get('/gooods/1')
assert 1 == 1
可以看下allure.step()方法的源码
def step(title):
if callable(title):
return StepContext(title.__name__, {})(title)
else:
return StepContext(title, {})
返回的是StepContext类的实例对象
class StepContext:
def __init__(self, title, params):
self.title = title
self.params = params
self.uuid = uuid4()
def __enter__(self):
plugin_manager.hook.start_step(uuid=self.uuid, title=self.title, params=self.params)
def __exit__(self, exc_type, exc_val, exc_tb):
plugin_manager.hook.stop_step(uuid=self.uuid, title=self.title, exc_type=exc_type, exc_val=exc_val,
exc_tb=exc_tb)
在StepContext类里面定义了__enter__
和__exit__
方法
于是有同学就想当然是不是也可以allure.title(),这个是不对的。
其它使用场景
使用python的requests模块发请求
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import requests
s = requests.Session()
r = s.get("https://www.cnblogs.com/yoyoketang/")
print(r.status_code)
s.close()
在Session类也可以看到定义了__enter__
和__exit__
方法
class Session(SessionRedirectMixin):
"""A Requests session.
Provides cookie persistence, connection-pooling, and configuration.
Basic Usage::
>>> import requests
>>> s = requests.Session()
>>> s.get('https://httpbin.org/get')
<Response [200]>
Or as a context manager::
>>> with requests.Session() as s:
... s.get('https://httpbin.org/get')
<Response [200]>
"""
__attrs__ = [
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
'cert', 'adapters', 'stream', 'trust_env',
'max_redirects',
]
def __init__(self):
......
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
在源码里面是可以看到with语法的使用示例
>>> with requests.Session() as s:
... s.get('https://httpbin.org/get')
<Response [200]>
于是也可以这样写
with requests.Session() as s:
r = s.get("https://www.cnblogs.com/yoyoketang/")
print(r.status_code)
参考资料:https://www.cnblogs.com/pythonbao/p/11211347.html
参考资料:https://blog.csdn.net/u012609509/article/details/72911564