给Python学习者的文件读写指南(含基础与进阶)


Posted in Python onJanuary 29, 2020

对于初学者来说,一份详尽又清晰明白的指南很重要。今天,猫猫跟大家一起,好好学习Python文件读写的内容,这部分内容特别常用,掌握后对工作和实战都大有益处。学习是循序渐进的过程,欲速则不达。文章较长,建议大家收藏,以备复习查阅哦。

1、如何将列表数据写入文件?
2、如何从文件中读取内容?
3、多样需求的读写任务
4、从with语句到上下文管理器

如何将列表数据写入文件?

首先,我们来看看下面这段代码,并思考:这段代码有没有问题,如果有问题的话,要怎么改?

li = ['python',' is',' a',' cat']
with open('test.txt','w') as f:
  f.write(li)

现在公布答案,这段代码会报错:

TypeError  Traceback (most recent call last)
<ipython-input-6-57e0c2f5a453> in <module>()
      1 with open('test.txt','w') as f:
----> 2     f.write(li)

TypeError: write() argument must be str, not list

以上代码的想法是将list列表内容写入txt文件中,但是报错 TypeError: write() argument must be str。就是说,write()方法必须接受字符串(str)类型的参数。

Python中内置了str()方法,可以返回字符串版本的对象(Return a string version of object)。所以,上面的例子中,我们试试把 f.write(li) 改为 f.write(str(li)) ,先做一下字符串类型的转化看看。代码略。

这次没有报错了,但是打开文件就傻眼了吧,写入的内容是“['python',' is',' a',' cat']”。怎么才能写成“python is a cat”呢?

文件写操作还有一个writelines()方法,它接收的参数是由字符串组成的序列(sequence),实际写入的效果是将全部字符串拼接在一起。字符串本身也是一种序列,所以当参数是字符串的时候,writelines()方法等价于write()。

# 以下3种写法等价,都是写入字符串“python is a cat”
In [20]: with open('test.txt','w') as f:
  ...:   f.writelines(['python',' is',' a',' cat'])
  ...:   f.writelines('python is a cat')
  ...:   f.write('python is a cat')

# 以下2种写法等价,都是写入列表的字符串版本“['python',' is',' a',' cat']”
In [21]: with open('test.txt','w') as f:
  ...:   f.write(str(['python',' is',' a',' cat']))
  ...:   f.writelines(str(['python',' is',' a',' cat']))

# 作为反例,以下写法都是错误的:
In [22]: with open('test.txt','w') as f:
  ...:   f.writelines([2018,'is','a','cat']) # 含非字符串
  ...:   f.write(['python','is','a','cat']) # 非字符串

由上可知,当多段分散的字符串存在于列表中的时候,要用writelines()方法,如果字符串是一整段,那直接使用write()方法。如果要以整个列表的形式写入文件,就使用str()方法做下转化。

这个问题还没结束,如果列表中就是有元素不是字符串,而且要把全部元素取出来,怎么办呢?

那就不能直接使用write()和writelines()了,需要先用for循环,把每个元素取出来,逐一str()处理。

In [37]: content=[1,' is',' everything']
In [38]: with open('test.txt','w') as f:
  ...:   for i in content:
  ...:     f.write(str(i))

需要注意的是,writelines()不会自动换行。如果要实现列表元素间的换行,一个办法是在每个元素后面加上换行符“\n”,如果不想改变元素,最好是用for循环,在写入的时候加在末尾:for i in content:  f.writelines(str(i)+“\n”).

引申一下,经过实验,数字及元祖类型也可以作为write()的参数,不需转化。但是dict字典类型不可以,需要先用str()处理一下。字典类型比较特殊,最好是用json.dump()方法写到文件,具体操作方法以及注意事项,请看喵喵之前发的《假期玩得开心也不忘充电,学习Python操作JSON,网络数据交换不用愁》.

总结一下,write()接收字符串参数,适用于一次性将全部内容写入文件;writelines()接收参数是由字符串组成的序列,适用于将列表内容逐行写入文件。str()返回Python对象的字符串版本,使用需注意。

如何从文件中读取内容?

从文件中读取内容有如下方法:

file.read([size])
从文件读取指定的字节数,如果未给定或为负则读取所有。

file.readline([size])
读取整行,包括 "\n" 字符。

file.readlines([sizeint])
读取所有行并返回列表,若给定sizeint>0,则是设置一次读多少字节,这是为了减轻读取压力。

简而言之,在不传参数的情况下,read()对应write(),读取全部内容;readlines()对应writelines(),读取全部内容(含换行符)并以列表形式返回,每个换行的内容作为列表的一个元素。

In [47]: with open('test.txt','r') as f:
    ...:     print(f.read())
1 is everything.
python is a cat.
this is the end.

In [48]: with open('test.txt','r') as f:
    ...:     print(f.readlines())
['1 is everything.\n', 'python is a cat.\n', 'this is the end.']

但是,以上两个方法有个缺点,当文件过大的时候,一次性读取太多内容,会对内存造成极大压力。读操作还有一个readline()方法,可以逐行读取。

In [49]: with open('test.txt','r') as f:
    ...:     print(f.readline())
1 is everything.

readline()读取第一行就返回,再次调用f.readline(),会读取下一行。

喵喵,是否感觉跟《超强汇总:学习Python列表,只需这篇文章就够了》学习过的生成器很像,需要不停调用next()获取下一行。

这么看来,readline()太笨拙了。那么,有什么办法可以优雅地读取文件内容呢?

回过头来看readlines()方法,它返回的是一个列表。这不奇怪么,好端端的内容为啥要返回成列表呢?

再想想writelines()方法,把字符串列表写入文件正是这家伙干的事,readlines()方法恰恰是它的逆操作!而writelines()方法要配合for循环,所以我们把readlines()与for循环结合,看看会怎样。

In [61]: with open('test.txt','r') as f:
  ...:   for line in f.readlines():
  ...:     print(line)
1 is everything.

python is a cat.

this is the end.

# 读取内容包含换行符,所以要strip()去掉换行符
In [62]: with open('test.txt','r') as f:
  ...:   for line in f.readlines():
  ...:     print(line.strip())
1 is everything.
python is a cat.
this is the end.

总结一下,readline()比较鸡肋,不咋用;read()适合读取内容较少的情况,或者是需要一次性处理全部内容的情况;而readlines()用的较多,比较灵活,因为for循环是一种迭代器,每次加载部分内容,既减少内存压力,又方便逐行对数据处理。

多样需求的读写任务

前两部分讲了文件读写的几大核心方法,它们能够起作用的前提就是,需要先打开一个文件对象,因为只有在文件操作符的基础上才可以进行读或者写的操作。

打开文件用的是open()方法,所以我们再继续讲讲这个方法。open() 方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError。

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

open()方法的参数里file(文件)是必需的,其它参数最常用的是mode(模式)和encoding(编码)。

先说说encoding,一般来说,打开文件的编码方式以操作系统的默认编码为准,中文可能会出现乱码,需要加encoding='utf-8'。

In [63]: with open('test.txt','r') as f:
  ...:   for line in f.readlines():
  ...:     print(line.strip())
-----------------------
UnicodeDecodeError   Traceback (most recent call last)
<ipython-input-63-731a4f9cf707> in <module>()
   1 with open('test.txt','r') as f:
----> 2   for line in f.readlines():
   3     print(line.strip())
UnicodeDecodeError: 'gbk' codec can't decode byte 0xa4 in position 26: illegal multibyte sequence

In [65]: with open('test.txt','r',encoding='utf-8') as f:
  ...:   for line in f.readlines():
  ...:     print(line.strip())
爱猫猫
python is a cat.

再说mode,它指定文件打开的模式。

'r': 以只读模式打开(缺省模式,必须保证文件存在)
'w':以只写模式打开。若文件存在,则清空文件,然后重新创建;若不存在,则新建
'a':以追加模式打开。若文件存在,则会追加到文件的末尾;若文件不存在,则新建

常见的mode组合
'r'或'rt': 默认模式,文本读模式
'w'或'wt':以文本写模式打开(打开前文件被清空)
'rb': 以二进制读模式打开
'ab': 以二进制追加模式打开
'wb': 以二进制写模式打开(打开前文件被清空)
'r+': 以文本读写模式打开,默认写的指针开始指在文件开头, 因此会覆写文件
'w+': 以文本读写模式打开(打开前文件被清空)
'a+': 以文本读写模式打开(只能写在文件末尾)
'rb+': 以二进制读写模式打开
'wb+': 以二进制读写模式打开(打开前被清空)
'ab+': 以二进制读写模式打开

喵喵,初看起来,模式很多,但是,它们只是相互组合罢了。建议记住最基本的w、r、a,遇到特殊场景,再翻看一下就好了。

从with语句到上下文管理器

基础部分讲完了,下面是进阶部分。知其然,更要知其所以然。

1、with语句是初学者必会常识

首先,要解释一下为啥前文直接就用了with语句。with语句是读写文件时的优雅写法,这已经默认是Python初学者必会的常识了。如果你还不会,先看看用和不用with语句的对比:

# 不用with语句的正确写法
try:
  f = open('test.txt','w')
  f.writelines(['python',' is',' a',' cat'])
finally:
  if f:
    f.close()

# 使用with语句的正确写法
with open('test.txt','w') as f:
  f.writelines(['python',' is',' a',' cat'])

因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量是有限的,所以open()方法之后一定要调用close()方法。另外,读写操作可能出现IO异常的情况,所以要加try…finally,保证无论如何,都会调用到close()方法。

这样写万无一失,但是实在繁琐,一不小心还可能漏写或者写错。而with语句会保证调用close(),只需一行代码,简直不要太优雅!所以,with语句是Python初学者必会技能。

2、什么是上下文管理器?

下面,重头戏来了,什么是上下文管理器(context manager)?

上下文管理器是这样一个对象:它定义程序运行时需要建立的上下文,处理程序的进入和退出,实现了上下文管理协议,即在对象中定义了 __enter__() 和 __exit__() 方法。

__enter__():进入运行时的上下文,返回运行时上下文相关的对象,with 语句中会将这个返回值绑定到目标对象。

__exit__(exception_type, exception_value, traceback):退出运行时的上下文,定义在块执行(或终止)之后上下文管理器应该做什么。它可以处理异常、清理现场或者处理 with 块中语句执行完成之后需要处理的动作。

注意enter和exit的前后有两个下划线,Python中自带了很多类似的方法,它们是很神秘又很强大的存在,江湖人常常称其为“黑魔法”。例如,迭代器协议就实现了__iter__方法。

在Python的内置类型中,很多类型都是支持上下文管理协议的,例如file,thread.LockType,threading.Lock等等。

上下文管理器无法独立使用,它们要与with相结合,with语句可以在代码块运行前进入一个运行时上下文(执行_enter_方法),并在代码块结束后退出该上下文(执行__exit__方法)。

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

3、自定义上下文管理器

除了Python的内置类型,任何人都可以定义自己的上下文管理器。下面是一个示例:

class OpenFile(object):
  def __init__(self,filename,mode):
    self.filename=filename
    self.mode=mode
  def __enter__(self):
    self.f=open(self.filename,self.mode)
    self.f.write("enter now\n")
    return self.f #作为as说明符指定的变量的值
  def __exit__(self,type,value,tb):
    self.f.write("exit now")
    self.f.close()
    return False  #异常会被传递出上下文
with OpenFile('test.txt','w') as f:
  f.write('Hello World!\n')

最终写入文件的结果是:

enter now
Hello World!
exit now

上下文管理器必须同时提供 __enter__() 和 _exit_() 方法的定义,缺少任何一个都会导致 AttributeError。

上下文管理器在执行过程中可能会出现异常,_exit_() 的返回值会决定异常的处理方式:返回值等于 False,那么这个异常将被重新抛出到上层;返回值等于 True,那么这个异常就被忽略,继续执行后面的代码。__exit()__ 有三个参数(exception_type, exception_value, traceback),即是异常的相关信息。

4、contextlib实现上下文管理器

上例中,自定义上下文管理器的写法还是挺繁琐的,而且只能用于类级别。为了更好地辅助上下文管理,Python 内置提供了 contextlib 模块,进而可以很方便地实现函数级别的上下文管理器。

该模块本质上是通过装饰器(decorators)和生成器(generators)来实现上下文管理器,可以直接作用于函数/对象,而不用去关心 __enter__() 和 __exit()__ 方法的具体实现。

先把上面的例子改造一下,然后我们再对照着解释:

from contextlib import contextmanager

@contextmanager
def open_file(name):
  ff = open(name, 'w')
  ff.write("enter now\n")
  try:
    yield ff
  except RuntimeError:
    pass
  ff.write("exit now")
  ff.close()

with open_file('test.txt') as f:
  f.write('Hello World!\n')

contextmanager是要使用的装饰器,yield关键字将普通的函数变成了生成器。yield的返回值(ff)等于上例__enter__()的返回值,也就是as语句的值(f),而yield前后的内容,分别是_enter_() 和 _exit_() 方法里的内容。

使用contextlib,可以避免类定义、_enter_() 和 __exit()__方法,但是需要我们捕捉可能的异常(例如,yield只能返回一个值,否则会导致异常 RuntimeError),所以try…except语句不能忽略。

喵喵喵,今天的分享就到这啦。看官们,觉得有用的话,分享给其他同样好学的胖友们吧~~~~

Python 相关文章推荐
Python中的引用和拷贝浅析
Nov 22 Python
python集合用法实例分析
May 30 Python
利用python程序帮大家清理windows垃圾
Jan 15 Python
Python中的错误和异常处理简单操作示例【try-except用法】
Jul 25 Python
Windows系统下多版本pip的共存问题详解
Oct 10 Python
python获取中文字符串长度的方法
Nov 14 Python
使用python list 查找所有匹配元素的位置实例
Jun 11 Python
Python pandas用法最全整理
Aug 04 Python
python、PyTorch图像读取与numpy转换实例
Jan 13 Python
安装完Python包然后找不到模块的解决步骤
Feb 13 Python
基于Python爬取爱奇艺资源过程解析
Mar 02 Python
python numpy实现rolling滚动案例
Jun 08 Python
40个你可能不知道的Python技巧附代码
Jan 29 #Python
你可能不知道的Python 技巧小结
Jan 29 #Python
Python如何通过Flask-Mail发送电子邮件
Jan 29 #Python
Python原始套接字编程实例解析
Jan 29 #Python
Python内置类型性能分析过程实例
Jan 29 #Python
python add_argument()用法解析
Jan 29 #Python
python使用ctypes调用扩展模块的实例方法
Jan 28 #Python
You might like
基于PHP选项与信息函数的使用详解
2013/05/10 PHP
PHP如何实现Unicode和Utf-8编码相互转换
2015/07/29 PHP
php自定义截取中文字符串-utf8版
2017/02/27 PHP
php中bind_param()函数用法分析
2017/03/28 PHP
thinkphp5 加载静态资源路径与常量的方法
2017/12/24 PHP
使用UglifyJS合并/压缩JavaScript的方法
2012/03/07 Javascript
Jquery原生态实现表格header头随滚动条滚动而滚动
2014/03/18 Javascript
JS常用表单验证方法总结
2014/05/22 Javascript
js+div实现文字滚动和图片切换效果代码
2015/08/27 Javascript
JS中实现函数return多个返回值的实例
2017/02/21 Javascript
vue路由前进后退动画效果的实现代码
2018/12/10 Javascript
详解Node.js amqplib 连接 Rabbit MQ最佳实践
2019/01/24 Javascript
Vue.js递归组件实现组织架构树和选人功能
2019/07/04 Javascript
[06:37]2014DOTA2国际邀请赛 昔日王者渴望重回巅峰
2014/07/12 DOTA
[50:05]VGJ.S vs OG 2018国际邀请赛淘汰赛BO3 第二场 8.22
2018/08/23 DOTA
[15:20]DOTA2-DPC中国联赛 正赛 Elephant vs Aster 选手采访
2021/03/11 DOTA
编写Python脚本来获取Google搜索结果的示例
2015/05/04 Python
python使用分治法实现求解最大值的方法
2015/05/12 Python
python如何把嵌套列表转变成普通列表
2018/03/20 Python
python操作kafka实践的示例代码
2019/06/19 Python
Django 响应数据response的返回源码详解
2019/08/06 Python
python实现静态服务器
2019/09/05 Python
tensorflow实现测试时读取任意指定的check point的网络参数
2020/01/21 Python
深入浅析css3 中display box使用方法
2015/11/25 HTML / CSS
Canvas引入跨域的图片导致toDataURL()报错的问题的解决
2018/09/19 HTML / CSS
Timberland德国官网:靴子、鞋子、衣服、夹克及配件
2019/12/10 全球购物
Kendra Scott官网:美国领先的时尚配饰品牌
2020/10/22 全球购物
在浏览器端如何得到服务器端响应的XML数据
2012/11/24 面试题
商务英语求职自荐信范文
2013/12/24 职场文书
合伙经营协议书
2014/04/18 职场文书
孩子教育的心得体会
2014/09/01 职场文书
2016年党员读书月活动总结
2016/04/06 职场文书
关于党风廉政建设宣传教育月的活动总结!
2019/08/08 职场文书
解决thinkphp6(tp6)在状态码500下不报错,或者显示错误“Malformed UTF-8 characters”的问题
2021/04/01 PHP
mysql中数据库覆盖导入的几种方式总结
2022/03/25 MySQL
使用MybatisPlus打印sql语句
2022/04/22 SQL Server