python os.path.splitext()的用法_你应该使用pathlib替代os.path

原标题:你应该使用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)

in

----> 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.expanduserand 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.basename和 os.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('123n')

Out: 4

In : p.read_text

Out: '123n'

In : p.write_bytes(b'456n')

Out: 4

In : p.read_bytes

Out: b'456n'

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: '456n'

In : p.write_text('789n')

Out: 4

In : p.write_text('1011n')

Out: 5

In : p.read_text

Out: '1011n'

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

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/withsuffix

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

获得转成png格式的路径

把图片名字改成 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)

in

----> 1 os.mkdir('1/2/3')

FileNotFoundError: [Errno 2] No such file or directory: '1/2/3'

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

In : Path('1/2/3').mkdir

---------------------------------------------------------------------------

FileNotFoundError Traceback (most recent call last)

in

----> 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+的正确之选~

https://www.python.org/dev/peps/pep-0428/

https://docs.python.org/3/library/pathlib.html

https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/

责任编辑:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值