一、 os模块与目录
python 3.4版本后支持了pathlib模块,它采用面向对象的方式处理路径,更加简洁方便,建议使用pathlib替代os模块。但在3.4之前还是只能使用传统的os模块。
1. 与目录相关的函数
- os.getcwd():获取当前目录。
- os.chdir(path):改变当前目录。
- os.fchdir(fd):通过文件描述符改变当前目录。与上一个函数的功能相似,只是以文件描述符作为参数来代表目录。
import os
# 获取当前目录
print(os.getcwd()) # E:\pytest02
# 改变当前目录
os.chdir('../')
# 再次获取当前目录
print(os.getcwd()) # E:\
- os.chroot(path):改变当前进程的根目录。
- os.listdir(path):同ls命令。
- os.mkdir(path[, mode]):创建path对应的目录,mode用于指定该目录的权限(类似linux)。
- os.makedirs(path[, mode]):类似mkdir(),但该函数可递归创建目录(相当于mkdir -p)
import os
path = 'my_dir'
# 直接在当前目录下创建目录
os.mkdir(path, 0o755)
path = "abc/xyz/wawa"
# 递归创建目录
os.makedirs(path, 0o755)
- os.rmdir(path):删除 path 对应的空目录。如果目录非空,可先用os.remove()函数删除文件,否则会抛出OSError异常。
- os.removedirs(path):递归删除目录。类似 rmdir(),但该函数可以递归删除。例如abc/xyz/wawa目录,它会从wawa子目录开始删除,然后删除 xyz 子目录,最后删除 abc 目录。
import os
path = 'my_dir'
# 直接删除当前目录下的子目录
os.rmdir(path)
path = "abc/xyz/wawa"
# 递归删除子目录
os.removedirs(path)
- os.rename(src, dst):重命名文件或目录,将 src 重名为 dst。
- os.renames(old, new):递归重命名文件或目录进行,功能类似于rename()。
import os
path = 'my_dir'
# 直接重命名当前目录下的子目录
os.rename(path, 'your_dir')
path = "abc/xyz/wawa"
# 递归重命名子目录
os.renames(path, 'foo/bar/haha')
2. 与权限相关的函数
- os.access(path, mode):检查path对应的文件或目录是否具有指定权限,mode可为以下一或多个值:
os.F_OK:判断是否存在
os.R_OK:判断是否可读
os.W_OK:判断是否可写
os.X_OK:判断是否可执行
import os
# 判断当前目录的权限
ret = os.access('.', os.F_OK|os.R_OK|os.W_OK|os.X_OK)
print("os.F_OK|os.R_OK|os.W_OK|os.X_OK - 返回值:", ret)
# 判断os.access_test.py文件的权限
ret = os.access('os.access_test.py', os.F_OK|os.R_OK|os.W_OK)
print("os.F_OK|os.R_OK|os.W_OK - 返回值:", ret)
- os.chrnod(path, mode):更改权限,mode 参数代表要改变的权限,该参数的值为以下一个或多个值的组合:
stat.S_IXOTH: 其他用户有执行权0o001
stat.S_IWOTH: 其他用户有写权限0o002
stat.S_IROTH: 其他用户有读权限0o004
stat.S_IRWXO: 其他用户有全部权限(权限掩码)0o007
stat.S_IXGRP: 组用户有执行权限0o010
stat.S_IWGRP: 组用户有写权限0o020
stat.S_IRGRP: 组用户有读权限0o040
stat.S_IRWXG: 组用户有全部权限(权限掩码)0o070
stat.S_IXUSR: 拥有者具有执行权限0o100
stat.S_IWUSR: 拥有者具有写权限0o200
stat.S_IRUSR: 拥有者具有读权限0o400
stat.S_IRWXU: 拥有者有全部权限(权限掩码)0o700
stat.S_ISVTX: 目录里文件目录只有拥有者才可删除更改0o1000
stat.S_ISGID: 执行此文件其进程有效组为文件所在组0o2000
stat.S_ISUID: 执行此文件其进程有效用户为文件所有者0o4000
stat.S_IREAD: windows下设为只读
stat.S_IWRITE: windows下取消只读
import os, stat
# 将os.chmod_test.py文件改为只读
os.chmod('os.chmod_test.py', stat.S_IREAD)
# 判断是否可写
ret = os.access('os.chmod_test.py', os.W_OK)
print("os.W_OK - 返回值:", ret)
- os.chown(path, uid, gid):更改文件的所有者,主要用于UNIX文件系统。
- os.fchmod(fd, mode):改变文件的访问权限,该文件由文件描述符 fd 指定。类似os.chmod() 函数,只是用fd代表文件。
- os.fchown(fd, uid, gid):改变文件的所有者,该文件由文件描述符 fd 指定。类似os.chown() 函数,只是用fd代表文件。
3. 与文件访问相关的函数
- os.open(file, flags[, mode]):打开一个文件并设置打开选项,该函数返回文件描述符。flags支持如下一或多个选项:
os.O_RDONLY:以只读的方式打开。
os.O_WRONLY:以只写的方式打开。
os.O_RDWR:以读写的方式打开。
os.O_NONBLOCK:打开时不阻塞。
os.O_APPEND:以追加的方式打开。
os.O_CREAT:创建并打开一个新文件。
os.O_TRUNC:打开一个文件并截断它的长度为0(必须有写权限)。
os.O_EXCL:在创建文件时,如果指定的文件存在,则返回错误。
os.O_SHLOCK:自动获取共享锁。
os.O_EXLOCK:自动获取独立锁。
os.O_DIRECT:消除或减少缓存效果。
os.O_FSYNC:同步写入。
os.O_NOFOLLOW:不追踪软链接。
- os.read(fd, n):从文件描述符 fd 中读取最多 n 个字节,返回读到的字符串。如果文件描述符fd对应的文件己到达结尾,则返回一个空字节串。
- os.write(fd, str):将字节串写入文件描述符 fd 中,返回实际写入的字节串长度。
- os.close(fd):关闭文件描述符 fd。
- os.lseek(fd, pos, how):该函数同样用于移动文件指针。其中 how 参数指定从哪里开始移动,如果将 how 设为 0 或 SEEK_SET,则表明从文件开头开始移动;设为 1 或 SEEK_CUR,则表明从文件指针当前位置开始移动;设为 2 或 SEEK_END,则表明从文件结束处开始移动。
上面几个函数可用于执行文件的读写,程序通常会先通过 os.open() 打开文件,然后调用 os.read()、os.write() 来读写文件,当操作完成后通过 os.close() 关闭文件。
import os
# 以读写、创建方式打开文件
f = os.open('abc.txt', os.O_RDWR|os.O_CREAT)
# 写入文件内容
len1 = os.write(f, '水晶潭底银鱼跃,\n'.encode('utf-8'))
len2 = os.write(f, '清徐风中碧竿横。\n'.encode('utf-8'))
# 将文件指针移动到开始处
os.lseek(f, 0, os.SEEK_SET)
# 读取文件内容
data = os.read(f, len1 + len2)
# 打印读取到字节串
print(data)
# 将字节串恢复成字符串
print(data.decode('utf-8'))
os.close(f)
- os.fdopen(fd[, mode[, bufsize]]):通过文件描述符 fd 打开文件,并返回对应的文件对象。
- os.closerange(fd_low, fd_high):关闭 [fd_low,fd_high)范围内(前闭后开)的所有文件描述符。
- os.dup(fd):复制文件描述符。
- os.dup2(fd,fd2):将文件描述符fd复制到另一个文件描述符fd2中。
- os.ftruncate(fd, length):将 fd 对应的文件截断到 length 长度(length 参数不应超过文件大小)
- os.remove(path):删除 path 对应的文件。若path 是文件夹,则抛出 OSError 错误。如果要删除目录,则使用 os.rmdir()。
- os.link(src, dst):创建从 src 到 dst 的硬链接。硬链接是 UNIX 系统的概念,如果在 Windows 系统中就是复制目标文件。
- os.symlink(src, dst):创建从 src 到 dst 的符号链接,对应于 Windows 的快捷方式(需管理员身份执行)。
import os
# 为os.link_test.py文件创建快捷方式
os.symlink('os.link_test.py', 'tt')
# 为os.link_test.py文件创建硬连接(Windows上就是复制文件)
os.link('os.link_test.py', 'dst')
运行上面程序,将会看到程序在当前目录下创建了一个名为“tt”的快捷方式,并将 os.link_test.py 文件复制为 dst 文件。
4. os.path 模块
os.path模块下也提供了一些操作目录的方法,这些函数可以操作系统的目录本身。
一些使用os.path的例子
import os
import time
# 获取绝对路径
print(os.path.abspath("log.txt")) # E:\pytest02\log.txt
# 获取共同前缀
print(os.path.commonprefix(['E:\pytest02', 'E:\pytest02\first_package'])) #E:\pytest02
# 获取共同路径
print(os.path.commonpath(['E:\pytest02', 'E:\pytest02\first_package'])) #E:\
# 获取目录
print(os.path.dirname('E:\pytest02')) #E:\
# 判断指定目录是否存在
print(os.path.exists('E:\pytest02')) # True
# 获取最近一次访问时间
print(time.ctime(os.path.getatime('testIPy.py'))) # Fri Oct 4 11:03:33 2019
# 获取最后一次修改时间
print(time.ctime(os.path.getmtime('testIPy.py'))) # Fri Oct 4 11:02:59 2019
# 获取创建时间
print(time.ctime(os.path.getctime('testIPy.py'))) # Fri Oct 4 11:02:59 2019
# 获取文件大小
print(os.path.getsize('testIPy.py')) # 514
# 判断是否为文件
print(os.path.isfile('testIPy.py')) # True
# 判断是否为目录
print(os.path.isdir('testIPy.py')) # False
# 判断是否为同一个文件
print(os.path.samefile('testIPy.py', './testIPy.py')) # True
二、 使用pathlib模块操作目录
pathlib在python 3.4版本之后支持,它采用面向对象的方式处理路径,更加简洁方便,建议使用pathlib替代os模块。pathlib模块提供了一组面向对象的类(如图),这些类可代表各种os上的路径,程序可通过这些类操作路径。
PurePath:代表并不访问实际文件系统的“纯路径”。它只负责对路径字符串执行操作而不关心该字符串是否对应实际路径。其下有PurePosixPath和PureWindowsPath子类,分别代表Unix和Windows风格的路径。
Path:代表访问实际文件系统的“真正路径”。Path对象可用于判断对应文件是否存在、是否为文件或目录等。其下也有PosixPath和WindowsPath子类,分别代表Unix和Windows风格的路径。
Unix风格:根路径为/,分隔符也为/
Windows风格:根路径为盘符(C:等),分隔符为反斜杠\
1. PurePath的基本功能
程序可使用PurePath或其两个子类来创建PurePath对象。创建时既可传入单个或多个路径字符串,PurePath会将它们拼接成一个字符串。
在unix或Mac系统上使用PurePath创建对象,程序实际会返回PurePosixPath对象;在Windows系统上使用PurePath创建对象,程序实际会返回PureWindowsPath对象。
import pathlib as pl
#如果在创建 PurePath 时不传入任何参数,系统会创建代表当前路径的PurePath,相当于传入点号.
pp = PurePath()
print(pp) # .
pp=pl.PurePath('setup.py')
print(type(pp)) #<class 'pathlib.PureWindowsPath'>
print(pp) #setup.py
pp=pl.PurePath('data','mssql/path','info')
print(pp) #data\mssql\path\info
pp=pl.PurePath(pl.Path('hello'),pl.Path('info'))
print(pp) #hello\info
pp=pl.PurePosixPath('data','mssql/path','info')
print(pp) #data/mssql/path/info
#若传入参数有多个包含根目录符号,只有最后一个及其后子目录会生效
pp=pl.PurePosixPath('/data','/mssql/path','info')
print(pp) #/mssql/path/info(/data被忽略)
#若在创建时传入多余的/或.(..除外) 系统会直接忽略
pp=pl.PurePosixPath('data/./data2','mssql//path','info/../info2')
print(pp) #data/data2/mssql/path/info/../info2
PurePath对象可进行比较运算
import pathlib as pl
#比较两个Unix风格路径(区分大小写)
print(pl.PurePosixPath('data')==pl.PurePosixPath('Data')) #False
#比较两个Windows风格路径(不分大小写)
print(pl.PureWindowsPath('data')==pl.PureWindowsPath('Data')) #True
2. PurePath的属性和方法
PurePath 提供的属性和方法主要用于操作路径字符串。由于 PurePath 并不真正执行底层的文件操作,也不理会路径字符串在底层是否有对应的路径,因此这些操作类似字符串方法。
类属性和方法名 | 功能描述 |
PurePath.parts | 该属性返回路径字符串中所包含的各部分。 |
PurePath.drive | 该属性返回路径字符串中的驱动器盘符。 |
PurePath.root | 该属性返回路径字符串中的根路径。 |
PurePath.anchor | 该属性返回路径字符串中的盘符和根路径。 |
PurePath.parents | 该属性返回当前路径的全部父路径。 |
PurPath.parent | 该属性返回当前路径的上一级路径,相当于 parents[0] 的返回值。 |
PurePath.name | 该属性返回当前路径中的文件名。 |
PurePath.suffixes | 该属性返回当前路径中的文件所有后缀名。 |
PurePath.suffix | 该属性返回当前路径中的文件后缀名,相当于suffixes属性返回列表的最后一项。 |
PurePath.stem | 该属性返回当前路径中的主文件名。 |
PurePath.as_posix() | 将当前路径转换成UNIX风格。 |
PurePath.as_uri() | 将当前路径转换成 URI。只有绝对路径才能转换,否则将会引发 ValueError。 |
PurePath.is_absolute() | 判断当前路径是否为绝对路径。 |
PurePath.joinpath(*other) | 将多个路径连接在一起,作用类似于前面介绍的斜杠运算符。 |
PurePath.match(pattern) | 判断当前路径是否匹配指定通配符。 |
PurePath.relative_to(*other) | 获取当前路径中去除基准路径之后的结果。 |
PurePath.with_name(name) | 将当前路径中的文件名替换成新文件名。若仅指定路径而没有文件名,会引发 ValueError。 |
PurePath.with_suffix(suffix) | 将当前路径中的文件后缀名替换成新的后缀名。若参数中未指定后缀名,会自动添加。 |
from pathlib import *
# 返回路径字符串中的驱动器盘符
print(PureWindowsPath('c:/Program Files/').drive) # c:
print(PureWindowsPath('/Program Files/').drive) # ''
print(PurePosixPath('/etc').drive) # ''
# 返回路径字符串中的根路径
print(PureWindowsPath('c:/Program Files/').root) # \
print(PureWindowsPath('c:Program Files/').root) # ''
print(PurePosixPath('/etc').root) # /
# 返回路径字符串中的盘符和根路径
print(PureWindowsPath('c:/Program Files/').anchor) # c:\
print(PureWindowsPath('c:Program Files/').anchor) # c:
print(PurePosixPath('/etc').anchor) # /
# 返回当前路径的全部父路径
pp = PurePath('abc/xyz/wawa/haha')
print(pp.parents[0]) # abc\xyz\wawa
print(pp.parents[1]) # abc\xyz
print(pp.parents[2]) # abc
print(pp.parents[3]) # .
# 访问parent属性
print(pp.parent) # abc\xyz\wawa
# 返回当前路径中的文件名
pp = PurePath('abc/wawa/bb.txt')
print(pp.name) # bb.txt
# 返回当前路径中的文件所有后缀名
pp = PurePath('abc/wawa/bb.txt.tar.zip')
print(pp.suffixes[0]) # .txt
print(pp.suffixes[1]) # .tar
print(pp.suffixes[2]) # .zip
#返回当前路径中的文件后缀名,相当于suffixes属性返回列表的最后一项。
print(pp.suffix) # .zip
#返回当前路径中的主文件名
print(pp.stem) # bb.txt.tar
pp = PurePath('abc', 'xyz', 'wawa', 'haha')
print(pp) # abc\xyz\wawa\haha
# 将当前路径转换成UNIX风格
print(pp.as_posix()) # abc/xyz/wawa/haha
# 创建绝对路径
pp = PurePath('d:/', 'Python', 'Python3.6')
# 将绝对路径转换成Uri(相对路径转URI会报错)
print(pp.as_uri()) # file:///d:/Python/Python3.6
# 判断当前路径是否匹配指定通配符
print(PurePath('a/b.py').match('*.py')) # True
print(PurePath('/a/b/c.py').match('a/*.py')) # False
# 获取当前路径中去除基准路径之后的结果
pp = PurePosixPath('c:/abc/xyz/wawa')
print(pp.relative_to('c:/')) # abc\xyz\wawa
print(pp.relative_to('c:/abc')) # xyz\wawa
print(pp.relative_to('c:/abc/xyz')) # wawa
# 将当前路径中的文件名替换成新文件名
p = PureWindowsPath('e:/Downloads/pathlib.tar.gz')
print(p.with_name('fkit.py')) # e:\Downloads\fkit.py
# 若仅指定路径而没有文件名,会引发 ValueError
p = PureWindowsPath('c:/')
#print(p.with_name('fkit.py')) # ValueError
# 将当前路径中的文件后缀名替换成新的后缀名
p = PureWindowsPath('e:/Downloads/pathlib.tar.gz')
print(p.with_suffix('.zip')) # e:\Downloads\pathlib.tar.zip
# 若参数中未指定后缀名,会自动添加
p = PureWindowsPath('README')
print(p.with_suffix('.txt')) # README.txt
3. Path类的功能和用法
Path是PurePath的子类,支持上面 PurePath 的各种操作,并且会真正访问底层的文件系统,包括判断指定是否存在、获取指定路径的各种属性(是否只读、是文件还是文件夹等),甚至可以对文件进行读写。
Path的属性及方法可参考官方文档,大部分名字跟linux命令是一样的 https://docs.python.org/zh-cn/3/library/pathlib.html
Path常用方法:
- iterdir()方法:返回指定目录下的所有子目录和文件
- glob()方法:获取指定目录及其子目录下匹配指定模式的所有文件,可以非常方便地查找指定文件
from pathlib import *
# 获取指定目录下所有文件和子目录
p = Path('E:/pytest02/first_package')
for x in p.iterdir():
print(x)
#输出
E:\pytest02\first_package\__init__.py
E:\pytest02\first_package\__pycache__
# 获取指定目录及其所有子目录下的.txt文件
p = Path('E:/pytest02')
# "**" 启用递归通配“此目录以及所有子目录”
for x in p.glob('**/*.txt'):
print(x) #E:\pytest02\log.txt
- read_bytes() 和 read_text(encoding=None, errors=None) 方法:分别用于读取该 Path 对应文件的字节数据(二进制数据)和文本数据
- write_bytes(data) 和 Path.write_text(data, encoding=None, errors=None) 方法:分别用于输出字节数据(二进制数据)和文本数据
from pathlib import *
p = Path('test.txt')
# 以GBK字符集输出文本内容
result = p.write_text('''暗淡轻黄体性柔
情疏迹远只香留''', encoding='GBK')
# 返回输出的字符数
print(result) # 15
# 指定以GBK字符集读取文本内容
content = p.read_text(encoding='GBK')
# 输出读取的文本内容
print(content)
#输出
暗淡轻黄体性柔
情疏迹远只香留
# 读取字节内容
bb = p.read_bytes()
print(bb)
#输出
b'\xb0\xb5\xb5\xad\xc7\xe1\xbb\xc6\xcc\xe5\xd0\xd4\xc8\xe1\r\n\xc7\xe9\xca\xe8\xbc\xa3\xd4\xb6\xd6\xbb\xcf\xe3\xc1\xf4'
4. fnmatch模块——用于文件名的匹配
前面介绍的那些操作目录的函数只能进行简单的模式匹配,但 fnmatch 模块可支持类似shell风格的文件名匹配。
fnmatch 匹配支持如下通配符:
- *:可匹配任意个任意字符。
- ?:可匹配一个任意字符。
- [字符序列]:可匹配中括号里字符序列中的任意字符。也支持中画线表示法,比如[a-c]可代表a、b 和 c 字符中任意一个。
- [!字符序列]:可匹配不在中括号里字符序列中的任意字符。
fnmatch 模块下提供了如下函数:
- fnmatch.fnmatch(filename, pattern):判断指定文件名是否匹配指定 pattern,不区分大小写。
- fnmatch.fnmatchcase(filename, pattern):与上一个函数的功能大致相同,只是区分大小写。
import fnmatch
import os
for file in os.listdir('.'):
if fnmatch.fnmatchcase(file, '*ipy.py'):
print(file) # 无输出
if fnmatch.fnmatch(file, '*ipy.py'):
print(file) # testIPy.py
- fnmatch.filter(names, pattern):对names列表进行过滤,返回names列表中匹配pattern的文件名组成的列表。
import fnmatch
names = ['a.py', 'b.py', 'c.py', 'd.py']
# 对names列表进行过滤
sub = fnmatch.filter(names, '[ac].py')
print(sub) # ['a.py', 'c.py']
- fnmatch.translate(pattern):该函数将shell风格的pattern转换为正则表达式pattern。
import fnmatch
print(fnmatch.translate('?.py')) # (?s:.\.py)\Z
print(fnmatch.translate('[ac].py')) # (?s:[ac]\.py)\Z
print(fnmatch.translate('[a-c].py')) # (?s:[a-c]\.py)\Z
三、 打开文件
想要操作文件,首先需要创建或者打开指定的文件,并创建一个文件对象,这些工作可通过内置的 open() 函数实
open() 函数用于创建或打开指定文件,该函数的语法格式如下:
file = open(file_name [, mode[, buffering]])
- file:表示要创建的文件对象。
- file_mode:要创建或打开的文件名。
- mode:可选参数,用于指定文件的打开模式,默认以只读(r)模式打开文件。
- buffering:可选参数,用于指定对文件做读写操作时,是否使用缓冲区。
1. 关于打开模式
打开模式 | 含义 |
r | 只读模式 |
w | 清空写模式(默认清空原文件) |
a | 追加写模式 |
+ | 读写模式,可与其他模式结合使用(r+,w+,a+均表示读写模式) |
b | 二进制模式,可与其他模式结合使用(rb为二进制只读、rb+为二进制读写、ab为二进制追加写) |
- 使用r或r+模式要求被打开的文件是已存在的,否则会报错;使用w,w+,a,a+模式,若被打开文件不存在会自动创建
- 通常程序读写文本文件以外的音频、视频、图片等都需以二进制模式访问,关于文本文件和二进制文件的区别,参考http://c.biancheng.net/view/4723.html
2. 关于缓冲区
内存中程序的读写速度很快,如果不使用缓冲,则程序必须等待外设进行同步读写。因此,一般建议打开缓冲。
在使用 open() 函数时,buffering可取值如下:
- 0或 False,该函数打开的文件不带缓冲
- 1或 True,带缓冲的,此时程序执行 I/O 将具有更好的性能
- 大于 1 的整数,则该整数用于指定缓冲区的大小(单位是字节)
- 负数,代表使用默认缓冲区大小
3. open()文件对象常用属性
成功打开文件之后,可以调用文件对象本身拥有的属性获取当前文件的部分信息,其常见的属性为:
- file.closed:判断文件是否己经关闭。
- file.mode:返回被打开文件的访问模式。
- file.name:返回文件的名称。
f=open('test.txt')
#查看文件编码方式
print(f.encoding) #cp936
#查看文件访问模式
print(f.mode) #r
#查看文件是否以关闭
print(f.closed) #False
#查看文件对象打开的文件名
print(f.name) #test.txt
四、 读取文件
读取方法很多,主要有以下7种
1. 按字节或字符读取文件——read()
read()函数可以按字节或字符读取文件内容。open()函数打开文件时,使用了 b 模式则每次读取一个字节;反之每次读取一个字符。
read() 函数的基本语法如下:
file.read([size])
file表示文件对象;size用于指定要读取的字符个数,如果省略,则默认读取所有内容。
f = open("test.txt", 'r', True)
while True:
# 每次读取一个字符
ch = f.read(1)
# 如果没有读到数据,跳出循环
if not ch:
break
# 输出ch
print(ch, end='')
f.close()
#输出
暗淡轻黄体性柔
情疏迹远只香留
上面程序采用循环依次读取每一个字符(因为程序没有使用 b 模式),每读取到一个字符,程序就输出该字符。
调用 read() 方法时不传入参数,该方法默认会读取全部文件内容。
f = open("a.txt", 'r', True)
# 直接读取全部文件
print(f.read())
f.close()
当程序读写完文件之后,推荐立即调用 close() 方法来关闭文件,这样可以避免资源泄露。想要更加安全地关闭文件,推荐将close() 方法放到finally块中。
当使用 open() 函数打开文本文件时,默认会使用当前os字符集,比如 Windows 平台,open() 函数默认使用 GBK 字符集。因此,上面程序读取的文件也必须使用 GBK 字符集保存,否则程序会出现UnicodeDecodeError错误。
如果要读取的文件所使用的字符集和当前os字符集不匹配,有两种解决方式:
- 使用二进制模式读取,然后用bytes的decode()方法恢复成字符串。
- 利用codecs模块的open()函数来打开文件,该函数在打开文件时允许指定字符集。
# 指定使用二进制方式读取文件内容,a.txt 以 utf-8 编码存储
f = open("a.txt", 'rb', True)
# 直接读取全部文件,并调用bytes的decode将字节内容恢复成字符串
print(f.read().decode('utf-8'))
f.close()
上面程序在调用 open() 函数时,传入了 rb 模式,这表明采用二进制模式读取文件,此时文件对象的 read() 方法返回的是 bytes 对象,程序可调用 bytes 对象的 decode() 方法将它恢复成字符串。由于此时读取的 a.txt 文件是以 UTF-8 的格式保存的,因此程序需要使用 decode() 方法恢复字符串时显式指定使用 UTF-8 字符集。
import codecs
#指定使用utf-8 字符集读取文件内容
f = codecs.open("a.txt", 'r', 'utf-8', buffering=True)
print(f.read())
f.close()
上面程序在调用 open() 函数时显式指定使用 UTF-8 字符集,这样程序在读取文件内容时就完全没问题了。
2. 按行读取文件——readline()和readlines()
使用 read() 函数读取文件时,如果文件过大,一次读取全部内容到内存容易造成内存不足,相比每次限制读取字符(或字节)的个数,更推荐使用逐行读取文件的方式。
一般情况下,逐行读取只适用于以文本格式打开的文件,因为只有文本文件才有行的概念,二进制文件没有。
readline() 函数用于读取一行内容,而 readlines() 函数用于读取文件内的所有行。
readline()函数
readline() 函数用于读取文件中的一行,包含最后的换行符“\n”。此函数的基本语法格式为:
file.readline([size])
file 为打开的文件对象;size指定读取每一行时,一次最多读取的字符数。
f = open("test.txt", 'r', True)
while True:
# 每次读取一行
line = f.readline()
# 如果没有读到数据,跳出循环
if not line:
break
print(line)
f.close()
#输出
暗淡轻黄体性柔
情疏迹远只香留
由于readline()函数在读取文件中一行的内容时会读取最后的换行符“\n”,而print()函数输出内容时默认会换行,所以输出结果中会看到多出了一个空行。
在逐行读取时,也可以限制每次最多读取的字符数,例如:
f = open("test.txt", 'r', True)
while True:
# 每次读取一行
line = f.readline(4)
# 如果没有读到数据,跳出循环
if not line: break
# 输出line
print(line)
f.close()
#输出
暗淡轻黄
体性柔
情疏迹远
只香留
readlines()函数
readlines() 函数用于读取文件中的所有行,与不指定size参数的 read() 函数功能类似,只不过该函数返回是一个字符串列表,其中每个元素为文件中的一行内容。readlines() 函数在读取每行时也会读取行尾的\n。
f = open("test.txt", 'r', True)
lines = f.readlines()
for line in lines:
print(line)
#输出
暗淡轻黄体性柔
情疏迹远只香留
3. 逐行读取多个文件——fileinput模块
fileinput 模块提供了input函数,可以合并多个输入流
fileinput.input(files="filename1, filename2, ...", inplace=False, backup='', bufsize=0, mode='r', openhook=None)
此函数会返回一个 FileInput 对象,其中各个参数的含义如下:
- files:多个文件的路径列表;
- inplace:是否将标准输出写回到文件,此参数默认值为 False;
- backup:指定备份文件的扩展名;
- bufsize:指定缓冲区的大小,默认为 0;
- mode:打开文件的模式,默认为 r(只读);
- openhook:控制文件的打开方式,例如编码格式等。
创建FileInput对象之后,即可通过for循环遍历文件每一行。此外,fileinput 还提供了很多函数获取正在读取的文件信息:
函数名 | 功能描述 |
fileinput.filename() | 返回正在读取的文件的文件名。 |
fileinput.fileno() | 返回当前文件的文件描述符(file descriptor,是一个整数)。 |
fileinput.lineno() | 返回当前读取的行号。 |
fileinput.filelineno() | 返回当前读取的行在其文件中的行号。 |
fileinput.isfirstline() | 返回当前读取的行在其文件中是否为第一行。 |
fileinput.isstdin() | 返回最后一行是否从 sys.stdin 读取。程序可以使用“-”代表从 sys.stdin 读取。 |
fileinput.nextfile() | 关闭当前文件,开始读取下一个文件。 |
fileinput.close() | 关闭 FileInput 对象。 |
FileInput也存在一个缺陷——在创建 FileInput 对象时不能指定字符集,它所读取的文件的字符集必须与os默认字符集保持一致。当然如果文本文件的内容是纯英文,则不存在问题。
import fileinput
# 一次读取多个文件
for line in fileinput.input(files=('log.txt', 'test.txt')):
# 输出文件名,当前行在当前文件中的行号
print(fileinput.filename(), fileinput.filelineno(), line)
# 关闭文件流
fileinput.close()
#输出
log.txt 1 若无闲事挂心头
log.txt 2 便是人间好时节
test.txt 1 暗淡轻黄体性柔
test.txt 2 情疏迹远只香留
4. 文件迭代器
实际上文件对象本身就是可遍历的,因此完全可用for-in循环遍历文件内容。
# 读取文件内容
file=open("log.txt",'r',True)
for line in file:
print(line,end='')
file.close()
#输出
若无闲事挂心头
便是人间好时节
若有需要,程序也可使用list()将文件转换成列表,与readlines()方法的返回值一样
print(list(open("log.txt",'r',True))) # ['若无闲事挂心头\n', '便是人间好时节']
sys.stdin也是一个类文件对象,程序同样可以for-in循环遍历sys.stdin
import sys
for line in sys.stdin:
print('用户输入:',line,end='')
#测试
hhhhhhhhhhhhds
用户输入: hhhhhhhhhhhhds
dddddddddddd
用户输入: dddddddddddd
5. 管道输入
某些时候,程序希望读取的输入是来自每个命令,此时就需要使用管道输入(用法同linux)
# 两个文件分别如下:
# write1.py:
print("Hello World!")
print(50)
# read.py
print('this is my got string:%s'%input())
import sys
data = sys.stdin.readline()[:-1]
print('this data is :'+data+' double is :',int(data)*2)
#调用:
python write1.py | read1.py
#输出
this is my got string:Hello World!
this data is :50 double is : 100
6. 自动关闭文件——with as语句
前面进行文件操作时,需要手动关闭,否则会程序的运行造成意想不到的隐患。但是即便使用 close() 做好了关闭文件的操作,如果在打开文件或文件操作过程中抛出了异常,还是无法及时关闭文件。为了更好地避免此类问题,不同的编程语言都引入了不同的机制。
在Python中,对应的解决方式是使用 with as 语句操作上下文管理器(context manager),它能够帮助我们自动分配并且释放资源。使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。
with 表达式 [as target]:
代码块
target 参数用于指定一个变量,语句会将表达式的结果保存到该变量中。
with open('test.txt', 'r') as f:
for line in f:
print(line,end='')
#输出
暗淡轻黄体性柔
情疏迹远只香留
也可使用with语句处理fileinput.input合并的多个文件
import fileinput
with fileinput.input(files=('log.txt','test.txt')) as f:
for line in f:
print(line)
#输出
若无闲事挂心头
便是人间好时节
暗淡轻黄体性柔
情疏迹远只香留
with语句实现原理
使用with as 语句管理的资源(操作对象)必须是上下文管理器。那么,到底什么是上下文管理器呢?
上下文管理器是实现了上下文管理协议(context manage protocol)的类的对象。要实现上下文管理协议,必须实现context_manager.__enter()__和context_manager.__exit__方法。
简单的理解,同时包含 __enter__() 和 __exit__() 方法的对象就是上下文管理器。
- __enter__(self):进入上下文管理器自动调用的方法,该方法会在 with as 代码块执行之前执行。如果 with 语句有 as子句,那么该方法的返回值会被赋值给 as 子句后的变量;该方法可以返回多个值,因此在 as 子句后面也可以指定多个变量(以元组形式传入)。
- __exit__(self, exc_type, exc_value, exc_traceback):退出上下文管理器自动调用的方法。该方法会在 with as 代码块执行之后执行。如果 with as 代码块成功执行结束,程序自动调用该方法,调用该方法的三个参数都为 None;如果 with as 代码块因为异常而中止,程序也自动调用该方法,使用 sys.exc_info 得到的异常信息将作为调用该方法的参数。
构建上下文管理器常见有 2 种方式——基于类实现和基于生成器实现。
基于类的上下文管理器
只要一个类实现了 __enter__() 和 __exit__() 2个方法,程序就可以使用 with as 语句来管理它,通过 __exit__() 方法的参数,即可判断出 with 代码块执行时是否遇到了异常。其实上面程序中的文件对象和FileInput对象也实现了这两个方法,因此可以接受 with as 语句管理。
下面自定义一个实现上下文管理协议的类,并尝试用 with as 语句来管理:
class FkResource:
def __init__(self, tag):
self.tag = tag
print('构造器,初始化资源: %s' % tag)
# 定义__enter__方法,with体之前的执行的方法
def __enter__(self):
print('[__enter__ %s]: ' % self.tag)
# 该返回值将作为as子句中变量的值
return 'fkit' # 可以返回任意类型的值
# 定义__exit__方法,with体之后的执行的方法
def __exit__(self, exc_type, exc_value, exc_traceback):
print('[__exit__ %s]: ' % self.tag)
# exc_traceback为None,代表没有异常
if exc_traceback is None:
print('没有异常时关闭资源')
else:
print('遇到异常时关闭资源')
return False # 可以省略,默认返回None也被看做是False
with FkResource('孙悟空') as dr:
print(dr)
print('[with代码块] 没有异常')
print('------------------------------')
with FkResource('白骨精'):
print('[with代码块] 异常之前的代码')
raise Exception
print('[with代码块] ~~~~~~~~异常之后的代码')
#输出
构造器,初始化资源: 孙悟空
[__enter__ 孙悟空]:
fkit
[with代码块] 没有异常
[__exit__ 孙悟空]:
没有异常时关闭资源
------------------------------
构造器,初始化资源: 白骨精
[__enter__ 白骨精]:
[with代码块] 异常之前的代码
[__exit__ 白骨精]:
遇到异常时关闭资源
Traceback (most recent call last):
File "E:/pytest02/var.py", line 25, in <module>
raise Exception
Exception
程序中两次使用 with as 语句管理 FkResource 对象。第一次代码块没有出现异常,第二次出现了异常。从上面的输出结果来看,使用 with as 语句管理资源,无论代码块是否有异常,程序总可以自动执行 __exit__() 方法。
注意,当出现异常时,如果 __exit__ 返回 False(默认不写返回值时,即为 False),则会重新抛出异常,让 with as 之外的语句逻辑来处理异常;反之,如果返回 True,则忽略异常,不再对异常进行处理。
基于生成器的上下文管理器
除了基于类的上下文管理器,它还可以基于生成器实现。接下来先看一个例子。比如,我们可以使用装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器,用以支持 with as 语句:
from contextlib import contextmanager
@contextmanager
def file_manager(name, mode):
try:
f = open(name, mode)
yield f
finally:
f.close()
with file_manager('a.txt', 'w') as f:
f.write('hello world')
这段代码中,函数 file_manager() 就是一个生成器,当执行 with as 语句时,便会打开文件并返回文件对象 f;with 语句执行完后,finally 中的关闭文件操作便会执行。
使用基于生成器的上下文管理器时,不再用定义 __enter__() 和 __exit__() 方法,但需要加上装饰器 @contextmanager。
需要强调的是,基于类的上下文管理器和基于生成器的上下文管理器在功能上是一致的。只不过,基于类的上下文管理器更加灵活,适用于大型的系统开发,而基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。无论使用哪一种,不要忘记在方法“__exit__()”或者是 finally 块中释放资源,这一点尤其重要。
7. 随机读取文件指定行——linecache模块
linecache模块允许从Python源文件中随机读取指定行,并在内部使用缓存优化存储。由于该模块主要被设计成读取 Python 源文件,因此它会用 UTF-8 字符集来读取文本文件。只要文件使用了UTF-8字符集存储,linecache模块也可读取其他文件。
linecache 模块包含以下常用函数:
- linecache.getline(filename, lineno, module_globals=None):读取指定文件的指定行。
- linecache.clearcache():清空缓存。
- linecache.checkcache(filename=None):检查缓存是否有效。如果未指定filename参数,默认检查所有缓存的数据。
下面程序示范了使用 linecache 模块来随机读取指定行:
import linecache
import random
# 读取random模块的源文件的第3行
print(linecache.getline(random.__file__, 3)) # integers
# 读取普通文件的第2行
print(linecache.getline('var.py', 2)) # import random
五、 写文件
1. 文件指针
文件指针用于标明文件读写的起始位置,如下图
seek()函数
seek() 函数用于将文件指针移动至指定位置,该函数的语法格式如下:
file.seek(offset[, whence])
- file:表示文件对象;
- whence:用于指定文件指针开始位置,有3个可选值。0代表从文件开头计算(默认值)、1代表从当前位置计算、2 代表文件结尾计算。
- offset:表示相对于 whence 位置的偏移量,正数表示向后偏移,负数表示向前偏移。
例如,whence为0&&offset为3(即 seek(3,0) ),表示文件指针从文件开头向后移动3个字节位置;whence为1&&offset为5(即 seek(5,1) ),表示文件指针从当前位置向后移动5个字节位置。
注意若offset为非0值,文件必须要以二进制格式打开,否则会抛出io.UnsupportedOperation错误
tell() 函数
判断文件指针位置,其基本语法格式如下:
file.tell()
f = open('log.txt', 'rb')
# 判断文件指针的位置
print(f.tell()) # 0
# 读取一个字节,文件指针自动后移1个数据
print(f.read(1)) # b'\xc8'
print(f.tell()) # 1
# 将文件指针从文件开头,向后移动到 5 个字符的位置
f.seek(5)
print(f.tell()) # 5
print(f.read(1)) # b'\xd0'
# 将文件指针从当前位置,向后移动到 5 个字符的位置
f.seek(5, 1)
print(f.tell()) # 11
print(f.read(1)) # b'\xc4'
# 将文件指针从文件结尾,向前移动到距离 10 个字符的位置
f.seek(-1, 2)
print(f.tell()) # 29
print(f.read(1)) # b'\xda'
2. 写文件——write()和writelines()
write() 函数
可以向文件中写入指定内容(字符串或字节串)。该函数的语法格式如下:
file.write(string或bytes)
在使用 write() 向文件中写入数据,需保证使用 open() 函数是以 r+、w、w+、a 或 a+ 的模式打开文件,否则执行 write() 函数会抛出 io.UnsupportedOperation 错误。
f = open("a.txt", 'w')
f.write("写入一行新数据") #注意若a.txt已存在会清空其内容
f.close()
如果打开文件模式包含 a(追加),则不会清空原有内容,而是将新写入的内容追加到原内容后边。
f = open("a.txt", 'a')
f.write("\n写入一行新数据")
f.close()
因此,采用不同的文件打开模式,会直接影响 write() 函数向文件中写入数据的效果。
在写入文件完成后,一定要调用 close() 函数关闭打开的文件,否则写入的内容不会保存到文件。例如,将上面程序中最后一行 f.close() 删掉,再次运行此程序并打开a.txt,会发现该文件是空的。因为在写入文件内容时,操作系统不会立刻把数据写入磁盘,而是先缓存起来,只有调用 close() 函数时,操作系统才会保证把没有写入的数据全部写入磁盘文件。
如果向文件写入数据后不想马上关闭文件,也可以调用flush() 函数,实现将缓冲区数据写入文件。
f = open("a.txt", 'w')
f.write("写入一行新数据")
f.flush()
如果设置open()函数的buffering参数关闭缓冲区,数据是不是就可以直接写入文件中了?
对于以二进制格式打开的文件,可以不使用缓冲区,写入的数据会直接进入磁盘文件;但对于以文本格式打开的文件,必须使用缓冲区,否则Python解释器会抛出ValueError错误。
f = open("a.txt", 'w',buffering = 0)
f.write("写入一行新数据")
# 运行结果为:
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\demo.py", line 1, in <module>
f = open("a.txt", 'w',buffering = 0)
ValueError: can't have unbuffered text I/O
writelines()函数
writelines(可迭代对象) 函数,可以写出多个字符或字节串到文件中,也可将一个文件内容复制到另一文件。
注意,写入函数只有 write() 和 writelines() 函数,而没有名为 writeline 的函数。
import os
f=open('test.txt','a+')
# os.linesep代表当前os换行符,注意追加不会自动换行,直接接在原文最后
f.writelines((os.linesep+'何须浅碧轻红色'+os.linesep,'自是花中第一流'+os.linesep))
f.close()
#输出
暗淡轻黄体性柔
情疏迹远只香留
何须浅碧轻红色
自是花中第一流
采用上面方法写入文件时,程序会使用当前os默认的字符集。若需要指定字符集,可以采用二进制形式——程序先将所输出转换成字节串,然后输出字节串。
import os
f=open('test.txt','wb+')
# os.linesep代表当前os换行符
f.writelines((('暗淡轻黄体性柔'+os.linesep).encode('utf-8'),
('情疏迹远只香留').encode('utf-8')))
f.close()
#输出
暗淡轻黄体性柔
情疏迹远只香留
通过使用 writelines() 函数,可以轻松实现将 a.txt 文件中的数据复制到其它文件中:
f = open('a.txt', 'r')
n = open('b.txt','w+')
n.writelines(f.readlines())
n.close()
f.close()
执行此代码,在 a.txt 文件同级目录下会生成一个 b.txt 文件,且该文件中包含的数据和 a.txt 完全一样。注意writelines()函数向文件中写入多行数据时,不会自动给各行添加换行符。上面例子中,b.txt 文件中会逐行显示数据是因为 readlines() 函数在读取各行数据时,读入了行尾的换行符。
六、 tempfile模块:生成临时文件和临时目录
tempfile模块专门用于创建临时文件和临时目录,在UNIX和Windows平台均运行良好
tempfile模块中常用的函数如下
tempfile 模块函数 | 功能描述 |
tempfile.TemporaryFile(mode='w+b', buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None) | 创建临时文件。该函数返回一个类文件对象,也就是支持文件 I/O。 |
tempfile.NamedTemporaryFile(mode='w+b', buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True) | 创建临时文件。该函数的功能与上一个函数的功能大致相同,只是它生成的临时文件在文件系统中有文件名。 |
tempfile.SpooledTemporaryFile(max_size=0, mode='w+b', buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None) | 创建临时文件。与 TemporaryFile 函数相比,当程序向该临时文件输出数据时,会先输出到内存中,直到超过 max_size 才会真正输出到物理磁盘中。 |
tempfile.TemporaryDirectory(suffix=None, prefix=None, dir=None) | 生成临时目录。 |
tempfile.gettempdir() | 获取系统的临时目录。 |
tempfile.gettempdirb() | 与 gettempdir() 相同,只是该函数返回字节串。 |
tempfile.gettempprefix() | 返回用于生成临时文件的前缀名。 |
tempfile.gettempprefixb() | 与 gettempprefix() 相同,只是该函数返回字节串。 |
提示:表中有些函数包含很多参数,但这些参数都具有自己的默认值,因此如果没有特殊要求,可以不对其传参。
tempfile模块还提供了tempfile.mkstemp()和tempfile.mkdtemp()两个低级函数。上面介绍的 4 个用于创建临时文件和临时目录的函数都是高级函数,高级函数支持自动清理(with 语句),而低级函数不支持。
此外,tempfile模块还提供了tempfile.tempdir属性,通过对该属性赋值可以改变系统临时目录。
import tempfile
# 创建临时文件
fp = tempfile.TemporaryFile()
print(fp.name)
fp.write('两情若是久长时,'.encode('utf-8'))
fp.write('又岂在朝朝暮暮。'.encode('utf-8'))
# 将文件指针移到开始处,准备读取文件
fp.seek(0)
print(fp.read().decode('utf-8')) # 输出刚才写入的内容
# 关闭文件,该文件将会被自动删除
fp.close()
# 通过with语句创建临时文件,with会自动关闭临时文件
with tempfile.TemporaryFile() as fp:
# 写入内容
fp.write(b'I Love Python!')
# 将文件指针移到开始处,准备读取文件
fp.seek(0)
# 读取文件内容
print(fp.read()) # b'I Love Python!'
# 通过with语句创建临时目录
with tempfile.TemporaryDirectory() as tmpdirname:
print('创建临时目录', tmpdirname)
#输出如下
C:\Users\admin\AppData\Local\Temp\tmphvehw9z1
两情若是久长时,又岂在朝朝暮暮。
b'I Love Python!'
创建临时目录C:\Users\admin\AppData\Local\Temp\tmp3sjbnwob
上面程序以两种方式来创建临时文件:
- 第一种是手动创建临时文件,读写临时文件后需要主动关闭它,当程序关闭该临时文件时,该文件会被自动删除。
- 第二种是使用 with 语句创建临时文件,with语句会自动关闭临时文件和目录。
上面第一行输出结果是程序生成的临时文件的文件名,最后一行输出结果是程序生成的临时目录的目录名。不要去找临时文件或临时文件夹,因为程序退出时它们都会被删除。