9 文件操作

文件操作

和其它编程语言一样,Python 也具有操作文件(I/O)的能力,比如打开文件、读取和追加数据、插入和删除数据、关闭文件、删除文件等。

除了提供文件操作基本的函数之外,Python 还提供了很多模块,例如 OS 模块块等,通过引入这些模块,我们可以获得大量实现文件操作可用的函数和方法。

按文件中数据的组织形式把文件分为文本文件和二进制文件两类。

  • 文本文件:文本文件存储的是常规字符串,由若干文本行组成,通常每行以换行符'\n'结尾。常规字符串是指记事本或其他文本编辑器能正常显示、编辑并且人类能够直接阅读和理解的字符串,如英文字母、汉字、数字字符串。文本文件可以使用字处理软件如gedit、记事本进行编辑。
  • 二进制文件:二进制文件把对象内容以字节串(bytes)进行存储,无法用记事本或其他普通字处理软件直接进行编辑,通常也无法被人类直接阅读和理解,需要使用专门的软件进行解码后读取、显示、修改或执行。常见的如图形图像文件、音视频文件、可执行文件、资源文件、各种数据库文件、各类office文档等都属于二进制文件。

文件基本操作

文件内容操作三步走:打开、读写、关闭。

文件的应用级操作可以分为以下 3 步,每一步都需要借助对应的函数实现:

打开文件

使用 open() 函数,该函数会返回一个文件对象。

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, losefd=True, opener=None)
  • file参数指定了被打开的文件名称。
  • mode参数指定了打开文件后的处理方式。
  • buffering参数指定了读写文件的缓存模式。0表示不缓存,1表示缓存,如大于1则表示缓冲区的大小。默认值-1表示由系统管理缓存。
  • encoding参数指定对文本进行编码和解码的方式,只适用于文本模式,可以使用Python支持的任何格式,如GBK、utf8、CP936等等。

如果执行正常,open()函数返回1个文件对象,通过该文件对象可以对文件进行读写操作。如果指定文件不存在、访问权限不够、磁盘空间不足或其他原因导致创建文件对象失败则抛出异常

f1 = open('file1.txt', 'r')     # 以读模式打开文件
f2 = open('file2.txt', 'w')      # 以写模式打开文件

文件打开方式

模式说明
r读模式(默认模式,可省略),如果文件不存在则抛出异常
w写模式,如果文件已存在,先清空原有内容
x写模式,创建新文件,如果文件已存在则抛出异常
a追加模式,不覆盖文件中原有内容
b二进制模式(可与其他模式组合使用)
t文本模式(默认模式,可省略)
+读、写模式(可与其他模式组合使用)

1605194945081

 

对已打开文件做读/写操作

读取文件内容可使用 read()、readline() 以及 readlines() 函数;向文件中写入内容,可以使用 write() 函数。

关闭文件

完成对文件的读/写操作之后,最后需要关闭文件,可以使用 close() 函数。

当对文件内容操作完以后,一定要关闭文件对象,这样才能保证所做的任何修改都确实被保存到文件中。

f1.close()

但是,即使写了关闭文件的代码,也无法保证文件一定能够正常关闭。例如,如果在打开文件之后和关闭文件之前发生了错误导致程序崩溃,这时文件就无法正常关闭。

上下文管理关键字with

如果我们开发的程序 在进行文件读写之后,忘记使用close方法关闭文件, 就可能造成意想不到的问题。我们可以使用with语句打开文件,像这样,就不需要我们调用close方法关闭文件。 Python解释器会帮我们调用文件对象的close方法,而程序员不需要主动调用close。

with语句的用法如下:

with open(filename, mode, encoding) as fp:
    #这里写通过文件对象fp读写文件内容的语句

上下文管理语句with还支持下面的用法:

with open('test.txt', 'r') as src, open('test_new.txt', 'w') as dst:
    dst.write(src.read())

使用 with as 语句,即便最终没有关闭文件,修改文件内容的操作也能成功。

文件对象常用属性

属性说明
buffer返回当前文件的缓冲区对象
closed判断文件是否关闭,若文件已关闭则返回True
fileno文件号,一般不需要太关心这个数字
mode返回文件的打开模式
name返回文件的名称

1605194990237

文件对象常用方法

方法功能说明
flush()把缓冲区的内容写入文件,但不关闭文件
close()把缓冲区的内容写入文件,同时关闭文件,并释放文件对象
read([size])从文本文件中读取size个字符(Python 3.x)的内容作为结果返回,或从二进制文件中读取指定数量的字节并返回,如果省略size则表示读取所有内容
readable()测试当前文件是否可读
readline()从文本文件中读取一行内容作为结果返回
readlines()把文本文件中的每行文本作为一个字符串存入列表中,返回该列表,对于大文件会占用较多内存,不建议使用
seek(offset[, whence])把文件指针移动到新的字节位置,offset表示相对于whence的位置。whence为0表示从文件头开始计算,1表示从当前位置开始计算,2表示从文件尾开始计算,默认为0
write(s)把s的内容写入文件
writable()测试当前文件是否可写
writelines(s)把字符串列表写入文本文件,不添加换行符

文件操作模块

os模块

在自动化测试中,经常需要查找操作文件,比如查找配置文件(从而读取配置文件的信息),查找测试报告等等,经常会对大量文件和路径进行操作,这就需要依赖os模块。

os模块常用的文件操作函数

方法功能说明
access(path, mode)测试是否可以按照mode指定的权限访问文件
chdir(path)把path设为当前工作目录
chmod(path, mode, *, dir_fd=None, follow_symlinks=True)改变文件的访问权限
curdir当前文件夹
environ包含系统环境变量和值的字典
extsep当前操作系统所使用的文件扩展名分隔符
get_exec_path()返回可执行文件的搜索路径
getcwd()返回当前工作目录/查看当前所在路径
listdir(path)返回path目录下的文件,结果是列表
open(path, flags, mode=0o777, *, dir_fd=None)按照mode指定的权限打开文件,默认权限为可读、可写、可执行
popen(cmd, mode='r', buffering=-1)创建进程,启动外部程序
remove(path)删除指定的文件,要求用户拥有删除文件的权限,并且文件没有只读或其他特殊属性
rename(src, dst)重命名文件或目录,可以实现文件的移动,若目标文件已存在则抛出异常,不能跨越磁盘或分区
replace(old, new)重命名文件或目录,若目标文件已存在则直接覆盖,不能跨越磁盘或分区
scandir(path='.')返回包含指定文件夹中所有DirEntry对象的迭代对象,遍历文件夹时比listdir()更加高效
sep当前操作系统所使用的路径分隔符
startfile(filepath [, operation])使用关联的应用程序打开指定文件或启动指定应用程序
stat(path)返回文件的所有属性
system()启动外部程序
truncate(path, length)将文件截断,只保留指定长度的内容
write(fd, data)将bytes对象data写入文件fd

os.path常用的文件操作函数

方法功能说明
abspath(path)返回给定路径的绝对路径
basename(path)返回指定路径的最后一个组成部分
commonpath(paths)返回给定的多个路径的最长公共路径
commonprefix(paths)返回给定的多个路径的最长公共前缀
dirname(p)返回给定路径的文件夹部分
exists(path)判断文件是否存在
getatime(filename)返回文件的最后访问时间
getctime(filename)返回文件的创建时间
getmtime(filename)返回文件的最后修改时间
getsize(filename)返回文件的大小
isabs(path)判断path是否为绝对路径
isdir(path)判断path是否为文件夹
isfile(path)判断path是否为文件
join(path, *paths)连接两个或多个path
realpath(path)返回给定路径的绝对路径
relpath(path)返回给定路径的相对路径,不能跨越磁盘驱动器或分区
samefile(f1, f2)测试f1和f2这两个路径是否引用的同一个文件
split(path)以路径中的最后一个斜线为分隔符把路径分隔成两部分,以元组形式返回
splitext(path)从路径中分隔文件的扩展名
splitdrive(path)从路径中分隔驱动器的名称

shutil模块

shutil 模块里面有很多目录文件操作的函数。

方法功能说明
copy(src, dst)复制文件,新文件具有同样的文件属性,如果目标文件已存在则抛出异常
copy2(src, dst)复制文件,新文件具有原文件完全一样的属性,包括创建时间、修改时间和最后访问时间等等,如果目标文件已存在则抛出异常
copyfile(src, dst)复制文件,不复制文件属性,如果目标文件已存在则直接覆盖
copyfileobj(fsrc, fdst)在两个文件对象之间复制数据,例如copyfileobj(open('123.txt'), open('456.txt', 'a'))
copymode(src, dst)把src的模式位(mode bit)复制到dst上,之后二者具有相同的模式
copystat(src, dst)把src的模式位、访问时间等所有状态都复制到dst上
copytree(src, dst)递归复制文件夹,拷贝一个目录里面所有的内容
disk_usage(path)查看磁盘使用情况
move(src, dst)移动文件或递归移动文件夹,也可以给文件和文件夹重命名
rmtree(path)递归删除文件夹
make_archive(base_name, format, root_dir=None, base_dir=None)创建tar或zip格式的压缩文件
unpack_archive(filename, extract_dir=None, format=None)解压缩压缩文件

二进制文件操作

文本文件通常用来保存肉眼可见的字符,比如 .txt 文件、.c 文件、.dat 文件等,用文本编辑器打开这些文件,我们能够顺利看懂文件的内容。而二进制文件通常用来保存视频、图片、音频等不可阅读的内容,当用文本编辑器打开这些文件,会看到一堆乱码,根本看不懂。

对于二进制文件,不能使用记事本或其他文本编辑软件进行正常读写,也无法通过Python的文件对象直接读取和理解二进制文件的内容。必须正确理解二进制文件结构和序列化规则,才能准确地理解二进制文件内容并且设计正确的反序列化规则。

所谓序列化,简单地说就是把内存中的数据在不丢失其类型信息的情况下转成对象的二进制形式的过程(程序的各种类型数据对象变成表示该数据对象的二进制形式这个过程称之为序列化),对象序列化后的形式经过正确的反序列化过程应该能够准确无误地恢复为原来的对象。

Python中常用的序列化模块有struct、pickle、marshal和shelve。

使用pickle模块

Python 中有个序列化过程叫作 pickle,它能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。也就是说,pickle 可以实现 Python 对象的存储及恢复。

pickle 模块提供了以下 4 个函数供我们使用:

  • dumps():将 Python 中的对象序列化成二进制对象,并返回;
  • loads():读取给定的二进制对象数据,并将其转换为 Python 对象;
  • dump():将 Python 中的对象序列化成二进制对象,并写入文件;
  • load():读取指定的序列化数据文件,并返回对象。

以上这 4 个函数可以分成两类,其中 dumps 和 loads 实现基于内存的 Python 对象与二进制互转;dump 和 load 实现基于文件的 Python 对象与二进制互转。

使用struct模块

使用struct模块读取二进制文件。

import struct

with open('sample_struct.dat', 'rb') as fp:
    sn = fp.read(9)
    tu = struct.unpack('if?', sn) 
    print(tu)
    n, x, bl = tu
    print('n=', n)
    print('x=', x)
    print('bl=', bl)
    s = fp.read(9).decode()
    print('s=', s)
>>> import struct
>>> struct.pack('if?', 13000, 56.0, True)
b'\xc82\x00\x00\x00\x00`B\x01'
>>> len(_)
9
>>> len(struct.pack('if?', 9999, 5336.0, False))
9
>>> x = 'a1@中国'
>>> len(x.encode())
9

使用shelve序列化

Python标准库shelve也提供了二进制文件操作的功能,可以像字典赋值一样来写入二进制文件,也可以像字典一样读取二进制文件。

>>> import shelve
>>> zhangsan = {'age':38, 'sex':'Male', 'address':'SDIBT'}
>>> lisi = {'age':40, 'sex':'Male', 'qq':'1234567', 'tel':'7654321'}
>>> with shelve.open('shelve_test.dat') as fp:
    fp['zhangsan'] = zhangsan      # 像操作字典一样把数据写入文件
    fp['lisi'] = lisi
    for i in range(5):
        fp[str(i)] = str(i)

>>> with shelve.open('shelve_test.dat') as fp:
        print(fp['zhangsan'])                 #读取并显示文件内容
        print(fp['zhangsan']['age'])
        print(fp['lisi']['qq'])
        print(fp['3'])

{'sex': 'Male', 'address': 'SDIBT', 'age': 38}
38
1234567
3

使用marshal序列化

Python标准库marshal也可以进行对象的序列化和反序列化。

>>> import marshal                              #导入模块
>>> x1 = 30                                     #待序列化的对象
>>> x2 = 5.0
>>> x3 = [1, 2, 3]
>>> x4 = (4, 5, 6)
>>> x5 = {'a':1, 'b':2, 'c':3}
>>> x6 = {7, 8, 9}
>>> x = [eval('x'+str(i)) for i in range(1,7)]  #把待序列化对象放到列表中
>>> x
[30, 5.0, [1, 2, 3], (4, 5, 6), {'a': 1, 'b': 2, 'c': 3}, {8, 9, 7}]
>>> with open('test.dat', 'wb') as fp:          #创建二进制文件
    marshal.dump(len(x), fp)                    #先写入对象个数
    for item in x:
        marshal.dump(item,fp) 

>>> with open('test.dat', 'rb') as fp:    #打开二进制文件
    n = marshal.load(fp)                  #获取对象个数
        for i in range(n):
            print(marshal.load(fp))       #反序列化,输出结果

30
5.0
[1, 2, 3]
(4, 5, 6)
{'a': 1, 'b': 2, 'c': 3}
{8, 9, 7}

目录操作

os模块常用的目录操作函数

函数名称使用说明
mkdir(path[, mode=0o777])递归的创建目录,要求上级目录必须存在
makedirs(path1/path2…, mode=511)创建多级目录,会根据需要自动创建中间缺失的目录
rmdir(path)删除目录,要求该文件夹中不能有文件或子文件夹
removedirs(path1/path2…)删除多级目录
listdir(path)返回指定目录下所有文件信息
getcwd()返回当前工作目录
chdir(path)把path设为当前工作目录
walk(top, topdown=True, onerror=None)遍历目录树,该方法返回一个元组,包括3个元素:所有路径名、所有目录列表与文件列表
>>> import os
>>> os.getcwd()                         #返回当前工作目录
'C:\\Python35'
>>> os.mkdir(os.getcwd()+'\\temp')      #创建目录
>>> os.chdir(os.getcwd()+'\\temp')      #改变当前工作目录
>>> os.getcwd()
'C:\\Python35\\temp'
>>> os.mkdir(os.getcwd()+'\\test')
>>> os.listdir('.')
['test']
>>> os.rmdir('test')                    #删除目录
>>> os.listdir('.')
[]