使用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/

相关推荐
爱写代码的小朋友18 分钟前
Python的几个高级特性
python
Eric.Lee202124 分钟前
数据集-目标检测系列- 螃蟹 检测数据集 crab >> DataBall
python·深度学习·算法·目标检测·计算机视觉·数据集·螃蟹检测
一丝晨光30 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
sp_wxf1 小时前
Lambda表达式
开发语言·python
蜡笔小新星1 小时前
Python Kivy库学习路线
开发语言·网络·经验分享·python·学习
篝火悟者1 小时前
问题-python-运行报错-SyntaxError: Non-UTF-8 code starting with ‘\xd5‘ in file 汉字编码问题
开发语言·python
hakesashou2 小时前
python如何比较字符串
linux·开发语言·python
_.Switch2 小时前
Python机器学习模型的部署与维护:版本管理、监控与更新策略
开发语言·人工智能·python·算法·机器学习
Hoper.J3 小时前
PyTorch 模型保存与加载的三种常用方式
人工智能·pytorch·python
弱冠少年3 小时前
websockets库使用(基于Python)
开发语言·python·numpy