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()方法負責執行“清理”工作,如釋放資源等。
如果執行過程中沒有出現異常,或者語句體中執行了語句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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM