鱼C论坛

 找回密码
 立即注册
查看: 1510|回复: 11

[技术交流] Python with 语句详解

[复制链接]
发表于 2020-6-7 07:17:31 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 qiuyouzhi 于 2020-6-7 07:43 编辑

Python with 语句


大部分人对with的认识我想是这样:

"with 是什么?

" with open("record.txt", 'w') as f:
        # blablabla"

with 其实是一个上下文管理器,

而open 里面正好定义了这俩管理器,也就是 __enter__() 和 __exit__()。

with 后面的对象必须要有这俩玩意,没有就报错。

比如这样:
>>> with "asdasdasd" as test:
        pass

Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    with "asdasdasd" as test:
AttributeError: __enter__

报错是因为字符串类型是没有 __enter__ 滴!

然后来给大家分析下这俩东西:



object.__enter__(self)

进入与此对象相关的运行时上下文。with语句将将此方法的返回值绑定到语句的as子句中指定的目标(如果有设置的话)

object.__exit__(self, exc_type, exc_value, traceback)

退出与此对象相关的运行时上下文。参数描述导致上下文退出的异常。如果上下文运行时没有异常发生,那么三个参数都将置为None。

如果有异常发生,并且该方法希望抑制异常(即阻止它被传播),则它应该返回True。否则,异常将在退出该方法时正常处理。

注意:
__exit__()方法不应该重新抛出传入的异常,这是调用者的职责。

------------------------------------------分割线------------------------------------

基础知识都学完了,让我们来实践一下吧!

自己定义一个类,里面包含上下文管理器:
class Test:
    def func(self):
        print("I am  FUNC !")

    def __enter__(self):
        # __enter__通常都是返回self,用于对这个实例对象进行下一步操作,
        # 后面会进行详细讲解。
        print("I am __ENTER__ !")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("self:", self)
        print("exc_type:", exc_type)
        print("exc_value:", exc_value)
        print("traceback:", traceback)
        # return True

with Test() as tester:
    tester.func()

运行结果:
I am __ENTER__ !
I am  FUNC !
self: <__main__.Test object at 0x000001D91342DA30>
exc_type: None
exc_value: None
traceback: None

不难发现,with语句就是先调用 __enter__,然后执行缩进内的内容,

最后调用 __exit__ 退出。

那为什么这些 exc_type, exc_value 啥的都是None呢?

因为没有报错,所以它们默认为None。我们稍微修改下代码:
with Test() as tester:
    tester.asdasdasd()

运行结果:
I am __ENTER__ !
self: <__main__.Test object at 0x00000216DAA97A30>
exc_type: <class 'AttributeError'>
exc_value: 'Test' object has no attribute 'asdasdasd'
traceback: <traceback object at 0x00000216DAAF7C80>
Traceback (most recent call last):
  File "C:\Users\rzzl\Desktop\test2.py", line 19, in <module>
    tester.asdasdasd()
AttributeError: 'Test' object has no attribute 'asdasdasd'

可以发现正常报错了,这时候,我们把那个 __exit__ 里面的 return True 放出来,看看效果:
I am __ENTER__ !
self: <__main__.Test object at 0x000002BAE60D7A30>
exc_type: <class 'AttributeError'>
exc_value: 'Test' object has no attribute 'asdasdasd'
traceback: <traceback object at 0x000002BAE5FEE180>

啊哈!不报错了!

所以,我们可以简单猜想下open函数的上下文管理器:
def __enter__(self):
    return self

def __exit__(self, exc_type, exc_value, traceback):
    self.close()

-----------------------------------------分割线------------------------------------------

__enter__ 的返回值可以是任何东西,比如一个字符串:
class Test:
    def func(self):
        print("I am  FUNC !")

    def __enter__(self):
        # __enter__通常都是返回self,用于对这个实例对象进行下一步操作,
        # 后面会进行详细讲解。
        print("I am __ENTER__ !")
        return "string"

    def __exit__(self, exc_type, exc_value, traceback):
        print("self:", self)
        print("exc_type:", exc_type)
        print("exc_value:", exc_value)
        print("traceback:", traceback)
    

with Test() as tester:
    print(tester.upper())[code]


运行结果:

[code]I am __ENTER__ !
STRING
self: <__main__.Test object at 0x00000224763D7A30>
exc_type: None
exc_value: None
traceback: None

符合预期。

------------------------------------------分割线------------------------------------

掌握了以上的知识,我们可以试着自己写个 with(用函数的方法实现)!

直接上代码:

(注意,本函数需要 sys 模块的 exc_info 函数,它返回的三元组正好是__exit__所需的参数。)
def WITH(cls,  *func):
    tester = cls.__enter__()
    try:
        for each in func:
            try:
                if (temp := eval(each)):
                    print(temp)
            except:
                continue
    except Exception as e:
        temp = sys.exc_info()
        ex = cls.__exit__(temp[0], temp[1], temp[2])
        if not ex: raise e
    else:
        cls.__exit__(None, None, None)

蒙了没?我想大部分人都不能一下子就看懂,要是真的能看明白,有鱼币奖励!

游客,如果您要查看本帖隐藏内容请回复


用法:

WITH(类名(), "要执行的方法")

比如用于pynput.Listener:
WITH2(Listener(on_press = chk), "tester.join()")

注意,这个实例名字必须是tester,除非你改WITH源代码

评分

参与人数 5荣誉 +15 鱼币 +11 贡献 +5 收起 理由
nizitao + 1
Twilight6 + 5 + 5 鱼C有你更精彩^_^
小甲鱼 + 2 + 2 + 2 鱼C有你更精彩^_^
WangJS + 3 + 3 + 3 支持大佬^_^
Hello. + 5 鱼C有你更精彩^_^

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-6-7 07:20:05 | 显示全部楼层
@小甲鱼 申精~
不知道有转载部分还可不可以申精了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-6-7 07:41:13 | 显示全部楼层
注:
我写的 with 还是很简陋的,各位大神们可以试试写个更强大的!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-6-7 07:44:27 | 显示全部楼层
支持
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-6-7 08:22:45 | 显示全部楼层
再次改进了一下,加了个参数:
class WITH2:
    def __init__(self, cls, run, *func):
        tester = cls.__enter__() # 先调用类的__enter__,返回一个实例化对象(这只是通常来讲,当然,__enter__也有可能返回个别的东西..)
        try:
            for each in func: # 遍历func元组,调用传入的函数
                try:
                    if (temp := run(each)): # 如果使用了没有返回值的函数,会导致打印一个None
                        print(temp)
                except:
                    continue
        except Exception as e: # 如果产生报错,则修改__exit__参数
            temp = sys.exc_info() # 返回个三元组,内容正好是__exit__所需参数
            ex = cls.__exit__(temp[0], temp[1], temp[2])
            if not ex: raise e # __exit__不是True就报错
        else: cls.__exit__(None, None, None) # 没问题参数就直接填None

如果这个参数为eval,则以交互模式(不print也能出结果),exec则是标准形式,需要用print。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-6-7 08:24:10 | 显示全部楼层
奥利给!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-6-7 08:24:25 | 显示全部楼层
牛~  支持
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-6-7 08:47:09 | 显示全部楼层
牛~  支持
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-6-7 12:16:48 | 显示全部楼层
牛~  支持
牛~  支持
牛~  支持
牛~  支持
牛~  支持
牛~  支持
牛~  支持
牛~  支持
牛~  支持
牛~  支持
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-6-7 14:41:25 | 显示全部楼层
666
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-6-8 00:32:48 | 显示全部楼层
qiuyouzhi 发表于 2020-6-7 07:20
@小甲鱼 申精~
不知道有转载部分还可不可以申精了

内容不是很丰满,并且有一半是转的,嘻嘻,所以不合适加精哦~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-6-11 18:42:47 | 显示全部楼层
小甲鱼 发表于 2020-6-8 00:32
内容不是很丰满,并且有一半是转的,嘻嘻,所以不合适加精哦~

支持 对了
您12点都没睡??
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-20 19:23

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表