Python初学者必备的文件读写指南
作者:Z_Data
一、如何将列表数据写入文件
⾸先,我们来看看下⾯这段代码,并思考:这段代码有没有问题,如果有问题的话,要怎么改?
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()⽅法写到⽂件。 总结⼀下,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(),会读取下⼀⾏。 这么看来,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 positio n 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): 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')
最终写⼊⽂件的结果是:
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文件读写内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!