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

posted @ 2021-08-21 11:06  上海-悠悠  阅读(677)  评论(0编辑  收藏  举报