Python 文件路径处理问题
由于许多不同的原因,使用文件和与文件系统交互很重要。 最简单的情况可能只涉及读取或写入文件,但有时候会有更复杂的任务。 也许你需要列出给定类型的目录中的所有文件,查找给定文件的父目录,或者创建一个尚不存在的唯一文件名。
一般情况,Python 使用常规文本字符串表示文件路径。 一般在使用 os,glob 和 shutil 等库的时候会使用到路径拼接的操作,使用os模块拼接起来显得略显复杂,以下示例仅需要三个 import 语句来将所有文本文件移动到归档目录:
1 import os 2 import glob 3 import shutil 4 5 for file_name in glob.glob("*.txt"): 6 new_path = os.path.join('archive', file_name) 7 shutil.move(file_name, new_path)
使用常规的字符串去拼接路径是可以的,但是由于不同的操作系统使用的分隔符不同,这样就容易出现问题,所以一般我们使用最多的还是使用 os.path.join()。
Python 3.4 中引入了 pathlib 模块(PEP 428)再一次的优化了路径的拼接。使用 pathlib 库的 Path 方法,可以将一个普通的字符串转换为 pathlib.Path 对象类型的路径
创建路径
1 from pathlib import Path
.cwd(当前工作目录)和 .home(用户的主目录):
1 from pathlib import Path 2 3 now_path = Path.cwd() 4 home_path = Path.home() 5 6 print("当前目录", now_path, type(now_path)) 7 print("home目录", home_path, type(home_path))
输出:
1 当前目录 C:\Users\huangm\Desktop <class 'pathlib.WindowsPath'> 2 home目录 C:\Users\huangm <class 'pathlib.WindowsPath'>
可以通过把字符串类型的路径,转换为 Pathlib.Path 类型的路径
1 import pathlib 2 DIR_PATH = pathlib.Path('/Users/huangm/Untitled.ipynb') 3 print(DIR_PATH, type(DIR_PATH))
输出:
1 \Users\huangm\Untitled.ipynb <class 'pathlib.WindowsPath'>
读文件和写文件
open 来操作文件读写操作的时候,不仅可以使用字符串格式的路径,对于 pathlib 生成的路径完全可以直接使用:
1 import pathlib 2 3 path = pathlib.Path.cwd() / '123.txt' 4 with open(path, mode='r') as fid: 5 headers = [line.strip() for line in fid if line.startswith('#')] 6 7 print('\n'.join(headers))
或者在 pathlib 的基础使用 open,我们推荐使用下面的方式
1 import pathlib 2 3 DIR_PATH = pathlib.Path("/Users/huangm") / "Desktop" / "123.txt" 4 with DIR_PATH.open('r') as fs: 5 data = fs.read() 6 print(data)
实际上这里的 open 方法,底层也是调用了 os.open 的方法
可以不用再使用 with open 的形式即可以进行读写。
1 import pathlib 2 3 DIR_PATH = pathlib.Path("/Users/huangm") / "Desktop" / "123.txt" 4 print(DIR_PATH.read_text()) #找到对应的路径然后打开文件,读成str格式。等同open操作文件的"r"格式。 5 print(DIR_PATH.read_bytes()) #读取字节流的方式。等同open操作文件的"rb"格式 6 print(DIR_PATH.write_text("妹子")) #文件的写的操作,等同open操作文件的"w"格式。 7 print(DIR_PATH.write_bytes(b'#\xb4\xb2\xb5\xe6 \xd0\xe8\xd2\xaa 1\r\n\r\n\xc1\xb9\xcf\xaf ')) #文件的写的操作,等同open操作文件的"wb"格式。
使用 resolve 可以通过传入文件名,来返回文件的完整路径,使用方式如下
1 import pathlib 2 text_path = pathlib.Path('123.txt') 3 print(text_path.resolve())
输出:
1 C:\Users\huangm\Desktop\123.txt
需要注意的是 “123.txt” 文件要和我当前的程序文件在同一级目录。
选择路径的不同组成部分
pathlib 还提供了很多路径操作的属性,这些属性可以选择路径的不用部位,如
.name: 可以获取文件的名字,包含拓展名。
.parent: 返回上级文件夹的名字
.stem: 获取文件名不包含拓展名
.suffix: 获取文件的拓展名
.anchor: 类似盘符的一个东西,
1 import pathlib 2 3 now_path = pathlib.Path.cwd() / '123.txt' 4 print("name", now_path.name) 5 print("stem", now_path.stem) 6 print("suffix", now_path.suffix) 7 print("parent", now_path.parent) 8 print("anchor", now_path.anchor)
输出:
1 name 123.txt 2 stem 123 3 suffix .txt 4 parent C:\Users\huangm\Desktop 5 anchor C:\
移动和删除文件
当然 pathlib 还可以支持文件其他操作,像移动,更新,甚至删除文件,但是使用这些方法的时候要小心因为,使用过程不用有任何的错误提示即使文件不存在也不会出现等待的情况。
使用 replace 方法可以移动文件,如果文件存在则会覆盖。为避免文件可能被覆盖,最简单的方法是在替换之前测试目标是否存在。
1 import pathlib 2 3 destination = pathlib.Path.cwd() / "123.txt" 4 source = pathlib.Path.cwd() / "123.txt" 5 if not destination.exists(): 6 source.replace(destination)
但是上面的方法存在问题就是,在多个进程多 destination 进行的操作的时候就会现问题,可以使用下面的方法避免这个问题。也就是说上面的方法适合单个文件的操作。
1 import pathlib 2 3 destination = pathlib.Path.cwd() / "123.txt" 4 source = pathlib.Path.cwd() / "123.txt" 5 with destination.open(mode='xb') as fid: 6 #xb表示文件不存在才操作 7 fid.write(source.read_bytes())
当 destination文件存在的时候上面的代码就会出现 FileExistsError 异常。
从技术上讲,这会复制一个文件。 要执行移动,只需在复制完成后删除源即可。
使用 with_name 和 with.shuffix 可以修改文件名字或者后缀。
1 import pathlib 2 source = pathlib.Path.cwd() / "demo.py" 3 source.replace(source.with_suffix(".txt")) #修改后缀并移动文件,即重命名
可以使用 .rmdir() 和 .unlink() 来删除文件。
1 import pathlib 2 3 destination = pathlib.Path.cwd() / "target" 4 source = pathlib.Path.cwd() / "demo.txt" 5 source.unlink()
几个 pathlib 的使用例子
统计文件个数
我们可以使用.iterdir方法获取当前文件下的所以文件.
1 import pathlib 2 from collections import Counter 3 now_path = pathlib.Path.cwd() 4 gen = (i.suffix for i in now_path.iterdir()) 5 print(now_path) 6 print(Counter(gen))
pathlib 也有 glob 方法和 rglob 方法,不同的是 glob 模块里的 glob 方法结果是列表形式的,iglob 是生成器类型,在这里 pathlib 的 glob 模块返回的是生成器类型,然后 pathlib 还有一个支持递归操作的 rglob 方法。
下面的这个操作我通过使用 glob 方法,设定规则进行文件的匹配。
1 import pathlib 2 3 from collections import Counter 4 gen = (p.suffix for p in pathlib.Path.cwd().glob('*.py')) 5 print(gen) 6 print(Counter(gen))
输出:
1at 0x000001B3D528F8B8> 2 Counter({'.py': 6})
展示目录树
下一个示例定义了一个函数 tree(),该函数的作用是打印一个表示文件层次结构的可视树,该树以一个给定目录为根。因为想列出其子目录,所以我们要使用 .rglob() 方法:
1 import pathlib 2 3 4 from collections import Counter 5 def tree(directory): 6 print(f'+ {directory}') 7 for path in sorted(directory.rglob('*')): 8 # print(path.relative_to(directory)) 9 # print(path.relative_to(directory).parts) 10 depth = len(path.relative_to(directory).parts) 11 spacer = ' ' * depth 12 print(f'{spacer}+{path.name}') 13 14 tree(pathlib.Path.cwd())
其中 relative_to 的方法的作用是返回 path 相对于 directory 的路径。
parts 方法可以返回路径的各部分。例如
1 import pathlib 2 now_path = pathlib.Path.cwd() 3 if __name__ == '__main__': 4 print(now_path.parts)
输出:
1 ('C:\\', 'Users', 'huangm', 'Desktop')
获取文件最后一次修改时间
iterdir(),.glob()和.rglob()方法非常适合于生成器表达式和列表理解。
使用stat()方法可以获取文件的一些基本信息,使用.stat().st_mtime可以获取文件最后一次修改的信息
1 import pathlib 2 now_path = pathlib.Path.cwd() 3 from datetime import datetime 4 time, file_path = max((f.stat().st_mtime, f) for f in now_path.iterdir()) 5 print(datetime.fromtimestamp(time), file_path)
甚至可以使用类似的表达式获取上次修改的文件内容
1 import pathlib 2 from datetime import datetime 3 now_path =pathlib.Path.cwd() 4 result = max((f.stat().st_mtime, f) for f in now_path.iterdir())[1] 5 print(result.read_text())
其他内容
关于 pathlib.Path 格式路径转换为字符串类型
因为通过 pathlib 模块操作生成的路径,不能直接应用字符串的一些操作,所以需要转换成字符串,虽然可以使用 str() 函数进行转换,但是安全性不高,建议使用 os.fspath() 方法,因为如果路径格式非法的,可以抛出一个异常。str()就不能做到这一点。
拼接符号”/”背后的秘密
/ 运算符由 __truediv__ 方法定义。 实际上,如果你看一下 pathlib 的源代码,你会看到类似的东西。
1 class PurePath(object): 2 3 def __truediv__(self, key): 4 return self._make_child((key,))
参考文章:
https://realpython.com/python-pathlib/