Python文件读写 Python初学者必备的文件读写指南
Z_Data 人气:0一、如何将列表数据写入文件
⾸先,我们来看看下⾯这段代码,并思考:这段代码有没有问题,如果有问题的话,要怎么改?
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 语句不能忽略。
加载全部内容