玩转python中with的使用与上下文管理器

        人是随着时间不断进化而来的,同样编程语言也是随着IT行业的更新换代,功能模块不断地优化与丰富才壮大起来的。比如在python2.5之前使用open读写文件操作就要注意。比如Python 程序打开一个文件后,往文件中写内容,写完之后,就要关闭该文件,否则会出现什么情况呢?极端情况下会出现 "Too many open files" 的错误,因为系统允许你打开的最大文件数量是有限的,默认打开文件最大文件数1024。

       所以实际开发中要对可能发生异常的代码处进行 try 捕获,使用 try/finally 语句,因为如果在 try 代码块中程序出现了异常,后续代码就不再执行,而直接跳转到 except 代码块,但finally 块的代码无路如何最终都会被执行。因此,只要把 close 放在 finally 代码中,文件就一定会关闭,解决了这种隐藏的问题。

1.使用open读写文件,try捕捉异常处理,finally关闭文件

def f1():
    f = open("aaa.txt", "w+")
    try:
        f.write("hello ,world")
    except IOError:
        print("io 异常了啦")
    finally:
        f.close()
f1()

  使用上面处理当然没有任何问题,但在python2.5以后,基于之前的try....except....finally增加了一个功能更加简洁的方式with关键字。with语句相对try/finally来说简洁了很多,而且也不需要每一个用户都去写f.close()来关闭文件了。

2.使用with关键字进行文件操作

def m2():
    with open("aaaa.txt","w+") as f : #open 方法的返回值给变量f,所以这里f可以自定义名称
        f.write("hahhahaha")
m2()
  1.  open 方法的返回值赋值给变量 f,这里f只是变量名,指向open返回值的引用。
  2.  当离开 with 代码块的时候,系统会自动调用 f.close() 方法, with 的作用和使用 try/finally 语句是一样的,所以这里不用手动写close()了

      上面那么为什么with可以实现这么强大的功能呢,即替代了try...finally,不用捕捉异常了,也不用手动调用close了,要想弄明白这个问题,先了解下python中的上下文管理器(Context Manager)。

3.什么是上下文管理器

       查看官网看是说:任何实现(重写)了 __enter__() 和 __exit__() 方法的对象都可称之为上下文管理器,搞不清context manager,内容管理器,为啥翻译成上下文管理器。使用pycharm可以查看原来__enter__() 和 __exit__() 是object类中自带的魔法方法。

      细心的人用ctrl键打开查看该方法的源码,发现打开不了,这是为啥子呢?因为python的解释器底层是用c语言写的(也有用java的)。所以底层很多方法,都是查看不了源码的。要想查看源码可以到github上搜搜。但是一些导入的python库,用python实现的,是可以直接查看源码的。

       所谓的上下文管理器其实是一个遵守了context management protocol 协议的对象就是一个对象,一个重写了object类中__enter__() 和 __exit__() 方法的对象。这样的对象被称为上下文管理器Content Manager,它可以使用with关键字进行操作这个对象。

3.1 with语句使用的格式

with expression [as variable]:
    with-block
----------------------------------------------------------------------------
案例1:
 with open("aaaa.txt","w+") as f : #open方法的返回值给变量f,相当于起个别名。
        f.write("hahhahaha")
  1. 这里expression是一个表达式,该表达式的结果必须是一个支持上下文管理协议的对象(即具有__enter__()和__exit__()方法的对象),比如案例中操作文件就是用的open("aaaa.txt","w+"),这个表达式返回的结果就是file对象。具体返回的结果根据打开模式不同,返回值也不同,比如当open()用于以文本模式('w')打开文件时,它返回一个TextIOWrapper。那么这么说,既然open()可以用with进行操作,底层必然实现了上下文管理器协议的。查看官网可知。一些标准Python对象现在都支持上下文管理协议,并且可以与with语句一起使用。文件对象就是一个例子。  
  2. 既然支持上下文管理协议,也就是实现底层实现了__enter__()和__exit__()方法。对象的__enter__()方法在with-block执行之前被调用,因此可以在类重写的__enter__方法里根据实际需求添加特定条件的过滤代码,__enter__方法。具体使用后面再说。
  3. 在with-block 块的执行完成后,会调用对象的_exit__()方法,即使with-block块执行中引发异常,也可以运行这个__exit__方法,执行一些特定的操作(类似于try finally,finally无论是否有异常,都会执行其中的代码)。比如文件对象就在这个__exit__方法里调用了close()函数,这样不管open操作是否异常,文件最终都会关闭。

3.2自定义一个实现了上下文管理器协议的对象:上下文管理器

     根据官网定义很简单,只要该类重写了object类中__enter__()和__exit__()方法即可。

#自定义一个上下文管理器,实现原open()函数的功能
class MyOpen(object):

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("entering")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, *args):
        print("will exit")
        self.f.close()

#下面使用自定义的上下文管理器,
#1.首先MyOpen("bbbb.txt","w") 先执行__init__,完成初始化,返回的是一个支持上下文管理器协议的对象给f
#2.然后执行__enter__函数,该函数执行open()方法操作文件,并且返回这个file对象。
#3.根据返回的file对象f,这个时候执行f.f.write("hello ,hahahhah")
#4.不管第三步是否异常,最后都会调用__exit__执行文件的close.
with MyOpen("bbbb.txt","w") as f:
    f.write("hello ,hahahhah")

4.实现上线文管理器的其他方式

          Python 还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 __enter__ 方法中执行,yield 之后的语句在 __exit__ 方法中执行。紧跟在 yield 后面的值是函数的返回值。具体详细参考python官网:https://docs.python.org/release/2.6/whatsnew/2.6.html#pep-343-the-with-statement

from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
    f = open(path, mode)
    yield f
    f.close()

使用演示:

with my_open('out.txt', 'w') as f:
    f.write("hello , the simplest context manager")

尖叫提示: 

          Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。但是实际开中,一般不需要不要开发者掌握with的实现原理,会使用即可哈。

发布了248 篇原创文章 · 获赞 1600 · 访问量 267万+

猜你喜欢

转载自blog.csdn.net/qq_26442553/article/details/95635775