使用pathlib库更优雅的处理路径

前言

如果你需要在 Python 里进行文件处理,那么标准库中的osos.path兄弟俩一定是你无法避开的两个模块。它们提供了非常多与文件路径处理、文件读写、文件状态查看相关的工具函数。

os.path一直是Python中处理路径事实上的标准,但它可能会显得有些繁琐。与之相比,pathlib模块提供了更简单、更直观的方式来完成绝大多数任务。

在Python3.4开始,官方提供了pathlib面向对象的文件系统路径,核心的点在于面向对象, 这也是os.pathpathlib的本质区别。

2019年Django也将os.path 替换成了pathlib

为什么需要pathlib?

pathlib出现之前,Python的标准库osos.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

但是路径并不只是一个字符串,如果需要对文件进行操作,需要结合使用多个标准库的功能,如: 需要移动当前目录下的一些文件到备份目录,需要使用osglobshutil库。

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/

相关推荐
qq_49244844639 分钟前
Jmeter设置负载阶梯式压测场景(详解教程)
开发语言·python·jmeter
lianyinghhh1 小时前
瓦力机器人-舵机控制(基于树莓派5)
人工智能·python·自然语言处理·硬件工程
Mike_Zhang2 小时前
python3.14版本的free-threading功能体验
python
StarPrayers.2 小时前
旅行商问题(TSP)(2)(heuristics.py)(TSP 的两种贪心启发式算法实现)
前端·人工智能·python·算法·pycharm·启发式算法
木头左2 小时前
波动率聚类现象对ETF网格密度配置的启示与应对策略
python
华仔AI智能体3 小时前
Qwen3(通义千问3)、OpenAI GPT-5、DeepSeek 3.2、豆包最新模型(Doubao 4.0)通用模型能力对比
人工智能·python·语言模型·agent·智能体
盼哥PyAI实验室3 小时前
踏上编程征程,与 Python 共舞
开发语言·python
西柚小萌新3 小时前
【深入浅出PyTorch】--6.2.PyTorch进阶训练技巧2
人工智能·pytorch·python
weixin_307779133 小时前
使用Python高效读取ZIP压缩文件中的UTF-8 JSON数据到Pandas和PySpark DataFrame
开发语言·python·算法·自动化·json