qiuyouzhi 发表于 2020-6-7 07:17:31

Python with 语句详解

本帖最后由 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__ 滴!

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

以下解释转载自:
https://www.cnblogs.com/gengyufei/p/11341853.html

object.__enter__(self)

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

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

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

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

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

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

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

自己定义一个类,里面包含上下文管理器:

class Test:
    def func(self):
      print("I amFUNC !")

    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 amFUNC !
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 amFUNC !")

    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())


运行结果:

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, temp, temp)
      if not ex: raise e
    else:
      cls.__exit__(None, None, None)

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

**** Hidden Message *****

用法:

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

比如用于pynput.Listener:

WITH2(Listener(on_press = chk), "tester.join()")

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

qiuyouzhi 发表于 2020-6-7 07:20:05

@小甲鱼 申精~
不知道有转载部分还可不可以申精了

qiuyouzhi 发表于 2020-6-7 07:41:13

注:
我写的 with 还是很简陋的,各位大神们可以试试写个更强大的!

Hello. 发表于 2020-6-7 07:44:27

支持{:10_256:}

qiuyouzhi 发表于 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, temp, temp)
            if not ex: raise e # __exit__不是True就报错
      else: cls.__exit__(None, None, None) # 没问题参数就直接填None

如果这个参数为eval,则以交互模式(不print也能出结果),exec则是标准形式,需要用print。

_荟桐_ 发表于 2020-6-7 08:24:10

奥利给!

Twilight6 发表于 2020-6-7 08:24:25

{:10_298:}牛~支持

WangJS 发表于 2020-6-7 08:47:09

{:10_298:}牛~支持

liuzhengyuan 发表于 2020-6-7 12:16:48

{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持
{:10_298:}牛~支持

s0mnus 发表于 2020-6-7 14:41:25

666

小甲鱼 发表于 2020-6-8 00:32:48

qiuyouzhi 发表于 2020-6-7 07:20
@小甲鱼 申精~
不知道有转载部分还可不可以申精了

内容不是很丰满,并且有一半是转的,嘻嘻,所以不合适加精哦~

Mike_python小 发表于 2020-6-11 18:42:47

小甲鱼 发表于 2020-6-8 00:32
内容不是很丰满,并且有一半是转的,嘻嘻,所以不合适加精哦~

支持 对了
您12点都没睡??{:7_145:}
页: [1]
查看完整版本: Python with 语句详解