查看原文
其他

python小课堂39 - 用 with 优雅的读写文件

咪咪怪 咪哥杂谈 2019-10-30
咪哥杂谈

python小课堂39

用 with 优雅的读写文件


本篇阅读时间约为 5 分钟。


1

前言


本篇来介绍一下 Python 中的关键词 with 的小技巧。但是在了解 with 之前,需要先了解一下如何使用 Python 对文件进行读写操作。


在了解基本的文件读写操作后,在使用 with 对其进行优雅的操作。写出符合 Pythonic 的代码。


2

对文件的读写操作


1. 读文件


在 Python 中,有一个函数 open ,就像英语中描述的一样,具有打开的意思,先来看下函数详情:


open(name[, mode[, buffering]]) 


name : 一个包含了你要访问的文件名称的字符串值。  


mode : mode 决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读(r)。 


buffering : 如果 buffering 的值被设为 0,就不会有寄存。如果 buffering 的值取 1,访问文件时会寄存行。如果将 buffering 的值设为大于 1 的整数,表明了这就是的寄存区的缓冲大小。如果取负值,寄存区的缓冲大小则为系统默认。

菜鸟教程


首先,在 py 文件的同目录下创建一个1.txt的文本,对其进行读取:



只需要,两行代码,即可操作文件:


f = open('./1.txt', 'r', encoding='utf-8') # 获取文件对象print(f.read()) # 读取文件内容f.close() # 释放资源



解释下代码,通过 open 函数获得到一个文件对象,其中第一个参数传入文件路径,第二个参数使用 ‘r’ (read) 模式,第三个参数是使用utf-8 编码进行文件读取。读取完成后,需要关闭资源。


注意:如果你指定的文件路径不存在,则会报错 IOError 错误,可自行尝试。


2. 写文件


写文件,只需要改变模式参数即可,其余参数不需要改变,代码如下:


f = open('./1.txt', 'w', encoding='utf-8') # 写文件f.write('abdc')  # 具体什么内容写到文件f.close() # 关闭资源


写完后的结果:



写完后,可以发现,新内容将原来的中文覆盖掉了。所以若想在源文件后追加,可以将 'w'(write) 改为 'a'(append) 即可。


关于具体模式参数,可以自行查阅菜鸟教程,或者通过 Pycharm 按住 Ctrl 点击鼠标左键,看查 open() 函数中的源码注释来使用,故不作详细介绍了:



3. 读写文件异常处理


关于操作资源类的代码,需要注意的是必须要对资源进行释放,比如读写文件,在使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的,所以你会看到上述代码中,需要用 f.close() 进行关闭。


对于读写文件来说,非常容易发生 IOError 这样的异常,改进下代码,正常写法:


try:  f = open('./1.txt', 'r', encoding='utf-8') print(f.read())except Exception as e:    raise e      # 抛出异常finally: if f: print('file close') f.close()


使用 try - except - finally 语句来捕获异常,若有异常进行抛出,无论是否有异常,最终将文件对象进行关闭。


可以自行制造一个异常来检验下,最终是否会走入 finally 中:



手动制造一个 1/0 的错误,可以看到,即使程序发生了异常,最后的代码也会走入。


3

使用 with 优雅的操作文件


以上正常的写法,大家有没有觉得有一种冗余的感觉,并且每次这么写会非常麻烦,一点也不符合 Python 的简洁特点。人生苦短,我用 Python,这句话不是白说的!所以 Python 提供了一个 with 关键词可以自行管理资源,比如在程序结束后,自动关闭文件资源。


代码写法:


with open('./1.txt', 'r', encoding='utf-8') as f: print(f.read()) # 控制台输出>>> abdc


仅仅两行代码,实现了 try - except - finally 的方法, 使用 with 对文件对象进行 as 别名处理,具体逻辑中,操作 f 即可,最终 with 会帮我们调用资源关闭的方法。


4

with 背后的工作原理


with 背后的工作原理,要涉及到两个 Python 的魔术方法(Magic method)了,__enter__() 和 __exit__()在 Python 中,任何对象实现了这两个魔术方法,都可以被称作上下文管理器。而正是这样的管理器,来使用 with 关键词实现代码中的简洁编写。


下面来看个例子,我们可以手写一个类似文件对象的类,封装 open 函数:


class MyOpen:
def __init__(self, filename, mode, encoding): """ 对象初始化,传入路径,模式,编码 """ self.filename = filename self.mode = mode self.encoding = encoding
def __enter__(self): """ 准备工作 """ print('----- Connect File -----') self.f = open(self.filename, self.mode) return self.f
def __exit__(self, exc_type, exc_val, exc_tb): """ 退出工作 """ print('----- Release File ------')        self.f.close()
with MyOpen('./1.txt', 'r', encoding='utf-8') as f: print(f.read())



使用 with 关键词创建自己的类时,相当于在 enter 函数中,封装了 open 函数方法,将 open 函数实例化对象进行返回,便是 as 得到的实例对象 f,然后调用 f 文件对象的 read 方法,最终程序结束,文件资源被释放。


假设,在 enter 返回时,我们不返回文件对象 f ,而返回的是 self ,也就是 MyOpen 的实例对象,那么结果会怎么样呢?



看到了,因为 MyOpen 类根本没有 read 的方法,所以此时, as f代表的含义是 MyOpen 的实例对象,意味着 enter 的返回决定着 with ... as ... 生成什么样的实例对象。


改一下的结果:



再多思考一下,如果发生异常会是怎样的呢?依然是手动制造一个异常,1/0 即可,同时在 exit 中还有三个参数,分别打印看下结果:


class MyOpen:
def __init__(self, filename, mode, encoding): """ 对象初始化,传入路径,模式,编码 """ self.filename = filename self.mode = mode self.encoding = encoding
def __enter__(self): """ 准备工作 """ print('----- Connect File -----') self.f = open(self.filename, self.mode) return self
def __exit__(self, exc_type, exc_val, exc_tb): """ 退出工作 """ print('----- Release File ------') self.f.close() print(f'exc_type:{exc_type}') print(f'exc_val:{exc_val}') print(f'exc_tb:{exc_tb}')

with MyOpen('./1.txt', 'r', encoding='utf-8') as myOpen: 1 / 0 print(myOpen.f.read())



先来看,即使发生了异常,我们的资源还是被成功释放了,符合了 with 关键字发生异常时会自动关闭的机制。


其次,打印出的 exit 函数中的三个参数,分别是 异常类型、异常值信息,还有 traceback (异常堆栈)。但是若不发生异常,这三个参数则都是 None ,可以自行尝试把异常抹去后,看看打印结果。


额外说的一点,关于 exit 函数的返回值,决定着是否向外抛出异常,什么意思呢?来看下面代码示例:


class MyOpen:
def __init__(self, filename, mode, encoding): """ 对象初始化,传入路径,模式,编码 """ self.filename = filename self.mode = mode self.encoding = encoding
def __enter__(self): """ 准备工作 """ print('----- Connect File -----') self.f = open(self.filename, self.mode) return self
def __exit__(self, exc_type, exc_val, exc_tb): """ 退出工作 """ print('----- Release File ------') self.f.close() return True

try: with MyOpen('./1.txt', 'r', encoding='utf-8') as myOpen: 1 / 0 print(myOpen.f.read())except Exception as e: print(e)



通过手动制造一个错误,使 exit 返回 True,可以看到,在 with 处捕获异常,没有打印出异常。说明 exit 的返回值控制着 with 语句发生异常时,是否抛出异常。默认 exit 返回值是 None,而 None 在 Python 中的值就是 False。所以默认情况下,有异常时,控制台会抛出异常提示。


5

总结


在对文件进行操作时,使用 with 这种 pythonic 的方式来代替 try - except - finally,省时经济又实惠,而 with 背后的工作原理则是 Python 上下文管理器的 2 个魔术方法上的实现。在我们日常写操作资源类时,为了达到优雅的方式,不妨用上下文管理器的来实现自定义类,最终使其达到 pythonic 风格!



至此完!




▼往期精彩回顾▼关于 *args,**kwargs 的作用if __name__ = '__main__的理解
如何零基础自学Python?


长按关注

公众号名称:咪哥杂谈

一个咪咪怪的公众号

长按二维码关注哦!


你点的每个赞,我都认真当成了喜欢


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存