python文件路径操作及pathlib库

对文件的路径操作是一个非常基础的问题,但也是一个至关重要的问题,优雅的路径操作不仅可以让代码可读性更高;还可以让用户避免很多不必要的麻烦。python中路径操作常用的几种方式重要包括:字符串拼接、os.path、以及python 3.4中新增的面向对象的路径操作库 pathlib。

字符串拼接

字符串拼接是最原始、最不优雅的路径操作方式,也是很多初学者偏爱的方式,但是强烈推荐使用以下两种更优雅的路径操作方式。因为不推荐字符串拼接,所以下面简要列举一下常见的拼接方式(更多字符串操作方法请见Python字符串处理)。

In [2]: directory = '/home/jeffery0207'
In [3]: filename = 'a.txt'

In [4]: directory + '/' + filename
Out[4]: '/home/jeffery0207/a.txt'

In [5]: '/'.join([directory, filename])
Out[5]: '/home/jeffery0207/a.txt'

In [6]: f'{directory}/{filename}'  # python3.6之后新增
Out[6]: '/home/jeffery0207/a.txt'

In [7]: '{0}/{1}'.format(directory, filename)
Out[7]: '/home/jeffery0207/a.txt'

In [8]: '%s/%s' % (directory, filename)
Out[8]: '/home/jeffery0207/a.txt'

os.path

os.path模块是个人比较常用的一个模块,因为Unix系统和windows系统上的路径表征有一定的差别,加载该模块时python会自动根据所使用的的系统加载不同版本的os.path (posixpath对应于Unix-style的路径处理;ntpath对应于Windows-style的路径处理)。该模块实现了一些实用的路径处理函数,主要包括:

__all__ = ['expanduser', 'expandvars',
           'abspath', 'realpath', 'relpath',
           'split', 'splitdrive', 'splitext', 'basename', 'dirname', 'join',
           'getatime', 'getctime', 'getmtime', 'getsize',
           'isabs', 'isdir', 'isfile', 'islink', 'ismount', 'exists', 'lexists',
           'samefile', 'sameopenfile', 'samestat',
           'normcase', 'normpath',
           'commonpath', 'commonprefix']
  • expanduser()expandvars()函数

    python默认不会识别shell变量及家目录符~,可以通过这两个函数实现扩展

    In [1]: expandvars('$HOME/workspace')
    Out[1]: '/home/liunianping/workspace'
    
    In [2]: expanduser('~/workspace')
    Out[2]: '/home/liunianping/workspace'
    
  • split(), splitdrive(), splitext(), basename(), dirname()join()

    该模块中最好用的一组函数,常用于路径拼接

    In [3]: split(path)
    Out[3]: ('/Users/jeffery0207', 'a.b.c.txt')
    
    In [4]: splitext(path)
    Out[4]: ('/Users/jeffery0207/a.b.c', '.txt')
    
    In [5]: splitdrive(path)
    Out[5]: ('', '/Users/jeffery0207/a.b.c.txt')
      
    In [6]: basename(path)
    Out[6]: 'a.b.c.txt'
    
    In [7]: dirname(path)
    Out[7]: '/Users/jeffery0207'
      
    In [8]: join(dirname(path), 'text.txt')
    Out[8]: '/Users/jeffery0207/text.txt'
    
  • isabs(), isdir(), isfile(), islink(), ismount(), exists(), lexists()

    该模块中用于路径处理判断的一组函数

    In [9]: isabs(expanduser('~/.zshrc')),isabs('./.zshrc')  # 判断给定路径是否是绝对路径
    Out[9]: (True, False)
      
    In [11]: isdir('./.local') # 判断给定路径是否是文件夹类型
    Out[11]: True
      
    In [12]: isfile('./.local')  # 判断给定路径是否是普通文件
    Out[12]: False
      
    In [13]: islink('./miniconda3/bin/python')  # 判断给定路径是否是符号链接文件
    Out[13]: True
    
    In [14]: ismount('/media/disk01')  # 判断给定路径是否是挂载点
    Out[14]: True
    
    In [15]: exists('./miniconda3/bin/python'), lexists('./miniconda3/bin/python')  # 当判断一个链接文件是否存在时,如果所链接那个文件不存在时,前者返回False,后者返回True
    Out[15]: (True, True)
    
  • getatime(), getctime(), getmtime()getsize()

    依次指:返回上次访问该path的时间;返回该path的系统ctime,在unix系统上对应于该path上次元数据更改的时间,在windows上对应文件的创建时间;返回该path上一次修改的时间;返回该path的文件大小

    In [16]: path = './.zshrc'
    In [17]: getatime(path), getctime(path), getmtime(path), getsize(path)
    Out[17]: (1634019401.9940903, 1633942149.2245831, 1633942149.2205787, 4506)
    
  • normcase(), normpath()

    In [18]: normcase(path)  # 在windows系统上将所有字符小写,并将所有正斜杠转换为反斜杠;其他系统返回不变的path
    Out[18]: './.zshrc'
    
    In [19]: join('./a/', './b.txt')
    Out[19]: './a/./b.txt'
    
    In [20]: normpath(join('./a/', './b.txt'))  # 折叠多余的分隔符,在这种情况下非常有用
    Out[20]: 'a/b.txt'
    
    
  • commonpath(), commonprefix()

    In [27]: commonpath(['./a/b/c.1.txt', './a/b/c.2.txt'])
    Out[27]: 'a/b'
    
    In [28]: commonprefix(['./a/b/c.1.txt', './a/b/c.2.txt'])
    Out[28]: './a/b/c.'
    
  • samefile(path1, path2)sameopenfile(fp1, fp2) , samestat(stat1, stat2)

    我们可以创建如下两个文件作为演示:

    >>> mkdir test && cd test
    >>> touch a.txt
    >>> ln -s a.txt a.link.txt
    >>> ll
    # lrwxr-xr-x  1 jeffery0207  staff     5B Oct 12 21:47 a.link.txt -> a.txt
    # -rw-r--r--  1 jeffery0207  staff     0B Oct 12 21:47 a.txt
    

    首先我们需要了解一下另外三个函数,os.fstat(fd), os.lstat(path, *, dir_fd=None), os.stat(path, *, dir_fd=None, follow_symlinks=True)

    • os.stat(path, *, dir_fd=None, follow_symlinks=True)

      该函数返回一个stat_result对象(stat_result详细属性请见os.stat_result),表征一个文件(file)或则文件描述符(file descriptor)的状态,等效于系统调用stat

      $ stat a.txt  # mac系统的显示
      16777223 12934280785 -rw-r--r-- 1 jeffery staff 0 0 "Oct 12 21:47:10 2021" "Oct 12 21:47:10 2021" "Oct 12 21:47:10 2021" "Oct 12 21:47:10 2021" 4096 0 0 a.txt
      
      $ stat a.txt  # linux 系统的显示
        File: ‘a.txt’
        Size: 4         	Blocks: 8          IO Block: 4096   regular file
      Device: fd0ah/64778d	Inode: 69309229738  Links: 1
      Access: (0644/-rw-r--r--)  Uid: ( 1042/jeffery)   Gid: (  110/  staff)
      Access: 2021-12-02 22:01:41.000000000 +0800 # 最后访问时间
      Modify: 2021-12-02 22:01:40.000000000 +0800  # 最后修改时间
      Change: 2021-12-03 10:10:28.992397846 +0800  # 最后状态改变时间
      Birth: # 创建时间
      

      该函数默认会跟踪符号链接文件,要获得符号链接文件本身的状态,需要设置参数follow_symlinks=False或则使用os.lstat

    • os.lstat(path, *, dir_fd=None)

      该函数返回一个stat_result对象,表征一个文件(file)的状态。从python3.3起,os.lstat(path, *, dir_fd=None)函数等同于os.stat(path, dir_fd=dir_fd, follow_symlinks=False函数。

    • os.fstat(fd)

      该函数返回一个stat_result对象,表征一个文件描述符(file descriptor)的状态。从 Python 3.3 开始,这等效于 os.stat(fd)

    samestat(stat1, stat2)检测两个stat_result对象是否指向同一个文件:

    In [30]: a_link_stat = stat('./test/a.link.txt')
    In [31]: a_stat = stat('./test/a.txt')
    In [32]: a_link_lstat = lstat('./test/a.link.txt')
    In [33]: samestat(a_link_stat, a_stat), samestat(a_link_lstat, a_stat)
    Out[33]: (True, False)
    

    samefile(path1, path2)函数即底层调用os.stat(path, *, dir_fd=None, follow_symlinks=True)检测两个文件是否指向同一个文件:

    In [34]: samefile('./test/a.link.txt', './test/a.txt')
    Out[34]: True
    

    sameopenfile(fp1, fp2)则检测两个文件描述符是否是指同一个文件,是则返回True,反之返回False。其先通过依次调用fstat()函数和samestat()函数判断个文件描述符是否是指同一个文件。

    In [34]: import os
    In [35]: fd_a = os.open('./test/a.txt', os.O_RDWR)
    In [36]: fd_a_link = os.open('./test/a.link.txt', os.O_RDWR)
    In [37]: sameopenfile(fd_a, fd_a_link)
    Out[37]: True
    
  • abspath(path), relpath(path, start=os.curdir)), realpath(path, *, strict=False)

    分别对应绝对路径 (等效于normpath(join(os.getcwd(), path)))、相对路径(返回path相对于start文件夹的路径)、真实路径(该函数用于解析链接文件的真实路径,当strict=False时,如果path不存在或遇到符号链接循环则会抛出OSError错误)。

    In [39]: abspath(path)
    Out[39]: '/Users/jeffery/.zshrc'
      
    In [40]: relpath(abspath(path), start='./')  # start 参数默认值为os.curdir,即当前文件夹
    Out[40]: '.zshrc'
    
    In [41]: realpath('./miniconda3/bin/python')  # 一般,我们的python其实是一个链接文件,对应一个具体的python版本文件
    Out[41]: '/Users/jeffery/miniconda3/bin/python3.9'
    

pathlib

​ pathlib是python3.4中新增的面向对象的文件系统路径操作模块,为不同文件系统提供较为统一的文件系统路径操作方法。该模块主要定义了以下类(class),其中pure path包括PurePath, PurePosixPath, PureWindowsPath; concrete paths 包括PathPosixPath以及WindowsPath,其继承关系如下图所示,PurePath 为其他所有类的父类;Path类继承于PurePath,同时作为PosixPathWindowsPath的子类。

 from python documentation

​ 因此,pure paths和concrete paths主要区别在于Path类的方法,pure paths类主要封装了对路径本身的操作,而concrete paths还包括对文件系统地操作,如新建、删除等。这种继承关系可以很好的分离路径操作和文件系统的操作,更多请参考之前的博客python面向对象编程

PurePath方法和属性-路径操作

driverootanchorparents
namesuffixsuffixesstem
as_uri()is_absolute()is_relative_to(*other)is_reserved()
match(pattern)relative_to(*other)¶with_name(name)with_stem(stem)
In [1]: from pathlib import PurePath, PurePosixPath, PureWindowsPath
In [2]: p = PurePath('/Users/jeffery/.zshrc')
In [3]: p1 = PurePath('/Users/jeffery/a.b.c.txt')
In [4]: p2 = PureWindowsPath('c:/foo/bar/setup.py')
  • drive
    In [5]: p.drive, p2.drive  # Unix-style的路径没有盘的概念
    Out[5]: ('', 'c:')
    
  • root
    In [6]: p.root, p2.root
    Out[6]: ('/', '\\')
    
  • anchor
    In [7]: p1.anchor, p2.anchor  # anchor 就是指 drive和root两者
    Out[7]: ('/', 'c:\\')
    
  • parents

    该属性是路径的所有父目录,返回一个Iterable对象

    In [8]: pars = p.parents
    
    In [9]: from collections import Iterable
    In [11]: isinstance(pars, Iterable)
    Out[11]: True
    
    In [12]: for par in pars:
        ...:     print(par)
        ...: 
    /Users/jeffery
    /Users
    /
    
  • parent

    该属性指路径的父目录,p.parent则是/Users/jeffery

  • name

    该属性指路径最后一层级的名称,但不包括drive和root,可方便的用于取文件名或文件夹名

    In [13]: p1.name
    Out[13]: 'a.b.c.txt'
    In [14]: PurePath('/').name
    Out[14]: ''
    
  • suffix
    In [15]: p.suffix  # 取文件扩展名
    Out[15]: ''
    In [16]: p1.suffix
    Out[16]: '.txt'
    
  • suffixes
    In [17]: p1.suffixes  # 取多层级的文件扩展名,其本质就是根据.进行字符串分割
    Out[17]: ['.b', '.c', '.txt']
    
  • stem

    相当于先取path.name,然后再去除文件扩展名(path.suffix

    In [18]: p1.stem
    Out[18]: 'a.b.c'
    
  • as_posix()

    将路径转换成斜杆/的形式

    In [19]:  p = PureWindowsPath('c:\\windows')
    In [20]: str(p)
    Out[20]: 'c:\\windows'
    In [21]:  p.as_posix()
    Out[21]: 'c:/windows'
    
  • as_uri()

    将路径转换成文件url的形式,参数path必须是绝对路径,否则抛出ValueError

    In [22]: p1.as_uri()
    Out[22]: 'file:///Users/jeffery/a.b.c.txt'
    
  • is_absolute()

    判断一个路径是否是绝对路径的形式

  • is_relative_to(*other)

    该方法判断路径是否和另一个路径是相对关系,换句话说就是other是否是路径的父目录或则相同目录(当路径本身是目录时)

    In [26]: p1.is_relative_to('/Users/jeffery')
    Out[26]: True
    In [27]: p1.is_relative_to('/Users')
    Out[27]: True
    In [28]: p1.is_relative_to('/Users/jeffery/c.txt')
    Out[28]: False
    
  • is_reserved()

    判断一个目录是否是操作系统中一些特殊的文件设备名,如nul, com1, com2等,该命令只针对windows-like path, Posix-like path总是返回False.

    In [35]: PureWindowsPath('nul').is_reserved()
    Out[35]: True
    
  • joinpath(*other)

    路径名连接,等效于操作符/

    In [32]: PurePosixPath('/etc').joinpath('init.d', 'apache2')
    Out[32]: PurePosixPath('/etc/init.d/apache2')
    
    In [33]: PurePosixPath('/etc') / 'init.d' / 'apache2'
    Out[33]: PurePosixPath('/etc/init.d/apache2')
    
  • match(pattern)

    路径名称匹配

    In [34]: PurePath('/a/b/c.py').match('b/*.py')
    Out[34]: True
    
    In [35]: PurePath('/a/b/c.py').match('a/*.py')  
    Out[35]: False
      
    In [36]: PurePath('/a/b/c.py').match('/a/*.py') # 特别注意这一点,和shell的匹配模式不一样
    Out[36]: False
    
  • relative_to(*other)¶

    other路径为起点,计算路径的相对路径,如果无法计算则抛出ValueError

    In [39]: p.relative_to('/')
    Out[39]: PurePosixPath('etc/passwd')
    
    In [40]: p.relative_to('/etc/passwd')
    Out[40]: PurePosixPath('.')
    

    以上代码等效于os.path.relpath

    In [42]: from os.path import relpath
    In [43]:  relpath('/etc/passwd', start='/')
    Out[43]: 'etc/passwd'
    In [44]: relpath('/etc/passwd', start='/etc/passwd')
    Out[44]: '.'
    
  • with_name(name)

    该方法将路径中原本的name替换为新的name,如果路劲中本身不存在name,则抛出ValueError错误

    In [45]: p1.with_name('new.txt')
    Out[45]: PurePosixPath('/Users/jeffery/new.txt')
      
    ## 用os.path实现
    In [46]: from os.path import join, split
    In [47]: join(split('/Users/jeffery/a.b.c.txt')[0], 'new.txt')
    Out[47]: '/Users/jeffery/new.txt'
    
  • with_stem(stem)

    该方法将路径中原本的stem替换为新的stem,如果路劲中本身不存在name,则抛出ValueError错误

    In [51]:  p1.with_stem('new')
    Out[51]: PurePosixPath('/Users/jeffery/new.txt')
      
    ## 用os.path实现
    In [52]: from os.path import join, split, splitext
    In [53]: join(split("/Users/jeffery/a.b.c.txt")[0],'new' + splitext("/Users/jeffery/a.b.c.txt")[1])
    Out[53]: '/Users/jeffery/new.txt'
    
  • with_suffix(suffix)

    该方法将路径中原本的suffix替换为新的suffix,如果suffix="",则原本的suffix直接移除

    In [54]: p1.with_suffix('.py')
    Out[54]: PurePosixPath('/Users/jeffery/a.b.c.py')
    

ConcretePath方法和属性-文件系统操作

cwd()home()stat(*,follow_symlinks=True)chmod(mode,*,follow_symlinks=True)
expanduser()glob(pattern)group()is_dir()
is_mount()is_symlink()is_socket()is_fifo()
is_char_device()iterdir()lchmod(mode)lstat()
open(mode=‘r’,buffering=-1,encoding=None,errors=None,newline=None)owner()read_bytes()read_text(encoding=None,errors=None)
rename(target)replace(target)resolve(strict=False)rglob(pattern)
samefile(other_path)symlink_to(target,target_is_directory=False)hardlink_to(target)link_to(target)
unlink(missing_ok=False)write_bytes(data)write_text(data,encoding=None,errors=None,newline=None)

为了探索ConcretePath方法和属性,我们首先在家目录下建一个新的文件tmp, 并在tmp下新建文件a.txt

In [1]: from pathlib import PosixPath, Path, WindowsPath

In [2]: p1 = PosixPath('./tmp')

In [3]: p2 = Path('./tmp/a.txt')
  • cwd()

    该方法返回当前所在文件夹,等同于os.getcwd(),因此可以知道该方法应该是一个classmethod,其调用与具体的对象无关

    In [4]: Path.cwd(), PosixPath.home()
    Out[4]: (PosixPath('/Users/jeffery'), PosixPath('/Users/jeffery'))
    
  • home()

    该方法也是一个classmethod,返回用户的家目录路径,等同于os.path.expanduser()

  • stat(*, follow_symlinks=True)

    该方法类似于前面介绍的os.stat(),返回一个os.stat_result对象(stat_result详细属性请见os.stat_result)。所以,类似的,follow_symlinks=False时,可以统计链接文件的信息, 等效于os.lstat

    In [5]: stat_result  = p2.stat()
    
    In [6]: stat_result
    Out[6]: os.stat_result(st_mode=33188, st_ino=12936268394, st_dev=16777223, st_nlink=1, st_uid=501, st_gid=20, st_size=15, st_atime=1637994757, st_mtime=1637994751, st_ctime=1637994751)
    
  • chmod(mode, *, follow_symlinks=True)

    修改路径权限,类似于os.chmod()或则shell 命令chmod。这里我们使用

    In [10]: p2.chmod(0o655)  # (python3)这里使用8进制,更为简明
    In [11]: ll tmp/
    -rw-r-xr-x  1 jeffery  staff  15 Nov 27 14:32 a.txt
    
  • exists()

    判断路径是否存在,值得注意的是,当路径是链接文件时,该方法会进一步追踪查看对应的符号链接是否存在

  • expanduser()

    同上面的os.path.expanduser(),如果家目录无法确定的话,则抛出RuntimeError

  • glob(pattern)

    根据pattern搜索路径中匹配的文件,pattern的匹配规则同fnmatch,主要利用以下unix shell-like通配符进行简单的文件匹配任务(如更复杂的任务,请参考python re 正则表达式):

    PatternMeaning
    *matches everything
    ?matches any single character
    [seq]matches any character in seq
    [!seq]matches any character not in seq

    glob(pattern)方法返回一个generator类型,可以通过迭代的方法取出所有匹配的文件:

    In [13]: g = p1.glob('*.txt')
    
    In [14]: for _g in g:
        ...:     print(_g)
        ...: 
    tmp/a.txt
    
    In [15]: type(g)
    Out[15]: generator
    

    python标准库globglob.glob()glob.iglob()函数也是利用unix shell-like 通配符进行文件匹配。前者返回数据类型为list,后者返回类型为iterator。

    In [16]: from glob import glob, iglob, escape
    In [17]: glob('./tmp/*.txt')
    Out[17]: ['./tmp/a.txt']
    In [18]: iglob('./tmp/*.txt')
    Out[18]: <generator object _iglob at 0x110e8e510>
    

    需要注意的是,glob.glob()默认情况下是不会匹配隐藏文件的;而Path.glob()会匹配隐藏文件:

    In [19]: for _g in p1.glob('*.txt'):
        ...:     print(_g)
        ...: 
    tmp/.a.txt
    tmp/a.txt
    
    In [20]: glob('./tmp/*.txt')
    Out[20]: ['./tmp/a.txt']
    

    另外,当文件匹配的路径中含有通配符时,我们可以借助glob.escape()函数进行快速注释,比如我们新建文件mkdir -p "./tmp/[ABC]/b.txt",则我们需要利用以下方式进行匹配:

    In [21]: from os.path import join
    
    In [22]: glob(join(escape('./tmp/[ABC]'), '*.txt'))
    Out[22]: ['./tmp/[ABC]/b.txt']
    
  • group()

    返回文件所属组别

  • is_dir()

    判断路径是否是文件夹,返回bool值;需要注意的是如果文件不存在或符号链接文件有问题时,也返回False(以下类似函数也适用该规则)。

  • is_file()

    判断路径是否是文件,返回bool值

  • is_mount()

    判断路径是否是挂载点,返回bool值

  • is_symlink()

    判断路径是否是符号链接文件,返回bool值

  • is_socket()

    判断路径是否是socket连接,返回bool值

  • is_fifo()

    判断路径是否是FIFO管道文件,返回bool值

  • is_block_device()

    判断路径是否是块设备文件,返回bool值

  • is_char_device()

    判断路径是否是字符设备文件,返回bool值

  • iterdir()

    遍历文件夹,并返回路径中所有的文件

    In [23]: for _dir in p1.iterdir():
        ...:     print(_dir)
        ...: 
    tmp/.a.txt
    tmp/a.txt
    tmp/[ABC]
    
  • lchmod(mode)

    不同于Path.chmod(mode)默认效果,该函数修改文件权限时,如果路径是链接文件,不会同时修改对应符号链接文件的权限

  • lstat()

    不同于Path.stat(),当路径是链接文件时,返回链接文件的统计信息而非它的目标文件

  • mkdir(mode=511, parents=False, exist_ok=False)

    创建文件夹

  • open(mode=‘r’, buffering=- 1, encoding=None, errors=None, newline=None)

    打开文件,类似于python内置函数open()

  • owner()

    返回路径所属用户

  • read_bytes()

    读取bytes,相当于打开并读取两步操作

    In [24]: p2.read_bytes()
    Out[24]: b'This is a bird\n'
    
    In [25]: open('./tmp/a.txt', 'rb').read()
    Out[25]: b'This is a bird\n'
    
  • read_text(encoding=None, errors=None)

    类似于read_bytes(),但是以text形式读取文件

  • readlink()

    该方法返回路径的目标链接文件,如果路径不是链接文件,则抛出OSError

    In [56]: ll ./tmp/ # 命令行新建文件a.link.txt ln -s a.txt a.link.txt
    total 8
    drwxr-xr-x  3 jeffery  staff  96 Nov 30 11:27 [ABC]/
    lrwxr-xr-x  1 jeffery  staff   5 Nov 30 15:03 a.link.txt@ -> a.txt
    -rw-r-xr-x  1 jeffery  staff  15 Nov 30 11:16 a.txt*
    
    In [54]: Path('./tmp/a.link.txt').readlink()
    Out[54]: PosixPath('a.txt')
    
  • rename(target)

    重命名文件,返回值为更新后指向target的对象,如果target所对应的路径已经存在,在权限允许下,会直接覆盖。target参数既可以是字符串类型也可以是path对象。

    target参数可以是相对路径,也可以是绝对路径;当相对路径时,相对的是Path.cwd()而不是path 对象。

    In [55]: p = Path('foo')
    
    In [56]: p.open('w').write('some text')
    Out[56]: 9
    
    In [57]: target = Path('bar')
    
    In [58]: p.rename(target)
    Out[58]: PosixPath('bar')
    
    In [59]: target.open().read()
    Out[59]: 'some text'
    
  • replace(target)

    同上,重命名文件,返回值为更新后指向target的对象。如果target对象已经存在,则会无条件的覆盖

  • resolve(strict=False)

    返回路径的绝对路径,如果路径是链接文件,则返回路径对应符号链接文件的绝对路径

    In [60]: p2.resolve()
    Out[60]: PosixPath('/Users/jeffery/tmp/a.txt')
    
    In [61]: Path('./tmp/a.link.txt').resolve()
    Out[61]: PosixPath('/Users/jeffery/tmp/a.txt')
    

    strict=False时,如果文件不存在,它也会强行解析出其路径;strict=True时,如果文件不存在,则抛出FileNotFoundError错误

    In [62]: Path('./tmp/text.txt.zip').resolve()
    Out[62]: PosixPath('/Users/jeffery/tmp/text.txt.zip')
    
    In [63]: Path('./tmp/text.txt.zip').resolve(strict=True)
    FileNotFoundError: [Errno 2] No such file or directory: '/Users/jeffery/tmp/text.txt.zip'
    
  • rglob(pattern)

    该方法等同于Path.glob("**/{pattern}")

  • rmdir()

    删除文件夹,文件夹必须为空

  • samefile(other_path)

    返回bool值,判断路径与other_path是否指向同一个文件。如果任一文件不存在或权限问题而无法访问,则抛出OSError。该方法类似于上面探讨到的os.path.samefile()os.path.samestat()

  • symlink_to(target, target_is_directory=False)

    该方法链接路径到target路径,当target是文件夹时,在windows下需要设置target_is_directory=True; 在unix环境下,该参数是忽略的。

    In [64]: Path('./tmp/link.txt').symlink_to('./tmp/a.txt')
      
    -rw-r-xr-x  1 jeffery  staff    15B Nov 30 11:16 a.txt
    lrwxr-xr-x  1 jeffery  staff    11B Dec  2 21:43 link.txt -> ./tmp/a.txt
    
  • hardlink_to(target)

    类似于symlink_to(),该方法创建硬链接

  • link_to(target)

    hardlink_to()相反,该方法中target作为硬链接文件链接到对象对应路径

    In [65]: p2.link_to('./tmp/hard.link.txt')
      
    -rw-r-xr-x  2 jeffery  staff    15B Nov 30 11:16 a.txt
    -rw-r-xr-x  2 jeffery  staff    15B Nov 30 11:16 hard.link.txt
    
  • touch(mode=438, exist_ok=True)

    创建文件,当exist_ok=True且对应文件已存在时,会抛出FileExistsError

  • unlink(missing_ok=False)

    删除文件或移除符号链接文件,当missing_ok=False且路径不存在时,抛出FileNotFoundError

  • write_bytes(data)

    read_bytes反操作,写入bytes,相当于打开并写入bytes两步操作

    In [66]: p2.write_bytes(b'xxxx') # 覆盖式写入
    Out[66]: 4
    
  • write_text(data, encoding=None, errors=None, newline=None)

    类似于write_bytes,但是以text形式写入文件

对照表

python documentation官方网站也给出了pathlib库与os及os.path模块实现相同功能函数的对照表如下,如果你已经熟悉后者,可以依据该表格快速的进行”迁移学习“。

os and os.pathpathlib
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() and 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.hardlink_to()
os.symlink()Path.symlink_to()
os.readlink()Path.readlink()
os.path.relpath()Path.relative_to() 2
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

权限码转换

前面有个例子,涉及到python中权限编码的问题,这里进一步展开开探讨一下:

In [1]: from pathlib import Path

In [2]: p = Path('./tmp/a.txt')

In [3]: sr =p.stat()

In [4]: sr
Out[4]: os.stat_result(st_mode=33188, st_ino=12936268394, st_dev=16777223, st_nlink=1, st_uid=501, st_gid=20, st_size=4, st_atime=1638497905, st_mtime=1638453700, st_ctime=1638497208)

在命令行中,我们同样可以查看到文件的权限详情:

$ ll a.txt 
-rw-r--r--  1 jeffery  staff     4B Dec  2 22:01 a.txt

那么问题来了,st_mode=33188-rw-r--r--;以及st_mode=33188和644具有怎样的对应关系呢?

首先,我们可以利用标准库stat模块中的``函数进行转换:

In [5]: import stat
In [6]: stat.filemode(stat_result.st_mode)  # 33188 代表的权限就是644
Out[6]: '-rw-r--r--'

但是st_mode=33188包含的信息其实不仅仅包括文件读写权限的信息:

In [5]: oct(sr.st_mode)  # 转换成八进制,我们注意到八进制包含644,其实八进制最后三位对应的就是文件的读写权限编码
Out[5]: '0o100644'

st_mode=33188是一个十进制数,转换成八进制后,我们注意到八进制包含644,其实八进制最后三位对应的正是文件的读写权限编码,stat.filemode()正是利用该特性进行权限字符串转换的,规则如下:

S_IRWXU 00700   mask for file owner permissions
S_IRUSR 00400   owner has read permission
S_IWUSR 00200   owner has write permission
S_IXUSR 00100   owner has execute permission
S_IRWXG 00070   mask for group permissions
S_IRGRP 00040   group has read permission
S_IWGRP 00020   group has write permission
S_IXGRP 00010   group has execute permission
S_IRWXO 00007   mask for permissions for others (not in group)
S_IROTH 00004   others have read permission
S_IWOTH 00002   others have write permission
S_IXOTH 00001   others have execute permission

'0o100644'前面的部分0100000代表的则是文件类型—regular file,具体规则如下:

S_IFMT  0170000 bitmask for the file type bitfields
S_IFSOCK    0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
S_ISUID 0004000 set UID bit
S_ISGID 0002000 set-group-ID bit
S_ISVTX 0001000 sticky bit

写在篇后

这篇博客内容本身非常简单,但是所提到的两个模块包含了大量的API,需要我们在日常的使用中灵活应用,选择最高效、优雅的方式。其中,pathlib模块面向对象编程的路径处理方式,能够非常从容的应对较大的项目开发需求,高度统一路径操作、文件系统操作,比如单细胞转录组分析分析软件scanpy中便是采用了pathlib库进行路径处理工作,是项目开发中不可多得的好例子。

参考资料

python pathlib

python stat

get unix permission mask

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值