Python工具:pathlib

文件的路径实际上是一件很困扰的时间(各种平台有时候规则不一样,有时候还需要考虑字符转义的问题),因此我直接推荐使用模块 pathlib,当然,如果您不介意的话,可以使用 os.path 做较为低级的路径操作…

文档:https://docs.python.org/3/library/pathlib.html#module-pathlib

源代码:https://github.com/python/cpython/blob/3.12/Lib/pathlib.py

1.路径对象

pathlib 的文档中有这样一张类继承图片,让我来为您细细介绍该继承图:

Python工具:pathlib_第1张图片

  • Path(具体路径类):是 pathlib 中的具体路径类,它继承自 PurePathPath 类除了能够执行纯路径的抽象操作外,还能够进行与文件系统交互的实际文件和目录操作,比如创建文件、删除文件、读取文件内容等。

    """使用具体路径类"""
    from pathlib import Path
    
    concrete_path = Path("some/path/file.txt")
    concrete_path.touch()  # 在文件系统中创建文件
    print("File Exists:", concrete_path.exists())
    
  • PurePath(纯路径类):主要用于对路径字符串本身进行抽象的操作,比如:连接路径的不同部分、提取路径的各个部分等。是对路径的纯粹抽象,不牵涉到实际的文件或目录

    """使用纯路径类"""
    from pathlib import PurePath
    
    pure_path = PurePath("some/path")
    print("Pure Path Parts:", pure_path.parts)
    

    所以,当你只需要对路径进行抽象的操作时,使用 PurePath 足够了。但如果你需要进行实际的文件系统操作,就应该使用 Path,在本系列文章中,更多使用 Path 类。

  • PosixPathWindowsPath 这两个类分别是 Path 的子类,用于提供特定于操作系统的行为。PosixPath 用于类 Unix 操作系统,而 WindowsPath 用于 Windows 操作系统。通常你可以直接使用 Pathpathlib 会根据你的操作系统选择适当的子类。

  • PurePosixPathPureWindowsPath:类似于 PosixPathWindowsPath,这两个类是 PurePath 的子类,提供了特定于操作系统的路径抽象。

实际上上述类都是将字符串包装起来而已,如果需要取得上述路径对象的包装部分,直接使用 str() 即可获取被包装起来的字符串部分。

2.纯路径对象操作

可以通过 PurePath 对象来拼接目录形成字符串,并且在不同平台下生成对应的字符串,考虑分别在 Windows11Centos7 下运行以下代码:

"""拼接字符路径"""
import os
from pathlib import PurePath
from pathlib import PureWindowsPath
from pathlib import PurePosixPath

print("当前工作目录为:", os.getcwd())
print(PurePath( 'FatherDir', 'SonDir', 'setup.py'))
print(PureWindowsPath( 'FatherDir', 'SonDir', 'setup.py'))
print(PurePosixPath( 'FatherDir', 'SonDir', 'setup.py'))

Windows11 下运行结果为:

# Windows11 下运行结果
> python pathTest.py
当前工作目录为: C:\Users\Limou_p350ml9\Desktop\Test
fatherDir\sonDir\setup.py
fatherDir\sonDir\setup.py
fatherDir/sonDir/setup.py

Centos7 下运行结果为:

# Centos7 下运行结果
$ python3 pathTest.py
当前工作目录为: /home/ljp/LimouGitFile/limou-c-test-code/2024归档/my_code_2024_1_24
FatherDir/SonDir/setup.py
FatherDir\SonDir\setup.py
FatherDir/SonDir/setup.py

因此一般我们都是使用 PurePath 对象让 pathlib 在不同平台下自动转化为适应不同平台的路径字符串,只有在某些特定的场合下才会使用另外两个子类…

注意:后续代码为避免不必要的混乱,我只在 Windows11 下运行关于 PurePath 对象的代码,关于在 Centos7 或者其他平台下的运行结果大同小异,只不过格式不太一样,您可以自己验证一下…

补充:如果使用没有任何参数的 PurePath(),则会返回指向当前目录的 . 符号。

对于这三个类,都可以用以下方法构成一个 PurePath 对象:

  1. 可以传入字符串,并用逗号进行连接
  2. 也可以采用传入 Path 对象来连接(甚至部分 Path 对象,部分字符串,关于 Path 对象的创建我们后面提及)
  3. 或者直接传入我们在代码文件所处文件获取到的路径字符串作为参数,pathlib 会自动做转化处理(关于这些处理您可以前去文档看看,我不再阐述细节)
"""多种形成 PurePath 对象的方法"""
import os
from pathlib import PurePath
from pathlib import Path

# 创建路径字符串
print("当前工作目录为:", os.getcwd())

print(PurePath( 'FatherDir', 'SonDir', 'setup.py' )) # 传入多个字符串进行拼接
print(PurePath( Path('FatherDir'), Path('SonDir'), Path('setup.py') )) # 传入多个 Path 对象进行拼接
print(PurePath( Path('FatherDir'), 'FatherDir', 'SonDir', 'setup.py' )) # 传入部分 Path 对象和字符串对象
print(PurePath( os.getcwd() )) # 传入从本平台获取到的字符串

补充:文档在这里还提及了一个警告,如果 foo 是一个符号链接(例如 Linux 下的软链接),指向另一个目录,那么 PurePosixPath('foo/../bar') 的简化结果不应该简单地变成 PurePosixPath('bar')。这是因为符号链接可能引入了路径的实际结构,而简单地去除 foo/../ 可能会导致错误的路径。

并且在某些平台下,文件路径是不区分大小写的(例如 Windows11,该平台对目录名和文件名的大小写并不敏感),也因此会影响到路径对象比较的结果(这点在排序路径字符串上就会明显体现出来)。

"""路径的比较"""
from pathlib import PurePath
from pathlib import PureWindowsPath
from pathlib import PurePosixPath

print(PurePosixPath('foo') == PurePosixPath('FOO')) # 在一些类 Unix/Linux 平台下对大小写敏感 
print(PureWindowsPath('foo') == PureWindowsPath('FOO')) # 在 Windows 系列平台下,基本对大小写不敏感

print(PurePosixPath('FOO') in { PurePosixPath('foo') })
print(PureWindowsPath('FOO') in { PureWindowsPath('foo') })

print(PurePosixPath('C:') < PurePosixPath('d:'))
print(PureWindowsPath('C:') < PureWindowsPath('d:'))

除了可以对路径对象进行比较,还可以使用 / 操作符,该字符在任何支持的平台下均适用,可以用来连接两个 PurePath 对象,比直接使用 PurePath() 构造一个路径对象更加方便。

不过该运算符有一个使用前提,要求操作符的左操作数本身是一个 PurePath 对象,右操作数既可以是字符串,也可以是 PurePath 对象,最后的运算结果是一个 PurePath 对象。

"""拼接路径对象"""
from pathlib import PurePath

p = PurePath('/home')
q = PurePath('limou')

print(p)
print(p / 'dimou' / 'test.py')
print(p / q)

该运算符在创建多个子路径的时候非常好用。

如果需要访问路径对象中路径字符串的各个部分,则可以使用 PurePath.parts 属性来获取各个部分组成的元组。

# 获取路径的每个部分
import os
from pathlib import PurePath

p = PurePath(os.getcwd())
print(p.parts) # 得到元组 ('C:\\', 'Users', 'Limou_p350ml9', 'Desktop', 'Test')

另外,您最好了解一下不同平台对于路径字符串的不同部分的解释。

Python工具:pathlib_第2张图片

我们可以通过以下属性来获取这些部分(如果对应平台下没有某些部分,则对应属性的值为空字符串):

  • drive:获取驱动
  • root:获取根
  • anchor:获取锚点
  • parents:获取父路径序列,可使用下标访问不同范围的父路径(在 3.10 版本中支持切片和负索引),也可以直接使用 parent 属性获得最小范围的父路径
  • namestemsuffixe:获取文件名、获取主干名、获取最后一个后缀名
  • suffixes:获取后缀名列表(因为有的文件具有多个后缀名)
"""获取路径的各个部分"""
from pathlib import PurePath
from pathlib import PureWindowsPath
from pathlib import PurePosixPath

# 获取驱动器
print("获取驱动器")
print(PureWindowsPath('c:/Program Files/').drive)
print(PureWindowsPath('//host/share/foo.txt').drive) # UNC 共享也被视为驱动器
print(PurePosixPath('/etc').drive)

# 获取根
print("获取根")
print(PureWindowsPath('c:/Program Files/').root)
print(PureWindowsPath('c:Program Files/').root)
print(PureWindowsPath('//host/share').root)
print(PurePosixPath('/etc').root) # UNC 共享始终有一个根

# 获取锚点
print("获取锚点")
print(PureWindowsPath('c:/Program Files/').anchor)
print(PureWindowsPath('c:Program Files/').anchor)
print(PurePosixPath('/etc').anchor)
print(PureWindowsPath('//host/share').anchor)

# 获取父路径
print("获取父路径")
p = PurePosixPath('/a/b/c/d').parent
print(p)
print(p.parents[0])
print(p.parents[1])
print(p.parents[2])

# 获取文件名
p = PurePosixPath('my/library/library.tar.gz')
print(p.name)
print(p.stem)
print(p.suffix)
print(p.suffixes)

上述就是关于纯路径对象的基本操作,下面来重点介绍一些关于纯路径对象的方法:

  1. as_posix():转化为 posix 风格的路径
  2. as_uri():将本地绝对路径转化为标准的 URI 格式,该格式适用于 Web 获取其他网络上引用文件(如果不是绝对路径则会引发错误)
  3. is_absolute():判断路径是否为绝对路径
  4. is_relative_to():检查一个路径是否相对于其他路径
  5. is_reserved():检查文件名是否为保留字
  6. joinpath(*pathsegments):连接路径对象中的路径字符(主要是在代码中动态连接,但是用 / 实际更好一些…)
  7. match(pattern, *, case_sensitive=None):检查通配符匹配是否正确
  8. with_name(name)
  9. with_stem(stem)
  10. with_suffix(suffix)

上述函数没什么好讲解的,直接看我下面的代码运行例子即可:

"""使用 PurePath 各个方法"""
import os
from pathlib import PurePath
from pathlib import PureWindowsPath
from pathlib import PurePosixPath
p1 = PurePath(os.getcwd())
p2 = PurePath('dir1', 'dir2', 'text.txt')
print(p1)
print(p2)


# 转化为 posix 风格的字符串
print(p1.as_posix())
print(p1.as_posix())


# 将本地绝对路径转化为标准的 URI 格式
print(p1.as_uri())
# print(p2.as_uri()) # 抛出错误


# 判断路径是否为绝对路径
if p1.is_absolute():
    print("是绝对路径")
if not p2.is_absolute():
    print("是相对路径")


# 检查一个路径是否相对于其他路径
# (1)创建两个 Path 对象
path1 = PurePath("/path/to/your/")
path2 = PurePath("/path/to/your/file.txt")
# (2)使用 is_relative_to() 方法检查是否相对于另一个路径
result = path2.is_relative_to(path1) # path2 是相对于 path1 确定的
print("Is Path 2 relative to Path 1?", result)

# 检查文件名是否为保留字
print(PureWindowsPath('nul').is_reserved())
print(PurePosixPath('nul').is_reserved())


# 连接路径对象中的路径字符
print(PurePosixPath('/etc').joinpath('passwd'))
print(PurePosixPath('/etc').joinpath(PurePosixPath('passwd')))
print(PurePosixPath('/etc').joinpath('init.d', 'apache2'))
print(PureWindowsPath('c:').joinpath('/Program Files'))


# 检查通配符匹配是否正确
print(PurePath('a/b.py').match('*.py'))
pattern = PurePath('*.py')
print(PurePath('a/b.py').match(str(pattern))) # 我使用的版本较低,貌似在新版本中这里可以直接使用路径对象的参数而不用转化为字符串
print(PurePath('/a/b/c.py').match('a/*.py'))
print(PurePosixPath('b.py').match('*.PY')) # 遵循不同平台下的大小写规则匹配
print(PureWindowsPath('b.py').match('*.PY'))


# 更改路径的部分
p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
print(p.with_name('setup.py'))
p = PureWindowsPath('c:/Downloads/draft.txt')
print(p.with_stem('final'))
p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
print(p.with_suffix('.bz2'))

补充:上述有一个方法 is_reserved() 有提及一个“保留字”的概念,实际上类似编程语言中“不允许用户以关键字作为变量命名”的规则类似,有些平台也保留一些字符,不允许用户以这些字符作为文件名/目录名(下面例子尝试在 windows 中创建文件夹 COM,但是创建失败)。

Python工具:pathlib_第3张图片

注意:貌似有两个方法太新了,在我的模块版本中没法使用,您有兴趣可以前往了解一下 relative_to()with_segments()

3.具体路径对象操作

具体路径(Path)是纯路径类(PurePath)的子类,因此继承了后者提供的所有操作,但是除此之外还提供了对路径对象进行系统调用的方法,因此 Path 对象会比 PurePath 对象更加常用。

当然,有些方法略有区别,不是直接粗暴继承下来,还新增了检查对象是否适应平台的功能。

"""不允许创建和平台不对应的路径对象"""
from pathlib import Path
from pathlib import WindowsPath
from pathlib import PosixPath

print(Path('setup.py'))
# print(PosixPath('setup.py')) # 由于我使用的 Windows 平台,所以这个对象不被允许创建
print(WindowsPath('setup.py'))

因此通常情况下,为了代码的可移植性,建议使用路径对象 Path

下面除了 Path 继承下来的方法(因为除了创建会检查平台以外其他基本差不多),我们介绍一些该子类专有的方法:

  • cwd()home():返回工作目录、家目录

    """寻找工作路径和家路径"""
    from pathlib import Path
    print(Path.cwd())
    print(Path.home())
    
  • stat():查询文件的属性

    """查询文件的属性"""
    from pathlib import Path
    p = Path('pathTest.py')
    print(p.stat().st_size)
    print(p.stat().st_mtime)
    print(p.stat().st_mode)
    # 还可以查询其他属性,具体去看下文档
    
  • exists():查询文件/目录是否真实存在

    """查找文件/目录是否存在"""
    from pathlib import Path
    print(Path('.').exists())
    print(Path('pathTest.py').exists())
    print(Path('/etc').exists())
    print(Path('nonexistentfile').exists())
    
  • chmod():修改文件权限

    """更改文件模式和权限"""
    from pathlib import Path
    p = Path('pathTest.py')
    p.chmod(0o777) # 设置文件为所有用户都有完整权限
    p.stat().st_mode # 注意不同平台对权限的理解可能不太一样,这点需要用户自己把控
    
  • 一些对目录操作的方法:

    (1)mkdir():创建空目录

    (2)rmdir():删除空目录(递归删除目录及其内容可用 shutil.rmtree(),该方法是 shutil 模块的,貌似 pathlib 没有直接提供类似方法)

    """创建目录/删除目录"""
    from pathlib import Path
    # 创建一个路径对象
    path = Path('C:/Users/Limou_p350ml9/Desktop/Test/NewFolder')
    # 创建目录
    path.mkdir()
    # 删除目录
    path.rmdir() # 目录必须为空
    
  • 一些对文件操作的方法:

    (1)touch():创建文件

    (2)unlink():减少文件的链接数(相当于删除文件)

    (3)write_text()/write_bytes():写入文件(前者按文本,后者按字节)

    (4)read_text()/read_bytes():读取文件(前者按文本,后者按字节)

    (5)rename():文件重命名(也可以达到移动文件到其他路径的目的)

    """创建文件/删除文件/写入文件/读取文件/比较文件"""
    from pathlib import Path
    file_path = Path(r"C:/Users/Limou_p350ml9/Desktop/Test/new_file.txt")
    # 创建文件
    file_path.touch()
    # 删除文件
    file_path.unlink()
    
    # 读取/写入文件
    p = Path(r"C:\Users\Limou_p350ml9\Desktop\Test\test_1.txt")
    p.write_text('Text file contents') # 以字节写入可用 write_bytes()
    print(p.read_text()) # 以字节读取可用 read_bytes()
    
    # 重命名文件(该方法还可以用来当作移动文件使用)
    p = Path('foo')
    p.open('w').write('some text')
    
    target = Path('bar')
    p.rename(target)
    
  • iterdir()glob():获取所有指定目录下的子目录和子文件列表,后者则在指定目录下使用通配符查询匹配的文件或目录

    """获取当前目录下的所有子目录和子文件"""
    from pathlib import Path
    p = Path(r"C:\Users\Limou_p350ml9\Desktop\Test")
    for child in p.iterdir(): 
        print(child)
    
    """返回匹配指定模式的文件路径表"""
    from pathlib import Path
    pattern = Path(r"C:\Users\Limou_p350ml9\Desktop\Test")
    matched_files = pattern.glob("*.txt")
    for file_path in matched_files:
        print(file_path)
    # glob() 方法通过匹配指定目录下所有以 .txt 结尾的文件,返回一个生成器
    # 通过迭代生成器,我们可以获取匹配到的每个文件的路径并进行处理
    
  • absolute()resolve():返回对应文件/目录的绝对路径

    from pathlib import Path
    # 返回绝对路径
    p = Path('pathTest.py')
    print(p.absolute())
    
    # 下面的方法可以解析符号链接
    p = Path('.')
    print(p.resolve())
    p = Path('..')
    print(p.resolve())
    
  • is_dir()is_file():检查路径是否指向目录或文件

    """检查路径是否指向目录或路径"""
    from pathlib import Path
    p = Path(r":\Users\Limou_p350ml9\Desktop\Test\pathTest.py")
    print(p.is_dir())
    print(p.is_file())
    
  • samefile():可以判断两个文件或目录的指向是否相同

    """判断是否指向相同的文件"""
    from pathlib import Path
    p = Path('pathTest.py')
    q = Path('test_1.txt')
    print(p.samefile(q))
    

注意:还有一些比较细碎的方法,这里不再展开您可以直接查阅文档…

你可能感兴趣的:(Python(第一版),python,开发语言)