前言
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()方法負責執行“清理”工作,如釋放資源等。
如果執行過程中沒有出現異常,或者語句體中執行了語句break/continue/return,則以None作為參數調用__exit__(None, None, None);
如果執行過程中出現異常,則使用sys.exc_info得到的異常信息為參數調用__exit__(exc_type, exc_value, exc_traceback);
出現異常時,如果__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