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

相关推荐
进击的六角龙31 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
一只爱好编程的程序猿1 小时前
Java后台生成指定路径下创建指定名称的文件
java·python·数据下载
Aniay_ivy1 小时前
深入探索 Java 8 Stream 流:高效操作与应用场景
java·开发语言·python
gonghw4031 小时前
DearPyGui学习
python·gui
向阳12181 小时前
Bert快速入门
人工智能·python·自然语言处理·bert
engchina1 小时前
Neo4j 和 Python 初学者指南:如何使用可选关系匹配优化 Cypher 查询
数据库·python·neo4j
兆。1 小时前
掌握 PyQt5:从零开始的桌面应用开发
开发语言·爬虫·python·qt
南宫理的日知录2 小时前
99、Python并发编程:多线程的问题、临界资源以及同步机制
开发语言·python·学习·编程学习
coberup2 小时前
django Forbidden (403)错误解决方法
python·django·403错误
龙哥说跨境3 小时前
如何利用指纹浏览器爬虫绕过Cloudflare的防护?
服务器·网络·python·网络爬虫