Python文件读写指南

做者:豌豆花下猫,某985高校毕业生, 兼具极客思惟与人文情怀 。公众号【Python猫】, 专一python技术、数据科学和深度学习,力图创造一个有趣又有用的学习分享平台。html

对于初学者来讲,一份详尽又清晰明白的指南很重要。今天,猫猫跟你们一块儿,好好学习Python文件读写的内容,这部份内容特别经常使用,掌握后对工做和实战都大有益处。学习是按部就班的过程,欲速则不达。文章较长,建议你们收藏,以备复习查阅哦。
python

一、如何将列表数据写入文件?
二、如何从文件中读取内容?
三、多样需求的读写任务
四、从with语句到上下文管理器
sql

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

首先,咱们来看看下面这段代码,并思考:这段代码有没有问题,若是有问题的话,要怎么改?json

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

如今公布答案,这段代码会报错:ruby

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 strnot 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)) ,先作一下字符串类型的转化看看。代码略。app

此次没有报错了,可是打开文件就傻眼了吧,写入的内容是“['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()方法作下转化。ui

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

那就不能直接使用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 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语句到上下文管理器

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

一、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初学者必会技能。

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

下面,重头戏来了,什么是上下文管理器(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 语句适用于对资源进行访问的场合,确保无论使用过程当中是否发生异常都会执行必要的“清理”操做,释放资源,好比文件使用后自动关闭、线程中锁的自动获取和释放等。

三、自定义上下文管理器

除了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),便是异常的相关信息。

四、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语句不能忽略。

以上就是本文的所有内容,但愿对你们的学习有所帮助。若是以为文章不错,动手转发支持一下哦!


感谢您的阅读!想了解更多有关技巧,请关注个人微信公众号“R语言和Python学堂”,我将按期更新相关文章。同时也欢迎你们积极投稿,促进交流。

个人专栏:

  • 简书:https://www.jianshu.com/u/981ba7d6b4a6

  • 知乎:https://www.zhihu.com/people/zoro-3-92/activities

本文分享自微信公众号 - R语言和Python学堂(gh_02c4f77a735e)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索