pathlib模块替代os.path

在Python 3.4之前和路径相关操作函数都放在os模块里面,尤其是os.path这个子模块,可以说os.path模块非常常用。而在Python 3.4,标准库添加了新的模块 - pathlib,它使用面向对象的编程方式来表示文件系统路径。

作为一个从Python 2时代过来的人,已经非常习惯使用os,那么为什么我说「应该使用pathlib替代os.path」呢?基于这段时间的体验,我列出了几个pathlib模块的优势和特点。

基本用法

在过去,文件的路径是纯字符串,现在它会是一个pathlib.Path对象:

In : from pathlib import Path

In : p = Path('/home/ubuntu')

In : p
Out: PosixPath('/home/ubuntu') In : str(p)
Out: '/home/ubuntu'

使用str函数可以把一个Path对象转化成字符串。在Python 3.6之前,Path对象是不能作为os模块下的参数的,需要手动转化成字符串:

➜  ~ ipython3.5
Python 3.5.5 (default, Aug 1 2019, 17:00:43)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.3.0 -- An enhanced Interactive Python. Type '?' for help. In : import os In : pwd
Out: '/data/home/dongweiming' In : p = Path('/') In : os.chdir(p)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-5-facfc9d7a7d1> in <module>
----> 1 os.chdir(p) TypeError: argument should be string, bytes or integer, not PosixPath In : os.chdir(str(p)) In : pwd
Out: '/'

从Python 3.6开始,这些接受路径作为参数的函数内部会先通过os.fspath调用Path对象的__fspath__方法获得字符串类型的路径再去执行下面的逻辑。所以要注意: 如果你想全面使用pathlib模块,应该使用Python3.6或者更高版本!

和os功能对应的方法列表

先看一下os(os.path)模块里部分函数与pathlib.Path对应的方法吧。下面列出的这些可以直接用pathlib里面的用法代替:

| os and os.path | pathlib | | ------------------------ | --------------------------------------- | | os.path.abspath | Path.resolve | | os.chmod | Path.chmod | | os.mkdir | 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.path.isdir | Path.is_dir | | os.path.isfile | Path.is_file | | os.path.islink | Path.is_symlink | | 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 |

举2个例子:

# 原来的写法
In : os.path.isdir(os.getcwd())
Out: True
# 新的写法
In : Path.cwd().is_dir()
Out: True # 原来的写法
In : os.path.basename('/usr/local/etc/mongod.conf')
Out: 'mongod.conf'
# 新的写法
In : Path('/usr/local/etc/mongod.conf').name
Out: 'mongod.conf'

接着感受下pathlib带来的变化。

/拼接路径

过去路径拼接最正确的方法是用os.path.join:

In : os.path.join('/', 'home', 'dongwm/code')
Out: '/home/dongwm/code' In : os.path.join('/home', 'dongwm/code')
Out: '/home/dongwm/code'

现在可以用pathlib.Path提供的joinpath来拼接:

In : Path('/').joinpath('home', 'dongwm/code')
Out: PosixPath('/home/dongwm/code')

但是更简单和方便的方法是用/运算符:

In : Path('/') / 'home' / 'dongwm/code'
Out: PosixPath('/home/dongwm/code') In : Path('/') / Path('home') / 'dongwm/code'
Out: PosixPath('/home/dongwm/code') In : '/' / Path('home') / 'dongwm/code'
Out: PosixPath('/home/dongwm/code')

这也不是什么神奇魔法,有兴趣的可以看Path对象__truediv____rtruediv__方法的实现。

链式调用

链式调用是OOP带来的重要改变,从前面的例子中也能感受到,过去的都是把路径作为函数参数传进去,现在可以链式调用。我们看一个更复杂一点的例子:

In : os.path.isfile(os.path.join(os.path.expanduser('~/lyanna'), 'config.py'))
Out: True

长不长?现在的写法呢:

In : (Path('~/lyanna').expanduser() / 'config.py').is_file()
Out: True

是不是非常符合我们从左向右的阅读习惯呢?

自带属性

Path对象带了多个有用的属性

parent/parents

如果想获得某个文件的父级目录通常需要使用os.path.dirname或者字符串的rpartition(或split)方法:

In : p = '/Users/dongweiming/test'

In : p.rpartition('/')[0]
Out: '/Users/dongweiming' In : p.rsplit('/', maxsplit=1)[0]
Out: '/Users/dongweiming' In : os.path.dirname(p)
Out: '/Users/dongweiming'

如果想获得父级的父级更麻烦一些,例如用os.path.dirname,需要这样:

In : from os.path import dirname

In : dirname(dirname(p))
Out: '/Users'

使用Path对象的parents属性可以拿到各级目录列表(索引值越大越接近root),而parent就表示父级目录:

In : p = Path('/Users/dongweiming/test')

In : p.parents[0]
Out: PosixPath('/Users/dongweiming') In : p.parents[1]
Out: PosixPath('/Users') In : p.parents[2]
Out: PosixPath('/') In : p.parent
Out: PosixPath('/Users/dongweiming') In : p.parent.parent
Out: PosixPath('/Users')

由于parent返回的还是Path对象,所以可以链式的获取其parent属性。

suffix/stem

在过去获得文件后缀名,以及去掉后缀的文件名字,需要使用os.path.basenameos.path.splitext:

In : base = os.path.basename('/usr/local/etc/my.cnf')

In : base
Out: 'my.cnf' In : stem, suffix = os.path.splitext(base) In : stem, suffix
Out: ('my', '.cnf')

现在就很方便了:

In : p = Path('/usr/local/etc/my.cnf')

In : p.suffix, p.stem
Out: ('.cnf', 'my')

注意: 当文件有多个后缀,可以用suffixes返回文件所有后缀列表:

In : Path('my.tar.bz2').suffixes
Out: ['.tar', '.bz2'] In : Path('my.tar').suffixes
Out: ['.tar'] In : Path('my').suffixes
Out: []

实用方法

Path对象里面有多个实用的方法,我举例一些。

touch方法

Python语言没有内置创建文件的方法(linux下的touch命令),过去这么做:

with open('new.txt', 'a') as f:
...

现在可以直接用Path的touch方法:

Path('new.txt').touch()

touch接受mode参数,能够在创建时确认文件权限,还能通过exist_ok参数方式确认是否可以重复touch(默认可以重复创建,会更新文件的mtime)

home

获得用户的HOME目录比较常见,过去的写法:

In : os.path.expanduser('~')
Out: '/Users/dongweiming'

现在就Path.home就可以了:

In : Path.home()
Out: PosixPath('/Users/dongweiming')

读写文件

Path对象自带了操作文件的方法:

In : p = Path('~/1.txt').expanduser()

In : p.write_text('123\n')
Out: 4 In : p.read_text()
Out: '123\n' In : p.write_bytes(b'456\n')
Out: 4 In : p.read_bytes()
Out: b'456\n' In : with p.open() as f:
...: for line in f:
...: print(line)
...:
456

可以用write_text写入字符串,用write_bytes将文件以二进制模式打开写入字节数据。对应的,可以用read_text读取文本内容,也可以用read_bytes以字节对象的形式返回路径指向的文件的二进制内容。还可以用open获得文件句柄。

不过需要注意,Path里面带的这几个方法只是一些「快捷方式」,「一次性的」。举个例子:

In : p = Path('~/1.txt').expanduser()

In : p.read_text()
Out: '456\n' In : p.write_text('789\n')
Out: 4 In : p.write_text('1011\n')
Out: 5 In : p.read_text()
Out: '1011\n'

可以看到,多次写入最终结果是最后一次写入的内容。而读取也没有缓存区,全部读取出来。其实读一下源码即能理解:

In [96]: p.read_text??
Signature: p.read_text(encoding=None, errors=None)
Source:
def read_text(self, encoding=None, errors=None):
"""
Open the file in text mode, read it, and close the file.
"""
with self.open(mode='r', encoding=encoding, errors=errors) as f:
return f.read()
File: /usr/local/lib/python3.7/pathlib.py
Type: method In [97]: p.write_text??
Signature: p.write_text(data, encoding=None, errors=None)
Source:
def write_text(self, data, encoding=None, errors=None):
"""
Open the file in text mode, write to it, and close the file.
"""
if not isinstance(data, str):
raise TypeError('data must be str, not %s' %
data.__class__.__name__)
with self.open(mode='w', encoding=encoding, errors=errors) as f:
return f.write(data)
File: /usr/local/lib/python3.7/pathlib.py
Type: method

**在实际工作中这些方法要谨慎使用!``

with_name/with_suffix

以前我写过一些修改文件名字或者路径后缀的需求:基于某个文件路径生成另外一个文件路径。举个例子,有一个文件地址'/home/gentoo/screenshot/abc.jpg',2个需求:

  1. 获得转成png格式的路径
  2. 把图片名字改成123

过去需要这么做:

In : p = '/home/gentoo/screenshot/abc.jpg'

In : '{}.png'.format(os.path.splitext(p)[0])
Out: '/home/gentoo/screenshot/abc.png' In : root, ext = os.path.splitext(p) In : '{}/{}{}'.format(root.rpartition('/')[0], 123, ext)
Out: '/home/gentoo/screenshot/123.jpg'

可读性很差。现在呢:

In : p = Path('/home/gentoo/screenshot/abc.jpg')

In : p.with_suffix('.png')
Out: PosixPath('/home/gentoo/screenshot/abc.png') In : p.with_name(f'123{p.suffix}')
Out: PosixPath('/home/gentoo/screenshot/123.jpg')

mkdir

过去创建目录时,用os.mkdir只能创建一级目录:

In : os.mkdir('1/2/3')
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-160-71cc3a9a36b4> in <module>
----> 1 os.mkdir('1/2/3') FileNotFoundError: [Errno 2] No such file or directory: '1/2/3'

所以通常这种一次要创建多级目录,需要用到os.makedirs,我一直觉得这么搞很分裂。在Path对应上有mkdir方法,还接受parents,以及modeexist_ok参数:

In : Path('1/2/3').mkdir()
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-163-202ce4b81bf9> in <module>
----> 1 Path('1/2/3').mkdir() /usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pathlib.py in mkdir(self, mode, parents, exist_ok)
1249 self._raise_closed()
1250 try:
-> 1251 self._accessor.mkdir(self, mode)
1252 except FileNotFoundError:
1253 if not parents or self.parent == self: FileNotFoundError: [Errno 2] No such file or directory: '1/2/3' In : Path('1/2/3').mkdir(parents=True) In : !tree 1/
1/
└── 2
└── 3 2 directories, 0 files

我认为这么用的体验着实好了很多~

owner

有时候操作文件前需要确认拥有此文件的用户,过去我都是这么写:

In : import pwd

In : pwd.getpwuid(os.stat('/usr/local/etc/my.cnf').st_uid).pw_name
Out: 'dongweiming'

现在封装起来可以直接用了:

In : p.owner()
Out: 'dongweiming'

后记

以上就是我使用的体验了,pathlib可以说是Python 3.6+的正确之选~

延伸阅读

  1. https://www.python.org/dev/peps/pep-0428/
  2. https://docs.python.org/3/library/pathlib.html
  3. https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/

pathlib模块替代os.path的更多相关文章

  1. python 模块之os.path模块

    # -*- coding: cp936 -*- #python 27 #xiaodeng #python 模块之os.path模块 #http://www.cnblogs.com/dkblog/arc ...

  2. os模块和os.path模块常用方法

    今天和大家分享python内置模块中的os模块和os.path模块. 1.什么是模块呢? 在计算机开发过程中,代码越写越多,也就越来越难以维护,所以为了可维护的代码,我们会把函数进行分组,放在不同的文 ...

  3. os模块、os.path模块、shutil模块、configparser模块、subprocess模块

    一.os模块 os指的是操作系统 该模块主要用于处理与操作系统相关的操作,常用的是文件操作(读.写.删.复制.重命名). os.getcwd()  获取当前文件所在的文件夹路径 os.chdir()  ...

  4. 【9】python关于os模块与os.path的相关操作

    ---恢复内容开始--- #__author:"吉*佳" #date: 2018/10/20 0020 #function: # os模块知识点 import os # 获取平台名 ...

  5. 文件操作:os模块与os.path模块

    一.os与os.path 原创:http://www.cnblogs.com/lovemo1314/archive/2010/11/08/1871781.html os模块用于处理文件及文件夹,包括文 ...

  6. python之os模块(os.path)

    我们在做自动化测试的时候,可能会遇到一些需要处理文件一些需求,那么我们可以通过直接写文件的目录进行操作,当然作为一名自动化测试工程师,怎么可能用这种方法?python中自带的有OS,我们可以通过os模 ...

  7. Python笔记(十三)_os模块和os.path模块

    os模块中关于文件/目录常用的函数使用方法 getcwd() 返回当前工作目录 chdir(path) 改变工作目录 listdir(path='.') 列举指定目录中的文件名('.'表示当前目录,' ...

  8. Python os模块、os.path模块常用方法

    os模块:os模块在python中包含普遍的操作系统功能,下面列出了一些在os模块中比较有用的部分. os.sep 可以取代操作系统特定的路径分隔符.windows下为 "\" o ...

  9. 【python常用模块】os.path

    os.path.abspath(path) #返回绝对路径 os.path.basename(path) #返回文件名 os.path.commonprefix(list) #返回list(多个路径) ...

随机推荐

  1. Windows 环境上域名配置

    1.Hosts位置 C:\Windows\System32\drivers\etc\hosts 2.Hosts内容 # Copyright (c) 1993-2009 Microsoft Corp. ...

  2. 【每日一包0005】arr-flatten

    github地址:https://github.com/ABCDdouyae... arr-flatten 将多维数组展开成一维数组 文档地址:https://www.npmjs.com/packag ...

  3. hibernate UML图

  4. jsp四种属性范围

    在JSP提供了四种属性的保存范围.所谓的属性保存范围,指的就是一个设置的对象,可以在多个页面中保存并可以继续使用.它们分别是:page.request.session.appliction. 1.pa ...

  5. leetcode-easy-array-189 Rotate Array

    mycode  75.59% class Solution(object): def rotate(self, nums, k): """ :type nums: Lis ...

  6. loj#6038 「雅礼集训 2017 Day5」远行

    分析 代码 #include<bits/stdc++.h> using namespace std; #define fi first #define se second #define ...

  7. c语言中static关键字用法详解

    个人总结: 1.C不是面向对象的,在c中static修饰的变量或函数仅在当前文件中使用 2.C可以对局部变量使用static修饰(注意面向对象的java则不行),其放在全局区一直存在 概述static ...

  8. 为什么在vmware中不能使用ctrl+alt+F1~6切换到字符控制台

    为什么在vmware中不能使用ctrl+alt+F1~6切换到字符控制台 是因为vmware虚拟机的快捷键: ctrl+alt也用到了 因为vmware本身的hot keys也用到了ctrl+alt: ...

  9. Spring 缓存切面

    缓存切面:[通知+目标方法调用] 缓存操作执行过程: 1)如果是同步调用[sync=true],则首先尝试从缓存中读取数据,读取到则直接返回: 否则执行目标方法,将结果缓存后返回. 2)如果不是同步调 ...

  10. windows安装程序制作

    作为一个学计算机的,现在才知道那些安装软件都是用软件封装工具封装起来的. 我们写好exe以后可以下载一个Inno setup5 对其打包成可安装的软件,期间可加入图标,readme,等等一些东西.