首页 > 编程笔记

Python文件操作精讲

如果希望将数据长久保存,最简单的办法就是将数据写入磁盘文件中。这样在程序退出后,处理结果依然长期有效。对于大型软件项目,文件操作是无法避免的。

注意,本节主要介绍普通文本文件和二进制文件的操作方法,对于 json、yaml、cvs、xml 等特殊格式的文件,一般都有对应的包来进行操作,不必直接使用本节介绍的方法来处理。

文件的基本操作

文件的基本操作步骤如下:
  1. 打开文件。
  2. 进行读写操作。
  3. 关闭文件。

下面是打开某个文本文件,并将其内容显示在屏幕上的代码:
fd = open("in.dat", "r")   # 打开文件in.dat
for line in fd:            # 读取in.dat的每一行到变量line中
    print(line)            # 在屏幕上打印出line的内容
fd.close()                 # 处理结束,关闭文件
如果希望将数据写入文件中,可以使用下面的基本操作模式:
fd = open("out.dat", "r")    # 以写的方式打开文件out.dat
fd.write("output line 1")    # 向该文件写入一行数据
fd.write("output line 2")    # 再次向该文件写入一行数据
fd.close()                   # 处理结束,关闭文件
如果希望添加数据到某个文件的尾部,如将一些新的日志内容添加到日志文件的尾部,则需要设定文件打开的模式为 a,这样原来文件的数据不会丢失,写入的数据只是添加在原来数据的尾部。下面的代码演示了这样的用法:
fd = open("out.dat", "a")    # a表示是以添加的方式打开该文件
fd.write("log line 1")       # 在尾部添加一行日志
fd.write("log line 2")       # 再次在尾部添加一行日志
fd.close()                   # 处理结束,关闭文件

打开文件

打开文件可以使用“open(文件名,模式)”来完成。该函数有两个参数,第一个参数是文件名,第二个参数是打开的模式。该函数返回一个文件对象,以后的读写都需要使用该文件对象。

文件名可以包含路径,如 cfg/config.ini。使用绝对路径和相对路径都是允许的,但不能使用通配符,如 log/*.log。对于 Windows 用户来说,由于使用 \ 作为路径分隔符,而有人喜欢用两个连续的反斜杠 \ 即 \\ 来表示 \,如"c:\\log\\app.log"。推荐的用法是在引号前面加上 r,这样看起来更加自然一点,如 r"c:\log\app.log"。

模式参数也是一个字符串,通过不同的模式值可以指定按照只读、只写、尾部添加等方式来打开某个文件。

模式的基本格式如下:

mode  = 打开方式 + 文件类型

文件类型包括两种,文本模式和二进制模式。文本模式用 t 表示,这是默认值;二进制模式用 b 表示。

打开方式包括:只读,用 r 表示;只写,用 w 表示;添加,用 a 表示。除此之外,还包括可读可写,用 w+ 或者 r+ 表示。它们的区别是 w+ 会清空原来的文件内容,而 r+ 不会清空原来的文件内容。如 rb 即表示用二进制模式 + 只读打开指定文件。

如果操作的文件不存在或者没有权限进行相关操作时,其会抛出 FileNotFoundError 或者 IOError 异常,此时一般需要使用 try 语句来处理。下面是在 Python 3 中打开不存在的文件的情况:
>>> fd = open("notExists.dat", "r")    # 打开不存在的文件,抛出异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'notExists.dat'
下面是在 Python 2 中打开不存在的文件的情况:
>>> fd = open("notExists.dat", "r")    # Python 2中的异常情况
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'notExists.dat'
为了处理这些打开文件的异常情况,可以使用下面的代码:
try:
    file_obj1 = open(u"nonexist.txt", "r")
    file_obj1.close()
except IOError:
    # 找不到文件时提示文件不存在
    print(u"File not Exist")
如果希望某个文件只在某个局部有效,可以使用 with 语句。下面是一个使用的例子:
>>> with open('in.dat', 'r') as f:  # 使用with语句,f是打开的文件对象
...     print(f.read())    # 读出所有的数据并打印到屏幕上
...                                                                     # 结束with语句
this is input text file   # 文件内容
it contains 3 lines
this is the end of file
>>> f                     # 查看f
<_io.TextIOWrapper name='in.dat' mode='r' encoding='UTF-8'>
>>> f.closed              # f是不是自动关闭了?
True              # 文件对象自动关闭了

文件对象的操作

通过打开文件得到文件对象,该对象提供一系列操作文件的方法。

1) read(size):读入指定长度的文本

该函数从当前位置开始读,读出指定个数的字符。其返回值是一个字符串,表示读取的文件内容。

参数 size 如果为正数,表示最多读出 size 个字符;如果 size 为 0,则什么也不会读出,返回值是空字符串;如果 size 为负数,表示读出全部的内容。size 的默认值是 -1,表示读出全部的内容。

下面的例子演示了不指定 size 的值而使用默认值 -1 的情况。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> ret_str = fd.read()        # 将所有文件内容读到ret_str,size=-1
>>> type(ret_str)              # 返回值类型是字符串
<class 'str'>
>>> len(ret_str)               # 字符串长度为68
68
>>> print(ret_str)             # 显示文件内容
this is input text file
it contains 3 lines
this is the end of file
>>> fd.close()                 # 关闭文件

下面演示指定 size,并且 size 为正数的情况。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> str1 = fd.read(40)         # 读入最多40个字符,保存到str1中
>>> str2 = fd.read(40)         # 读入最多40个字符,保存到str2中
>>> len(str1)                  # str1包含40个字符
40
>>> len(str2)                  # str2包含28个字符,总共是68个字符
28
>>> print(str1+str2)           # 将str1和str2连接起来,就是文件的完整内容
this is input text file
it contains 3 lines
this is the end of file
>>> fd.close()                 # 关闭文件

如果到了文件的尾部,则返回空字符串。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> str1 = fd.read()           # 读出全部内容
>>> str2 = fd.read()           # 这时已经到了文件的尾部
>>> type(str2)                 # 返回值类型是字符串
<class 'str'>    
>>> len(str2)                  # str2的长度为0,所以是空字符串
0
>>> fd.close()                 # 关闭文件

2) readline(size):读入一行数据

该函数读入一行数据,所以文件类型要求是文本,不能是二进制。返回值是这行数据,包括尾部的换行符;如果没有数据则返回一个空的字符串。该函数也带有参数 size,表示的含义和 read() 一样。size 的默认值也是 -1,表示读取完整的一行。

下面的例子演示了没有指定 size 而使用默认值 -1 的情况。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> line = fd.readline()       # 读入一行
>>> while line:                # 如果不是空行,就是还没有到尾部
...     print(line, end='')    # 打印读入的行,关闭自动换行功能
...     line = fd.readline()   # 读入下一行
...                            # 结束while循环
this is input text file        # 显示的文件内容
it contains 3 lines
this is the end of file
>>> fd.close()                 # 关闭文件

下面的例子演示了size为正数的情况。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> line = fd.readline(2)      # 读入一行,但最多读入2个字符
>>> len(line)                  # 返回字符串的长度为2
2
>>> line                       # 显示返回值的内容
'th'
>>> fd.close()                 # 关闭文件

下面的例子演示了size为0的情况。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> line = fd.readline(0)      # 读入一行,但是最多只能读入0个字符
>>> len(line)                  # 返回的是空字符串
0
>>> fd.close()                 # 关闭文件

3) readlines(hint):读出全部行

该函数将文件的每行作为一个元素,组合成一个列表返回。

参数 hint 用来限制读入的行数。如果参数 hint 为负数或者 0,表示没有行数的限制。hint 的默认值是 -1,即默认情况下是不对读入的行数进行限制的,返回的是文件的所有行。

下面的例子演示了没有指定 hint 值而使用默认值 -1 的情况,其会读出所有的行,并且将这些行组成一个列表返回。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> lines = fd.readlines()     # 将所有的行读出
>>> lines                      # 显示返回值,是一个列表,每个元素表示一行
['this is input text file\n', 'it contains 3 lines\n', 'this is the end
        of file\n']
>>> fd.close()                 # 关闭文件

如果 hint 为正数,则会依次读入各行,并检查读入的字符数是否大于 hint。如果大于或等于 hint,则停止继续读入下一行;如果小于 hint 则继续读入下一行。下面的例子演示了 hint 比实际文件字节数小的情况。
>>> fd = open("in.dat", "r")   # 以只读方式打开文件in.dat
>>> lines = fd.readlines(3)    # 如果读入字符数超过3,就停止读入下一行
>>> lines                      # 所以只读入了一行便停止了
['this is input text file\n']
>>> lines = fd.readlines(30)   # 如果读入超过了30个字符,就停止读入下一行
>>> lines                      # 第二行字符串小于30,所以继续读入第三行
['it contains 3 lines\n', 'this is the end of file\n']
>>> fd.close()                 # 关闭文件

4) write(data):写入字符串

在 Python 3 中,该函数的返回值是参数 data 的字节数。在 Python 2 中,其返回值是 None。下面演示了这种不同:
>>> fd = open("out.dat", "w")  # Python 3中的情况
>>> fd.write("line 1")         # 写入字符串,返回值是字符的个数
6
>>> fd.close()
5
>>> fd = open("out.dat", "w")  # Python 2中的情况下打开文件
>>> fd.write("line 1")         # 写入字符串,返回值为None
>>> fd.close()

5) writelines(lines):写入多行

lines 是一个列表或者元组,其执行效果相当于是 write(‘’.join(lines)),各行之间并不会填充任何数据。
>>> fd = open("out.dat", "w")                  # 打开文件out.dat
>>> fd.writelines(["line1", "line2", "line3"]) # 写入字符串列表
>>> fd.close()                     # 关闭文件
>>> fdr = open("out.dat", "r")     # 查看写入的内容
>>> fdr.read()        # 读入文件内容
'line1line2line3'     # 行与行之间是没有添加任何信息的
>>> fdr.close()       # 关闭fdr
>>> fd = open("out.dat", "w")                  # 打开文件out.dat
>>> fd.writelines(("line1", "line2", "line3")) # 写入字符串元组
>>> fd.close()          # 关闭文件
>>> fdr = open("out.dat", "r")                 # 检查写入的数据
>>> fdr.read()                                 # 可以发现没有添加数据在行之间
'line1line2line3'
>>> fdr.read()

6) tell():得到文件位置

文件可以被看作是字节流或者字符流。第一个读入的字符是在该流的第 0 个位置上。如果读入了 5 个字符,则现在在该流的第 5 个位置上。

我们可以用 tell() 来得到当前位置,其返回一个整数。下面的例子演示了 tell() 的用法。
try:
    file_obj1 = open(u"data.txt", "r")
    pos = file_obj1.tell()              # 最开始,位置应该为0
    print(u"1)当前位置为%d" % pos)
    data = file_obj1.read(5)            # 读出5个字节
    print(u"读出了%d个字节" % len(data))
    pos = file_obj1.tell()
    print(u"2)当前位置为%d" % pos)
    file_obj1.close()
except IOError:
   # 找不到文件时提示文件不存在
   print(u"File not Exist")
执行该脚本,输出如下:

E:>python tellDemo1.py
1)当前位置为0
读出了5个字节
2)当前位置为5

7) seek(offset,whence):设定当前位置

可以使用该函数调整当前所在的位置。例如,我们从文件头部读取了 1000 个字节,处理这 1000 个字节后发现还有一个很重要的信息在文件的第 3 个字节,此时可以将当前位置移动到相对头部 3 个字节的位置,这样下次 read() 操作便可以得到希望得到的信息。

该函数有 2 个参数,第一个参数是 offset,表示偏移量,可以为负数;第二个参数是 whence,表示相对于什么,可以是相对文件的头、文件的尾、当前文件。whence 的默认值是 0,表示文件头,其还可以为1表示当前位置,或者为2表示文件尾部。

下面的例子将打开文件,然后将当前位置移动到尾部的前 2 个字符,这样下次读到的就是文件最后面的两个字符。该文件的最后两个字符是yz。这里需要注意的是,文件一定要以二进制模式打开,否则 seek() 会抛出异常。下面是完整代码:
try:
    # 必须以二进制模式打开文件,所以带上b这个标识
    file_obj1 = open(u"data.txt", "rb")
    pos = file_obj1.tell()
    print(u"1)当前位置为%d" % pos)
    file_obj1.seek(-2, 2)
    print(u"2)当前位置为%d" % pos)
    data = file_obj1.read(2)
    print(u"读出了%d个字节" % len(data))
    print("data = [%s]" % str(data))
    pos = file_obj1.tell()
    print(u"3)当前位置为%d" % pos)
    file_obj1.close()
except IOError:
   # 找不到文件时提示文件不存在
   print(u"File not Exist")
运行该程序,可以得到如下输出结果:

E:\>python seekDemo1.py
1)当前位置为0
2)当前位置为0
读出了2个字节
data = [b'yz']
3)当前位置为26

下面的例子使用 tell() 和 seek() 来得到某个文件的字节数。其方法是通过得到文件头部的位置信息 start_pos 和尾部的位置信息 stop_pos,求 stop_pos-start_pos 的值来得到文件的大小。代码如下:
try:
    file_obj1 = open(u"data.txt", "rb")
    file_obj1.seek(0, 2)
    end_pos = file_obj1.tell()
    print(u"文件大小为%d" % end_pos)
    file_obj1.close()
except IOError:
    # 找不到文件时提示文件不存在
    print(u"File not Exist")

8) flush():刷新

该函数用于将文件缓存清空,这样我们做的修改就会保存到文件中。

9) fileno():得到文件编号

该函数用于得到文件在进程中的编号,这是一个整数值。其中,stdin 在进程中的文件编号永远是 0,stdout 永远是 1,stderr 永远是 2,其他文件的编号都大于 2。

下面的例子查看了普通文件的编号以及 3 个特殊文件的编号。
>>> import sys                    # 引入sys模块
>>> fd = open("./in.dat", "rb")   # 打开一个普通文件
>>> fd.fileno()               # 得到该文件的编号
3
>>> fd.close()                # 关闭该文件
>>> sys.stdin.fileno()        # 得到stdin的文件编号
0
>>> sys.stdout.fileno()       # 得到stdout的文件编号
1
>>> sys.stderr.fileno()       # 得到stderr的文件编号
2
如果该文件已经被关闭,则 fileno() 会抛出 ValueError 异常。下面的代码演示了这种情况。
>>> import sys
>>> fd = open("./in.dat", "rb")        # 打开文件
>>> fd.fileno()
3
>>> fd.close()       # 关闭文件
>>> fd.fileno()      # 无法得到文件编号,抛出异常ValueError
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file

关闭文件

在文件使用完毕后,需要关闭文件以释放资源。关闭文件的方法是:

文件对象.close()

可以对一个已经关闭的文件再次执行 close() 操作,不必担心会产生异常。

在文件关闭后便不能对其进行读写操作。文件关闭后,文件对象还是存在的,但其属性 closed 现在为 True,表示该文件已经被关闭了。

文件关闭后,如果尝试对其进行读写操作,则会抛出 ValueError 异常。

文件关闭后,除了 closed 外,文件的其他属性并不会发生变化,如 name、mode 等属性还是有效的,能够被继续使用。
>>> fd = open("in.dat", "r")   # 打开文件
>>> fd.closed                                  # closed的值为False,表示没有关闭
False
>>> fd.close()                                 # 关闭该文件
>>> fd.closed                                  # 现在closed的值为True
True
>>> fd.read()                                  # 如果试图进行读操作,则抛出异常
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
>>> fd.name                                    # 查看name属性,依然有效
'in.dat'
>>> fd.mode                                    # 查看mode属性,依然有效
'r'

优秀文章