pathlib --- 面向对象的文件系统路径 — Python 3.9.17 文档 官网
讲的比较好的文章 python路径操作新标准:pathlib 模块 - 知乎
2|0简介
- 该模块提供表示文件系统路径的类,其语义适用于不同的操作系统
2|1优点(对比os.path)
- 老的路径操作函数管理比较混乱,有的是导入 os, 有的又是在 os.path 当中,而新的用法统一可以用 pathlib 管理。
- 老用法在处理不同操作系统 win,mac 以及 linux 之间很吃力。换了操作系统常常要改代码,还经常需要进行一些额外操作。
- 老用法主要是函数形式,返回的数据类型通常是字符串。但是路径和字符串并不等价,所以在使用 os 操作路径的时候常常还要引入其他类库协助操作。新用法是面向对象,处理起来更灵活方便。
- pathlib 简化了很多操作,用起来更轻松
3|0详解
3|1获取文件路径
-
语法
Path(__file__) #__file__是当前文件的绝对路径
Path(__file__).parent #所在目录
Path(__file__).parent.parent #所在目录的父目录 Path(文件的相对路径) Path(__file__).resolve() #仍然可以得到绝对路径 Path(__file__).parent / 'filename' #目录拼接文件名 用/
-
示例1
from pathlib import Path
print(Path.cwd()) #你当前py文件的所在目录,类似于pwd
print(Path.home()) #你当前用户的家目录(windows比如C:\Users\用户名)
-
示例2
# demo_pathlib.py from pathlib import Path file_abs_path = Path(__file__) print('对象类型是: ',type(file_abs_path)) #pathlib.WindowsPath print('当前文件的绝对路径:',file_abs_path) file_relative_path = Path('demo_pathlib.py') print('当前文件的相对路径:',file_relative_path) print('当前文件的绝对路径(另外一种写法):',file_relative_path.resolve()) parent_dir = Path(__file__).parent #还可以继续.parent,相当于cd ..和cd ../.. print('当前文件的父目录:',parent_dir) print('路径拼接用/(忽略操作系统差异): ',parent_dir / file_relative_path) #
-
注意
- 不管字符串使用的是正斜杠
/
还是反斜杠\
, 在 windows 系统里,得到的路径都是反斜杠\
, pathlib 会根据操作系统智能处理
- 不管字符串使用的是正斜杠
3|2获取路径组成部分
from pathlib import Path
saolei_apk = Path(r'd:\com.kejia.mine.apk')
print(saolei_apk.name)
属性 | 说明 |
---|---|
name | 文件名,包含后缀名,如果是目录则获取目录名 |
stem | 文件名,不包含后缀。 |
suffix | 后缀,比如 .txt , .png 。 |
parent | 父级目录,相当于 cd .. |
anchor | 锚,目录前面的部分 C:\ 或者 / 。 |
-
多级目录
from pathlib import Path saolei_apk = Path(r'D:\software\aDrive\locales\am.pak') for parent in saolei_apk.parents: print(parent)
D:\software\aDrive\locales
D:\software\aDrive
D:\software
D:\
-
如果是os模块,要获取其父目录用的是os.path.dirname(),多级目录的话就麻烦,对比一下
import os os_saolei_apk_dir = os.path.dirname(r'D:\software\aDrive\locales\am.pak') print(os_saolei_apk_dir) os_saolei_apk_parent_dir = os.path.dirname(os.path.dirname(r'D:\software\aDrive\locales\am.pak')) print(os_saolei_apk_parent_dir) from pathlib import Path pathlib_saolei_apk = Path(r'D:\software\aDrive\locales\am.pak') print(pathlib_saolei_apk.parent) print(pathlib_saolei_apk.parent.parent) #就相当于cd..多次 print(pathlib_saolei_apk.parents[1]) #你还可以用parents,注意0是第一层所在目录,1是父目录,2是爷目录....
-
3|3获取文件属性
from pathlib import Path
saolei_apk = Path(r'd:\com.kejia.mine.apk')
print(saolei_apk.stat())
#os.stat_result(st_mode=33206, st_ino=562949953558668, st_dev=3393479561, st_nlink=1, st_uid=0, st_gid=0, st_size=698179, st_atime=1648601046, st_mtime=1631589296, st_ctime=1631952752)
mtime = saolei_apk.stat().st_mtime
print(arrow.get(mtime).format('YYYY-MM-DD HH:MM:SS'))
属性 | 说明 |
---|---|
st_mode | 权限模式 |
st_ino | inode number |
st_dev | device |
st_nlink | 硬链接数 |
st_uid | 所属用户的user id |
st_gid | 所属用户的group id |
st_size | 文件的大小,以位为单位 |
st_atime | 文件访问时间 |
st_mtime | 文件修改时间 |
st_ctime | 文件创建时间 |
- 注意3个时间都是时间戳,要获取好看的时间就用arrow(详见我的另外一篇博文Python第三方库_arrow)来获取。
3|4文件(夹)操作
-
慎用!推荐用shutil
-
示例
from pathlib import Path newfile = Path(r'D:\20220402.txt') if not newfile.exists(): print('创建文件') newfile.touch(exist_ok=False) else: print('删除文件') newfile.unlink()
操作 | 说明 |
---|---|
exists() | 是否存在 |
is_dir() | 是否是文件 |
is_file() | 是否是目录 |
touch(mode=0o666, exist_ok=True) | 创建文件,默认权限666,如果exist_ok为True,文件存在不做任何事情,若为False,文件存在执行touch会报错FileExistsError |
unlink() | 删除文件!危险操作! |
rmdir() | 删除目录非常危险,并且没有提示,一定要谨慎操作。一次只删除一级目录,且当前目录必须为空 |
mkdir() | 创建目录 |
read_text() | 读文件内容,不再需要重复去打开文件和管理文件的关闭了,下同 |
read_bytes() | 读取bytes |
write_text | 写入文本,注意是w 模式,如果之前已经有文件内容,将会被覆盖 |
write_bytes | 写入bytes |
replace(文件新路径) | 移动文件 |
-
重命名
txt_path = Path('archive/demo.txt') new_file = txt_path.with_name('new.txt') txt_path.replace(new_file)
-
改后缀
txt_path = Path('archive/demo.txt') new_file = txt_path.with_suffix('.json') txt_path.replace(new_file)
4|0实例
4|1批量移动
-
现在有这个目录
E:\test (6.5KB) +-- 1 (6.5KB) | +-- 1.txt (0b) | `-- 1.xls (6.5KB) `-- 2 `-- 2.txt (0b)
-
移动所有子目录下的.txt文件到E:\test这个根目录下
from pathlib import Path def move_file_to(srcdir,dstdir,file_pattern): file = Path(srcdir).rglob(file_pattern) for _ in file: Path(_).replace(Path(dstdir) / Path(_).name) move_file_to(srcdir=r'e:\test',dstdir=r'e:\test',file_pattern='*.txt')
4|2批量修改后缀
-
还是上面的目录结构
E:\test (6.5KB) +-- 1 (6.5KB) | +-- 1.txt (0b) | `-- 1.xls (6.5KB) `-- 2 `-- 2.txt (0b)
from pathlib import Path def change_suffix(dst_dir,old_suffix,new_suffix): file = Path(dst_dir).rglob('*.'+old_suffix) if list(file): for _ in file: new_ = Path(_).with_suffix('.'+new_suffix) Path(_).replace(new_) else: raise Exception(f'file-type:{old_suffix} not found(recursive) in {dst_dir}') change_suffix(dst_dir=r'e:\test',old_suffix='txt',new_suffix='docx')
-
修改后
$ tree E:\test (6.5KB) +-- 1 (6.5KB) | +-- 1.docx (0b) | `-- 1.xls (6.5KB) `-- 2 `-- 2.docx (0b)
- rglob是递归,glob只会找一个目录下
- glob得到的是一个生成器,可以用list来转化成列表
4|3统计目录下的文件类型
-
还是刚才的目录结构
E:\test (6.5KB) +-- 1 (6.5KB) | +-- 1.txt (0b) | `-- 1.xls (6.5KB) `-- 2 `-- 2.txt (0b)
-
示例代码
import collections from pathlib import Path path = Path(r'e:\test\1') files = [f.suffix for f in path.iterdir() if f.is_file()] print(dict(collections.Counter(files))) #{'.txt': 1, '.xls': 1}
-
但是上面的代码不能递归,就是当前目录下的文件的类型
-
改造一下
import collections from pathlib import Path path = Path(r'e:\test') files = [f.suffix for f in path.rglob('*.*') if f.is_file()] print(dict(collections.Counter(files)))
4|4统计某个目录下最近修改的文件
-
转载知乎原文的代码
from pathlib import Path path = Path.cwd() print(max( [(f.stat().st_mtime, f) for f in path.iterdir() if f.is_file()] ))
- 也不能递归,你知道怎么改吗?