5.1 文件读写
文件可以从多个维度进行管理,例如,重命名文件、获取文件属性、判断文件是否存在、备份文件、读写文件等。
5.1.1 Python内置的open函数
在Python中,要对一个文件进行操作,只需要使用内置的open函数打开文件即可。open函数接受文件名和打开模式作为参数,返回一个文件对象。工程师通过文件对象操作文件,完成以后,调用文件对象的close方法关闭文件,如下:
f=open('data.txt')
f.close()
5.1.2 避免文件句柄泄露
在计算机程序中,每打开一个文件就要占用一个文件句柄,而一个进程拥有的文件句柄数事有限。此外,文件句柄也会消耗资源。所以,我们在打开文件的时候,记得要关闭文件,释放资源。在Python中,可以使用finally语句来保证,无论在什么情况下文件都会被关闭。如下:
try:
f=open('data.txt')
...
finally:
f.close()
Python中还有更加简洁优美的写法,就是使用上下文文管理器,使用上下文管理器的代码如下:
with open('data.txt') as f:
print(f.read())
5.1.3 常见的文件操作函数
Python的文件对象有多种类型的函数,如刷新缓存的flush函数。获取文件位置的tell函数,改变文件读写偏移量的seek函数。但是,工作中使用最多的还是与读写相关的函数。
Python提供了三个读相关得到函数,分别是read、readline和readlines,他们的作用如下:
- read:读取文件中的所有内容
- readline: 一次读取一行
- readlines: 将文件内容存到一个列表中,列表中的每一行对应于文件中的一行。
可以看到read和readlines函数都是一次性把文件的所有内容都读出,如果是大文件,有OOM的风险。
Python提供了两个写函数,分别是write和writelines,它们的区别如下:
- write: 写字符串到文件中,并返回写入的字符数
- writelines: 写一个字符串列表到文件中。
在Python中,除了使用文件对象的write和writelines函数向文件写入数据以外,也可以使用print函数将输出结果输出到文件中。print函数比write和writelines函数更加灵活,如下:
from __future__ import print_function
with open('/tmp/data.txt', 'w') as f:
print(1,2,'hello world',sep=",", file=f)
5.1.4 Python的文件是一个可迭代对象
如果我们要以行为单位依次处理文件中的每一行,应该怎么读取文件呢?如果使用readline或者readlines函数,但是每读一行就调用一次readline函数这种方式效率很低,并且会使得我们的代码非常混乱;如果使用readlines,加载大文件又是一个问题。
在Python中,还有更好的方式来依次处理文件的内容,即使用for循环遍历文件。为什么在Python中能使用for来循环遍历文件呢?因为Python的for循环比大家看到的还要通用,它不但可以遍历如字符串、列表、元组这个可迭代的序列,我们还可以使用迭代器协议来遍历可迭代对象。而Python的文件对象实现了迭代器协议,因此我们可以在for循环中遍历文件内容。如下:
with open('data.txt') as f:
for line in f:
print(line.upper)
5.2 文件与文件路径管理
介绍os模块,os模块对操作系统的API做了封装,并且使用统一的API访问不同的操作系统的相同功能。
5.2.1 使用os.path进行路径和文件管理
在os模块中最常见的就是getcwd和listdir函数了,前者用来获取当前目录,后者用来列出目录下的所有文件和文件夹。如下:
import os
os.getcw
os.listdir('.')
- 拆分路径
os.path模块用来对文件和路径进行管理,显然,它会包含很多拆分路径的函数。相关的函数有:
- split: 返回一个二元组,包含文件的路径与文件名
- dirname: 返回文件的路径
- basename: 返回文件的文件名
- splitext: 返回一个除去文件拓展名的部分和拓展名的二元组
path='/home/pangcm/test.txt'
os.path.split(path)
os.path.basename(path)
os.path.dirname(path)
os.path.splitext(path)
- 构建路径
os.path能拆分路径,自然也能构建路径。最常用的函数是expanduser、abspath和join函数。它们的作用:
- expanduser: 展开用户的HOME目录,如~、~username
- abspath: 得到文件或路径的绝对路径
- join: 根据不同的操作系统,使用不同的路径分隔符拼接路径
import os
os.getcwd()
os.path.expanduser('~')
os.path.expanduser('~pangcm')
os.path.expanduser('~pangcm/test')
os.path.abspath('.')
os.path.abspath('./test')
os.path.join('~','t','a.py')
前面介绍os.path模块构建路径时,介绍了abspath来返回绝对路径。相应地,os.path还存在一个函数用判断一个路径是否为绝对路径。
os.path.isabs('.')
在Python代码中,可以使用 '__file__' 这个特殊的变量表示当前代码所在的源文件。在编写代码时,有时需要导入当前源文件父目录下的软件包。因此,需要到这里的路径函数获取源文件的父目录。如下:
import os
os.path.abspath(__file__)
- 获取文件属性
下面几个函数用来获取文件的属性:
- getatime: 获取文件的访问时间
- getmtime: 获取文件的修改时间
- getctime: 获取文件的创建时间
- getsize: 获取文件的大小
- 判断文件类型
下面截个行数用来判断文件路径是否存在以及路径所指文件的类型。
- exists: 参数path所指向的路径是否存在
- isfile: 参数path所指向的路径存在,并且是一个文件
- isdir: 参数path所指向的路径存在,并且是一个文件夹
- islink: 参数path所指向的路径存在,并且是一个链接
- ismount: 参数path所指向的路径存在,并且是一个挂载点
下面有几个应用示例:
#获取当前用户home目录下所有的文件列表
[item for item in os.listdir(os.path.expanduser('~')) if os.path.isfile(item)]
#获取当前用户home目录下所有的目录列表
[item for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item)]
#获取当前用户home目录下所有的目录名到绝对路径之间的字典
{item: os.path.realpath(item) for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item)}
5.2.2 使用os模块管理文件和目录
前面已经介绍了getcwd函数,该函数用来获取当前目录,与之相关的chdir函数,该函数用来修改当前目录。
import os
os.getcwd()
os.chdir(os.path.expanduser('~pangcm'))
os模块还包含了文件和目录的操作函数,包括创建目录、删除目录、删除文件、重命名文件。
- unlink/remove: 删除path路径所指向的文件
- rmdir:删除path路径所指向的文件夹,该文件夹必须为空,否则报错
- rename: 重命名文件或文件夹
- mkdir: 创建一个文件夹
os模块也包含了修改文件权限、判断文件权限的函数,即chmod和access。chmod用来修改文件的权限,access用来判断文件是否具有相应的权限。在Linux中,权限分为读、写和执行。因此,os模块也提供了三个常量来表示读、写、可执行权限,即R_OK、W_OK和X_OK。
下面实例演示chmod和access的用法:
#!/usr/bin/python
from __future__ import print_function
import os
import sys
def main():
sys.argv.append("")
filename = sys.argv[1]
if not os.path.isfile(filename):
raise SystemExit(filename + ' does not exits')
elif not os.access(filename, os.R_OK):
os.chmod(filename,0777)
else:
with open(filename) as f:
print(f.read)
if __name__ == '__main__':
main()
5.3 查找文件
5.3.1 使用fnmatch找到特定的文件
大部分情况下,使用字符串匹配查找特定的文件就能满足需求,如果需要更加灵活的字符串匹配,可以使用标准库的fnmatch。这个库专门用来进行文件名匹配,支持使用通配符进行字符串匹配。支持的字符串列表如下:
通配符 | 含义 |
---|---|
* | 匹配任何数量的字符 |
? | 匹配单个字符 |
[seq] | 匹配seq中的字符 |
[!seq] | 匹配除了seq以外的任何字符 |
fnmatch这个库比较简单,只有4个函数,分别是:
- fnmatch: 判断文件名是否符合特定的模式
- fnmatchcase: 判断文件名是否符合特定的模式,不区分大小写
- filter: 返回输入列表中,符合特定模式的文件名列表
- translate: 将通配符模式转换成正则表达式
使用示例:
import fnmatch
[ name for name in os.listdir('.') if fnmatch.fnmatch(name, '*.gz')]
[ name for name in os.listdir('.') if fnmatch.fnmatch(name, '[a-c]*')]
[ name for name in os.listdir('.') if fnmatch.fnmatch(name, '[a-c]*.gz')]
fnmatchcase函数与fnmatch函数几乎一样,只是在匹配文件名时会忽略文件名中字母的大小写。filter函数与fnmatch函数比较类似,区别在于fnmatch每次对一个文件名进行匹配判断,filter函数每次都一组文件名进行匹配判断。filter函数接受文件名列表为第一个参数,文件名模式为第二个参数,然后以列表的形式返回输出例表中符合模式的文件名。
5.3.2 使用glob找到特定的文件
在前面我们都是通过os.listdir获取文件列表,然后再去匹配。在Python中还有更加简单的方式,那就是使用标准库的glob库。glob的作用相当于os.listdir加上fnmatch。如下:
import glob
glob.glob('*.txt')
glob.glob('[a-c]?.txt')
5.3.3 使用os.walk遍历目录树
前面的示例都是查找当前目录下的内容,很多时候我们还需要查找其子目录里面匹配的内容。这时候就需要使用os模块中的walk函数了。对于每一个目录,walk返回一个三元组(dirpath,dirnames,filenames)。其中dirpath保存的是当前目录,dirnames是当前目录的子目录列表,filenames是当前目录下的文件列表。下面演示其用法
import os
import fnmatch
images = ['*.jpg','*.jpeg','*.png','*.tif']
matches = []
for root,dirnames,filenames in os.walk(os.path.expanduser('~pangcm')):
for extensions in images:
for filename in fnmatch.filter(filenames,extensions):
matches.append(os.path.join(root,filename))
print(matches)
如果要忽略某个子目录,只需要修改三元组中的dirnames即可。
if 'exclude_dir' in dirnames:
dirnames.remove('exclude_dir')
5.4 高级文件处理接口shutil
如果读者对比一下os模块的函数和shutil模块中的函数,会发现它们有一些重叠。例如os.rename和shutil.move都可以用来重命名一个文件。那么,为什么会存在两个模块提供相同功能的情况呢?这就涉及标准库模块定位的问题,os模块是对操作系统的接口进行封装,主要作用是跨平台。shutil模块包含复制、移动、重命名和删除文件及目录的函数,主要作用是管理文件和目录。因此,他们并不冲突,并且是互补的关系。对于常见的文件操作,shutil更易于使用。我们应该尽可能使用shutil里面的函数,在shutil里面没有相应的功能情况下再使用os模块下的函数。
5.4.1 复制文件和文件夹
shutil里面的复制类函数很多,用得最多的是copy和copytree。前者是复制一个文件,后者用来复制整个目录。
import shutil
shutil.copy('a.py','b.py')
shutil.copytree('dir1','dir2')
5.4.2 文件和文件夹的移动与改名
shutil模块中的move函数的作用几乎等同于Linux下的mv命令。可以用来移动或者重命名文件
shutil.move('b.py','a.py')
shutil.move('a.py','dir1')
5.4.3 删除目录
shutil.rmtree函数能够删除非空的目录,比起os.rmdir实用多了。
shutil.rmtree('dir1')
5.5 文件内容管理
5.5.1 目录和文件比较
filecmp模块包含了比较目录和文件的操作。其中filecmp模块最简单的函数是cmp函数,该函数用来比较两个文件是否相同,如果文件相同就返回True,否则返回False。
import filecmp
filecmp.cmp('a.txt','b.txt')
filecmp目录下还有一个名为cmpfiles的函数,该函数用来同时比较两个不同目录下的多个文件,并且返回一个三元组。分别包含相同的文件、不同的文件和无法比较的文件。
filecmp.cmpfiles('dir1','dir2',['a.txt', 'b.txt', 'c.txt', 'a_copy.txt'])
filecmp也可以用来对比两个目录,但是需要传入两个目录的所有文件,这个比较麻烦。比较目录的话,可以使用dircmp函数。调用dircmp函数以后会返回一个dircmp类的对象,该对象保存了诸多属性,工程师可以通过读取这些属性的方式获取目录之间的差异。要注意的是,dircmp不会去比较子目录下的文件的差异。
5.5.2 MD5校验和比较
在Python中计算文件的MD5校验码可以使用标准库中的hashlib模块
import hashlib
d = hashlib.md5()
with open('/etc/passwd') as f:
for line in f:
d.update(line)
d.hexdigest()
5.6 使用Python管理压缩包
5.6.1 使用tarfile库读取与创建tar包
在Linux中,tar命令可以创建一个tar包,而不进行压缩,这是为了方便传输。在Python中,tarfile标准库提供了相同的功能,我们可以使用它来创建一个压缩或者不压缩的tar包。
- 读取tar包
tarfile中有不少函数,其中最常用的有:
- getname: 获取tar包的文件列表
- extract: 提取单个文件
- extractall: 提取所有文件
示例:
import tarfile
with tarfile.open('tarfile_add.tar') as t:
for member_info in t.getmembers():
print(member_info.name)
- 创建tar包
创建tar包的方式和写一个文件比较类似,如下:
import tarfile
with tarfile.open('tarfile_add.tar',mode=w) as out:
out.add('Readme.txt')
5.6.2 使用tarfile库读取与创建压缩包
下面演示读取和创建经过压缩过的tar包
#读取
with tarfile.open('tarfile_add.tar',mode='r:gz') as t:
#创建
with tarfile.open('tarfile_add.tar',mode='w:bz2') as out:
5.6.4 使用zipfile库创建和读取zip压缩包
在windows下zip格式的压缩方式更为常见,下面介绍zipfile库来读取和创建zip压缩包
- 读取zip文件
zipfile的常用方法有:
- namelist: 返回zip文件中包含的所有文件和文件夹的字符串列表
- extract: 从zip文件中提取单个文件
- extractall: 从zip文件中提取所有文件
使用示例:
import zipfile
example_zip = zipfile.ZipFile('example.zip')
example_zip.namelist()
- 创建zip文件
import zipfile
new_zip = zipfile.ZipFile('new.zip','w')
new_zip.write('spam.txt')
new_zip.close()
- 使用Python的命令行工具创建zip格式的压缩包
在Linux中,我们使用zip命令来创建zip压缩包,使用unzip命令来解压。但是,Linux默认是没有安装这两个软件的。如果不想安装这两个软件,由于实现这功能可以使用Python中的zipfile模块,其提供了命令行接口。zipfile模块的命令行接口包含以下几个选项:
- -l: 显示zip格式压缩包中的文件列表
- -c: 创建zip格式的压缩包
- -e: 提取zip格式压缩包
- -t: 验证文件是一个有效的zip格式的压缩包
下面是示例:
pythom -m zipfile -c monty.zip spam.txt eggs.txt
pythom -m zipfile -e monty.zip target-dir/
pythom -m zipfile -l monty.zip
5.6.6 使用shutil创建和读取压缩包
shutil模块是高层次的文件接口,除了包含文件和目录的操作函数以外,还包含了压缩包的创建和压缩的函数。并且使用起来更加简单,shutil支持的格式可以通过get_archive_formats函数获取。
- shutil创建压缩包
使用shutil模块创建压缩包,只需要调用shutil模块下的make_archive函数即可。make_archive函数有多个参数,其中只有base_name与format这两个参数是必填的。参数base_name说是创建压缩包的名称,参数format用来指定压缩的格式。下面是一个示例:
import shutil
shutil.make_archive('backup','gztar')
需要注意的是base_name是压缩包的名称,但是不包含文件拓展名,拓展明会更加压缩的格式自动添加上去的。
- 在Python3中使用shutil读取压缩包
在Python2中,shutil模块包含了创建压缩包的函数,并没有解压的函数。在Python3中,shutil模块包含了解压函数,名为unpack_archive函数。示例:
import shutil
shutil.unpack_archive('backup.tar.gz')
5.7 Python中执行外部命令
5.7.1 subprocess模块简介
subprocess模块最早在Python2.4版本中引入的,正如它名字所反应的,这个模块用于创建和管理子进程。它提供了高层次的接口,用来替换os.system(),os.spawn*()等模块和函数。subprocess其实非常简单,它提供了一个名为Popen的类来启动和设置子进程的参数。由于这个类比较复杂,subprocess还提供了若干便利函数。这些便利函数都是对Popen这个类的封装,以便于工程师能够快速启动一个子进程并获取他们的输出结果。
5.7.2 subprocess模块的便利函数
在subprocess模块中启动子进程,最简单的方式就是使用这一节介绍的便利函数。当这些便利函数不能满足需求时,再去使用底层的Popen类。便利函数包括:call、check_call与check_ouput.
- call
call 函数的定义如下
subprocess.call(args,*,stdin=None,stdout=None,stderr=None,shell=False)
call函数运行由args参数指定的命名直到命令结束。call函数的返回值是命令的退出状态码,工程师可以退出状态码判断命令是否执行成功。如下:
import subprocess
subprocess.call(['ls','-l'])
subprocess.call('exit 1',shell=True)
call函数执行的外部命令以一个字符串列表的形式进行传递,如果设置了shell为True,则可以使用一个字符串命令,而不是一个字符串列表来运行子进程。如果设置了shell为True,Python将先运行一个shell,再用这个shell来解析整个字符串。
- check_call
与call函数类似,不同是是如果命令执行失败,check_call不是返回非0,而是抛出subprocess.CalledProcessError异常。
In [7]: subprocess.check_call('exit 1',shell=True)
---------------------------------------------------------------------------
CalledProcessError Traceback (most recent call last)
<ipython-input-7-0e1de6e31a15> in <module>()
----> 1 subprocess.check_call('exit 1',shell=True)
/usr/lib64/python2.7/subprocess.pyc in check_call(*popenargs, **kwargs)
540 if cmd is None:
541 cmd = popenargs[0]
--> 542 raise CalledProcessError(retcode, cmd)
543 return 0
544
CalledProcessError: Command 'exit 1' returned non-zero exit status 1
- check_output
上面两个函数的输出结果都是再命令行终端,check_out的输出结果不会输出到命令行终端,而是可以进一步处理。如下
output=subprocess.check_output(['df','-h'])
print(output)
check_output函数通过方式之来返回命令的执行结果,但是却没办法和call函数一样返回退出状态码来表示异常。因此,check_output函数通过抛出一个subprocess.CalledProcessError异常来表示命令执行出错。此外,check_output命令捕获命令的标准输出,要捕获错误输出,需要将错误输出重定向到标准输出,如下:
output=subprocess.check_output(['cmd','arg1','arg2'],stderr=subprocess.STDOUT)
5.7.3 subprocess模块的Popen类
当便利函数不能满足需求的时候,需要使用Popen类,它能处理更多的情况。Popen的基本使用方式与上一节中介绍的便利函数类似。在Linux系统中,到shell设置为True时,shell默认使用/bin/sh。args时需要执行的命令,可以时一个命令字符串,也可以时一个字符串列表。
Popen对象创建后,子进程便会运行。Popen类提供了若干方法来控制子进程的运行,包括:
- wait: 等待子进程结束
- poll: 检查子进程状态
- kill: 给子进程发送SIGKILL信号终止子进程
- send_signal: 给子进程发送信息
- terminate: 给子进程发送SIGTERM信号终止子进程
- communicate: 与子进程交互
其中,使用communicate函数可以与子进程进行交互,包括输入数据,获取子命令的标准输出和错误输出。下面的函数对Popen执行shell命令进行封装,封装以后,只需要将要执行的命令传递给函数即可。当命令执行成功时,将返回命令的退出状态码和标准输出,当命令执行失败时,将返回退出状态码和错误输出。
def excute_cmd(cmd):
p = subprocess.Popen(cmd,shell=True,stdin=subprocess.PIPE,
stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout,stderr = p.communicate()
if p.returncode != 0:
return p.returncode,stderr
return p.returncode, stdout
网友评论