前言
如果你需要在 Python 里进行文件处理,那么标准库中的os
和os.path
兄弟俩一定是你无法避开的两个模块。它们提供了非常多与文件路径处理、文件读写、文件状态查看相关的工具函数。
os.path
一直是Python中处理路径事实上的标准,但它可能会显得有些繁琐。与之相比,pathlib
模块提供了更简单、更直观的方式来完成绝大多数任务。
在Python3.4开始,官方提供了pathlib
面向对象的文件系统路径,核心的点在于面向对象, 这也是os.path
和pathlib
的本质区别。
2019年Django也将os.path 替换成了pathlib。
为什么需要pathlib
?
在pathlib
出现之前,Python的标准库os
及os.path
支持操作文件路径,使用字符串表示文件路径。
python
>>> import os.path
>>> os.path.abspath('test')
'F:\\spug-3.0\\spug-3.0\\spug_api\\test'
或者写出下面这种长长的代码:
python
>>> import os.path
>>> os.path.isfile(os.path.join(os.path.expanduser('~'), 'realpython.txt'))
False
但是路径并不只是一个字符串,如果需要对文件进行操作,需要结合使用多个标准库的功能,如: 需要移动当前目录下的一些文件到备份目录,需要使用os
,glob
和shutil
库。
python
import glob
import os
import shutil
for file_name in glob.glob('*.txt'):
new_path = os.path.join('archive', file_name)
shutil.move(file_name, new_path)
而且,由于不同的操作系统使用的分隔符不同,使用字符串拼接路径就容易出现问题。
有了pathlib
,使得上述的问题变得更加轻松,pathlib创建的Path对象,可以直接通过正斜杠运算符/
连接字符串生成新的对象。
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
import pathlib
from pathlib import WindowsPath
path = pathlib.Path()
if __name__ == '__main__':
print(print) # .
print(path.absolute() / 'test' / 'data.txt') # F:\spug-3.0\spug-3.0\spug_api\test\data.txt
pathlib的基本使用
Path类的常用属性和方法
python
descriptor:
parts: 每一层路径
parent: 父目录
parents: 所有父目录
name: 文件名或目录名
suffix: 文件名后缀
suffixes: 文件名后缀列表
stem: 不带后缀文件名
function:
is_absolute: 是否为绝对路径
joinpath: 组合路径
cwd: 当前工作目录
home: 根目录
exists: 是否存在路径
expanduser: 返回带~和~user的路径
glob: 列出匹配的文件或目录
rglob: 递归列出匹配的文件或目录
is_dir: 是否为目录
is_file: 是否为文件
iterdir: 列出路径下的文件和目录
mkdir: 新建目录
open: 打开文件
rename: 重命名
replace: 覆盖
resolve: 转成绝对路径
rmdir: 删除目录
创建路径
前面用到了pathlib.Path()
获取当前路径的方法,也可以显示的传入路径字符串进行路径创建,支持相对路径和绝对路径字符串的传递。
os.path
python
from os.path import abspath, dirname, join
manage_path = abspath("./manage.py") # 绝对路径
base_dir = dirname(manage_path) # 父目录
another_manage_path = join(base_dir, "another_manage.py") # 构成新路径
print("manage_path:", manage_path)
print("base_dir:", base_dir)
print("another_manage_path:", another_manage_path)
# manage_path: F:\spug-3.0\spug-3.0\spug_api\manage.py
# base_dir: F:\spug-3.0\spug-3.0\spug_api
# another_manage_path: F:\spug-3.0\spug-3.0\spug_api\another_manage.py
pathlib
python
from pathlib import Path
manage_path = Path("manage.py").resolve() # 绝对路径
base_dir = manage_path.parent # 父目录
another_manage_path = base_dir / "another_manage.py" # 构成新路径
print("manage_path:", manage_path)
print("base_dir:", base_dir)
print("another_manage_path:", another_manage_path)
显然用pathlib更加便捷和优雅!!
创建文件
python
from pathlib import Path
path = Path()
new_path = path / "hello.py"
new_path.touch()
创建目录和重命名
os.path
python
import os
import os.path
os.makedirs(os.path.join("./src", "stuff"), exist_ok=True) # 构建目录./src/stuff
os.rename("./src/stuff", "./src/config") # 将./src/stuff重命名为./src/config
pathlib
python
from pathlib import Path
Path("./src/stuff").mkdir(parents=True, exist_ok=True) # 构建目录./src/stuff
Path("./src/stuff").rename("./src/config") # 将./src/stuff重命名为./src/config
mkdir方法:
- parents默认为False,父目录不存在时抛出FileNotFoundError
- exist_ok默认为False,该目录存在时抛出FileExistsError
递归列出某类型文件
假设目录:
python
(spug-3.0) PS F:\spug-3.0\spug-3.0\spug_api\path_lib_test> tree /F
F:.
│ pathlib_test.py
│ __init__.py
│
└─test
__init__.py
列出所有
.py
文件
glob
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from glob import glob
top_level_py_files = glob("./*.py")
all_py_files = glob("./**/*.py", recursive=True) # 递归
print(top_level_py_files)
print(all_py_files)
# ['.\\pathlib_test.py', '.\\__init__.py']
# ['.\\pathlib_test.py', '.\\__init__.py', '.\\test\\__init__.py']
pathlib
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
top_level_py_files = Path(".").glob("*.py")
all_py_files = Path(".").rglob("*.py") # 递归
print(list(top_level_py_files))
print(list(all_py_files))
# [WindowsPath('pathlib_test.py'), WindowsPath('__init__.py')]
# [WindowsPath('pathlib_test.py'), WindowsPath('__init__.py'), WindowsPath('test/__init__.py')]
打开多个文件并读取内容
glob
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from glob import glob
contents = []
for fname in glob("./**/*init*.py", recursive=True):
with open(fname, "r") as f:
contents.append(f.read())
print(contents)
# ["#! -*-conding=: UTF-8 -*-\n# 2023/12/6 17:20\n\n\nif __name__ == '__main__':\n pass\n", "#! -*-conding=: UTF-8 -*-\n# 2023/12/6 17:22\n\n\nif __name__ == '__main__':\n pass\n"]
pathlib
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
contents = []
for fname in Path(".").rglob("*init*.py"):
with open(fname, "r") as f:
contents.append(f.read())
print(contents)
# ["#! -*-conding=: UTF-8 -*-\n# 2023/12/6 17:20\n\n\nif __name__ == '__main__':\n pass\n", "#! -*-conding=: UTF-8 -*-\n# 2023/12/6 17:22\n\n\nif __name__ == '__main__':\n pass\n"]
操作符
使用/
取代os.path.join
进行路径拼接。
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
base_dir = Path(".")
child_dir = base_dir / "test"
file_path = child_dir / "__init__.py"
print(file_path)
# test\__init__.py
路径的每个位置 Path.parts
python
from pathlib import Path
file_path = Path("F:/spug-3.0/spug-3.0/spug_api/pathlib_test.py")
print(file_path.parts)
# ('F:\\', 'spug-3.0', 'spug-3.0', 'spug_api', 'pathlib_test.py')
父目录 Path.parents & Path.parent
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
file_path = Path("path_lib_test/test/__init__.py")
print(file_path.parents) # <WindowsPath.parents>
for parent in file_path.parents:
print(parent)
# path_lib_test\test
# path_lib_test
# .
print(file_path.parent)
# path_lib_test\test
文件名或目录名 Path.name
os.path
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
import os
print(os.path.basename("test/__init__.py")) # __init__.py
print(os.path.basename("test")) # test
pathlib
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
print(Path("test/__init__.py").name) # __init__.py
print(Path("test").name) # test
文件名后缀 Path.suffixes & Path.suffix
os.path
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
import os
print(os.path.splitext("test/__init__.py"))
# ('test/__init__', '.py')
pathlib
python
from pathlib import Path
file_path = Path("test/__init__.py")
print(file_path.suffixes) # ['.py']
print(file_path.suffix) # .py
不带后缀文件名 Path.stem
os.path(只有一个的情况下有坑)
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
import os
print(os.path.splitext(os.path.basename("test/__init__.py"))[0]) # __init__
pathlib
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
print(Path("test/__init__.py").stem) # __init__
是否为绝对路径 Path.is_absolute()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
file_path = Path("test/__init__.py")
print(file_path.is_absolute()) # False
组合路径 Path.joinpath(*other)
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
file_path = Path(".").joinpath("test", "__init__.py")
print(file_path) # test\__init__.py
当前工作目录 Path.cwd()
和 os.getcwd() 返回的相同
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
file_path = Path()
print(file_path.cwd()) # F:\spug-3.0\spug-3.0\spug_api\path_lib_test
根目录 Path.home()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
file_path = Path("test/__init__.py")
print(file_path.home()) # C:\Users\lianhf
是否存在路径 Path.exists()
os.path
python
import os.path
print(os.path.exists("test/aaaa.py"))
# False
pathlib
python
from pathlib import Path
file_path = Path("test/aaaa.py")
print(file_path.exists())
# False
返回带和user的路径 Path.expanduser()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
file_path = Path("~/test/aaaa.py")
print(file_path.expanduser()) # C:\Users\lianhf\test\aaaa.py
是否为目录或文件 Path.is_dir() & Path.is_file()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
dir_path = Path("test/")
print(dir_path.is_dir()) # True
print(dir_path.is_file()) # False
file_path = Path("test/__init__.py")
print(file_path.is_dir()) # False
print(file_path.is_file()) # True
列出路径下的文件和目录 Path.iterdir()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
base_path = Path(".")
contents = [content for content in base_path.iterdir()]
print(contents) # [WindowsPath('pathlib_test.py'), WindowsPath('test'), WindowsPath('__init__.py')]
打开文件 Path.open()
同内置函数open()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
with Path("test/__init__.py") as f:
contents = open(f, "r")
for line in contents:
print(line)
覆盖 Path.replace()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
path = Path("test/__init__.py")
path.replace(path.parent / "__init2__.py")
转成绝对路径 Path.resolve()
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
path = Path("test/__init__.py")
print(path.resolve()) # F:\spug-3.0\spug-3.0\spug_api\path_lib_test\test\__init__.py
strict设为True,如果路径不存在,则抛出FileNotFoundError
删除目录 Path.rmdir()
os.path
python
import os
os.rmdir("test/hello")
如果目录下不为空,抛出OSError
pathlib
python
from pathlib import Path
file_path = Path("test/hello")
file_path.rmdir()
如果目录下不为空,抛出OSError
删除文件 os.remove()
删除文件还是要用os模块
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
import os
os.remove("test/hello.txt")
移动文件 shutil.move()/path.rename()
shutil
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
import shutil
shutil.move('./__init__.py', './test')
pathlib
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
path = Path('test/__init2__.py')
folder = Path('.')
path.rename(folder / path.name)
转Unix风格
as_posix():返回使用正斜杠(/)的路径字符串
python
from pathlib import Path
path = Path('/host/share')
print(str(path)) # \host\share
print(path.as_posix()) # /host/share
常用脚本
统计文件个数
我们可以使用.iterdir
方法获取当前文件下的所以文件。
python
import pathlib
from collections import Counter
now_path = pathlib.Path.cwd()
gen = (i.suffix for i in now_path.iterdir())
print(Counter(gen)) # Counter({'.py': 16, '': 11, '.txt': 1, '.png': 1, '.csv': 1})
移动子文件夹下的所有文件到本文件夹
python
from pathlib import Path
current_file = Path(__file__).absolute()
for path in Path('.').rglob('*'):
if path == current_file or path.parent == Path('.'):
continue
if path.is_file():
try:
new_path = Path('.') / path.name
path.rename(new_path)
except Exception as e:
print(f"Error moving {path}: {e}")
展示目录树
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
import pathlib
def print_tree(directory, depth=0):
spacer = ' ' * depth
print(f'{spacer}+ {directory.name}/')
for path in sorted(directory.rglob('*')):
if path.is_file():
print(f'{spacer} {path.name}')
else:
print_tree(path, depth + 1)
if __name__ == '__main__':
current_path = pathlib.Path.cwd()
print_tree(current_path)
批量重命名
python
#! -*-conding=: UTF-8 -*-
# 2023/12/6 11:41
from pathlib import Path
def batch_rename(directory_path):
directory = Path(directory_path)
for file_path in directory.glob('*'):
if file_path.is_file():
new_name = file_path.stem.split(' RAW')[0] + '.mp4'
new_path = file_path.with_name(new_name)
file_path.rename(new_path)
if __name__ == '__main__':
target_directory = './test' # 需要批量重命名的路径
batch_rename(target_directory)
获取文件大小
python
import os
from pathlib import Path
print(os.path.getsize('test/1.mp4'))
print(Path('test/1.mp4').stat().st_size)
文件存在且不为空
python
from pathlib import Path
def path_exists(path):
"""文件存在且不为空"""
if path and Path(path).exists() and Path(path).stat().st_size > 0:
return True
return False
print(path_exists('')) # False
print(path_exists('abc.txt')) # False
print(path_exists(__file__)) # True
小结
从 Python 3.4 开始,pathlib
已在标准库中提供。 使用pathlib,文件路径可以由适当的Path
对象表示,而不是像以前一样用纯字符串表示。 这些对象使代码处理文件路径:
- 更容易阅读,特别是可以使用 "/" 将路径连接在一起
- 更强大,直接在对象上提供最必要的方法和属性
- 在操作系统中更加一致,因为 Path 对象隐藏了不同系统的特性
功能 | os 和 os.path | pathlib |
---|---|---|
绝对路径 | os.path.abspath() | Path.resolve() |
改变文件的模式和权限 | os.chmod() | Path.chmod() |
新建目录 | os.mkdir() | Path.mkdir() |
新建目录 | os.makedirs() | Path.mkdir() |
重命名 | os.rename() | Path.rename() |
覆盖 | os.replace() | Path.replace() |
删除目录 | os.rmdir() | Path.rmdir() |
移除此文件或符号链接 | os.remove(), os.unlink() | Path.unlink() |
当前工作目录 | os.getcwd() | Path.cwd() |
是否存在路径 | os.path.exists() | Path.exists() |
根目录 | os.path.expanduser() | Path.expanduser(), Path.home() |
列出路径下的文件和目录 | os.listdir() | Path.iterdir() |
是否为目录 | os.path.isdir() | Path.is_dir() |
是否为文件 | os.path.isfile() | Path.is_file() |
是否指向符号链接 | os.path.islink() | Path.is_symlink() |
创建硬链接 | os.link() | Path.link_to() |
将此路径创建为符号链接 | os.symlink() | Path.symlink_to() |
返回符号链接所指向的路径 | os.readlink() | Path.readlink() |
返回 os.stat_result 对象包含此路径信息 | os.stat() | Path.stat(), Path.owner(), Path.group() |
是否为绝对路径 | os.path.isabs() | PurePath.is_absolute() |
连接路径 | os.path.join() | PurePath.joinpath() |
文件名或目录名 | os.path.basename() | PurePath.name |
父目录 | os.path.dirname() | PurePath.parent |
是否相同文件 | os.path.samefile() | Path.samefile() |
文件名后缀 | os.path.splitext() | PurePath.suffix |
移动文件 | shutil.move() | Path.rename() |
更多内容和细节请参考官方文档~
参考文献
docs.python.org/zh-cn/3.11/...
stackoverflow.com/questions/2...
docs.python.org/zh-cn/3/lib... docs.python.org/zh-cn/3/lib...
docs.python.org/zh-cn/3/lib...
peps.python.org/pep-0428/