48

华丽的蜕变-Python3中使用Pathlib模块,文件操作So Easy!

 4 years ago
source link: https://www.tuicool.com/articles/jAfiYva
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在本教程中,你将了解如何使用pathlib模块操作目录和文件的名称。 学习如何读取和写入文件,拼接路径和操作底层文件系统的新方法,以及如何列出文件并迭代它们的一些示例。

大多人处理文件用的最多的还是os模快吧,比如下面这样的操作

>>> path.rsplit('\\', maxsplit=1)[0]
 

或者写出下面这样长长的代码

>>> os.path.isfile(os.path.join(os.path.expanduser('~'), 'realpython.txt'))
 

使用pathlib模块,可以使代码使用优雅,可读和Pythonic代码重写上面的两个示例,如:

>>> path.parent
>>> (pathlib.Path.home() / 'realpython.txt').is_file()
 

Python文件路径处理问题

由于许多不同的原因,使用文件和与文件系统交互很重要。 最简单的情况可能只涉及读取或写入文件,但有时候会有更复杂的任务。 也许你需要列出给定类型的目录中的所有文件,查找给定文件的父目录,或者创建一个尚不存在的唯一文件名。

一般情况,Python使用常规文本字符串表示文件路径。 一般在使用os,glob和shutil等库的时候会使用到路径拼接的操作,使用os模块拼接起来显得略显复杂,以下示例仅需要三个import语句来将所有文本文件移动到归档目录:

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)
 

使用常规的字符串去拼接路径是可以的,但是由于不同的操作系统使用的分隔符不同,这样就容易出现问题,所以一般我们使用最多的还是使用os.path.join()。

Python 3.4中引入了pathlib模块(PEP 428)再一次的优化了路径的拼接。使用pathlib库的Path方法,可以将一个普通的字符串转换为pathlib.Path对象类型的路径。

早期,其他软件包仍然使用字符串作为文件路径,但从Python 3.6开始,pathlib模块在整个标准库中得到支持,部分原因是由于增加了文件系统路径协议。 如果你坚持使用传统的Python,那么Python 2也有一个可用的向后移植。

ok,说了那么多下面让我们看看pathlib如何在实践中发挥作用。

创建路径

这里我们首先要知道两个用法,先看代码:

from pathlib import Path
 

你真正需要知道的是pathlib.Path类。 创建路径有几种不同的方式。 首先,有类方法,如.cwd(当前工作目录)和.home(用户的主目录):

from pathlib import Path
 
now_path = Path.cwd()
home_path = Path.home()
 
print("当前工作目录",now_path,type(now_path))
print("home目录",home_path,type(home_path))
 

输出内容

当前工作目录 /Users/chennan/pythonproject/demo <class 'pathlib.PosixPath'>
home目录 /Users/chennan <class 'pathlib.PosixPath'>
 

可以发现路径格式为pathlib.PosixPath这是在unix系统下的显示。在不同的系统上显示的格式也是不一样,在windows系统会显示为WindowsPath。但是不管什么显示类型,都不影响后面的操作。

前面我们提到过可以通过把字符串类型的路径,转换为Pathlib.Path类型的路径,经过测试发现在Python3.4以后很多模块以及支持该格式的路径。不用转为成字符串使用了。比起os.path.join拼接路径的方式,pathlib使用起来更加的方便,使用示例如下:

import pathlib
DIR_PATH = pathlib.Path("/Users/chennan/CDM")
print(DIR_PATH,type(DIR_PATH))
 

输出内容:

/Users/chennan/CDM <class 'pathlib.PosixPath'>
 

通过 “/” 我们就可以对路径进行拼接了,怎么样是不是很方便呢。

读文件和写文件

在我们使用open来操作文件读写操作的时候,不仅可以使用字符串格式的路径,对于pathlib生成的路径完全可以直接使用:

path = pathlib.Path.cwd() / 'test.md'
with open(path, mode='r') as fid:
    headers = [line.strip() for line in fid if line.startswith('#')]
print('\n'.join(headers))
 

或者在pathlib的基础使用open,我们推荐使用下面的方式

import pathlib
DIR_PATH = pathlib.Path("/Users/chennan/CDM") / "2000" / "hehe.txt"
with DIR_PATH.open("r") as fs:
     data = fs.read() 
print(data)     
 

这样写的好处就是open里面我们不需要再去传入路径了,直接指定文件读写模式即可。实际上这里的open方法,底层也是调用了os.open的方法。使用哪种方式看个人的喜好。

pathlib还提供几种文件的读写方式:

可以不用再使用with open的形式即可以进行读写。

.read_text(): 找到对应的路径然后打开文件,读成str格式。等同open操作文件的"r"格式。
.read_bytes(): 读取字节流的方式。等同open操作文件的"rb"格式。
.write_text(): 文件的写的操作,等同open操作文件的"w"格式。
.write_bytes(): 文件的写的操作,等同open操作文件的"wb"格式。
 

使用resolve可以通过传入文件名,来返回文件的完整路径,使用方式如下

import pathlib
py_path =pathlib.Path("superdemo.py")
print(py_path.resolve()) 
 

输出

/Users/chennan/pythonproject/demo/superdemo.py
 

需要注意的是”superdemo.py”文件要和我当前的程序文件在同一级目录。

选择路径的不同组成部分

pathlib还提供了很多路径操作的属性,这些属性可以选择路径的不用部位,如

.name : 可以获取文件的名字,包含拓展名。

.parent : 返回上级文件夹的名字

.stem : 获取文件名不包含拓展名

.suffix : 获取文件的拓展名

.anchor : 类似盘符的一个东西,

import pathlib
 
now_path = pathlib.Path.cwd() / "demo.txt"
print("name",now_path.name)
print("stem",now_path.stem)
print("suffix",now_path.suffix)
print("parent",now_path.parent)
print("anchor",now_path.anchor)
 

输出内容如下

name demo.txt
stem demo
suffix .txt
parent /Users/chennan/pythonproject/demo
anchor /
 

移动和删除文件

当然pathlib还可以支持文件其他操作,像移动,更新,甚至删除文件,但是使用这些方法的时候要小心因为,使用过程不用有任何的错误提示即使文件不存在也不会出现等待的情况。

使用replace方法可以移动文件,如果文件存在则会覆盖。为避免文件可能被覆盖,最简单的方法是在替换之前测试目标是否存在。

import pathlib
 
destination = pathlib.Path.cwd() / "target" 
source = pathlib.Path.cwd() / "demo.txt"
if not destination.exists():
    source.replace(destination)
 

但是上面的方法存在问题就是,在多个进程多destination进行的操作的时候就会现问题,可以使用下面的方法避免这个问题。也就是说上面的方法适合单个文件的操作。

import pathlib
 
destination = pathlib.Path.cwd() / "target" 
source = pathlib.Path.cwd() / "demo.txt"
with destination.open(mode='xb') as fid: 
    #xb表示文件不存在才操作
    fid.write(source.read_bytes())
 

当destination文件存在的时候上面的代码就会出现FileExistsError异常。

从技术上讲,这会复制一个文件。 要执行移动,只需在复制完成后删除源即可。

使用with_name和with.shuffix可以修改文件名字或者后缀。

import pathlib
source = pathlib.Path.cwd() / "demo.py"
source.replace(source.with_suffix(".txt")) #修改后缀并移动文件,即重命名
 

可以使用.rmdir()和.unlink()来删除文件。

import pathlib
 
destination = pathlib.Path.cwd() / "target" 
source = pathlib.Path.cwd() / "demo.txt"
source.unlink()
 

几个pathlib的使用例子

统计文件个数

我们可以使用.iterdir方法获取当前文件下的所以文件.

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})
 

通过配合使用collections模块的Counter方法,我们获取了当文件夹下文件类型情况。

前面我们说过glob模块点这里了解【 https://www. cnblogs.com/c-x-a/p/926 1832.html 】,同样的pathlib也有glob方法和rglob方法,不同的是glob模块里的glob方法结果是列表形式的,iglob是生成器类型,在这里pathlib的glob模块返回的是生成器类型,然后pathlib还有一个支持递归操作的rglob方法。

下面的这个操作我通过使用glob方法,设定规则进行文件的匹配。

import pathlib
from  collections import Counter
gen =(p.suffix for p in pathlib.Path.cwd().glob('*.py'))
print(Counter(gen))
 

展示目录树

下一个示例定义了一个函数tree(),该函数的作用是打印一个表示文件层次结构的可视树,该树以一个给定目录为根。因为想列出其子目录,所以我们要使用.rglob()方法:

import pathlib
from  collections import Counter
def tree(directory):
    print(f'+ {directory}')
    for path in sorted(directory.rglob('*')):
        depth = len(path.relative_to(directory).parts)
        spacer = '    ' * depth
        print(f'{spacer}+ {path.name}')
 
now_path = pathlib.Path.cwd()
 
if __name__ == '__main__':
    tree(now_path)
 

其中relative_to的方法的作用是返回path相对于directory的路径。

parts方法可以返回路径的各部分。例如

import pathlib
now_path = pathlib.Path.cwd()
if __name__ == '__main__':
    print(now_path.parts)
 

返回

('/', 'Users', 'chennan', 'pythonproject', 'demo')
 

获取文件最后一次修改时间

iterdir(),.glob()和.rglob()方法非常适合于生成器表达式和列表理解。

使用stat()方法可以获取文件的一些基本信息,使用.stat().st_mtime可以获取文件最后一次修改的信息

import pathlib
now_path = pathlib.Path.cwd()
from datetime import datetime
time, file_path = max((f.stat().st_mtime, f) for f in now_path.iterdir())
print(datetime.fromtimestamp(time), file_path)
 

甚至可以使用类似的表达式获取上次修改的文件内容

import pathlib
from datetime import datetime
now_path =pathlib.Path.cwd()
result = max((f.stat().st_mtime, f) for f in now_path.iterdir())[1]
print(result.read_text())
 

.stat().st_mtime会返回文件的时间戳,可以使用datetime或者time模块对时间格式进行进一步转换。

其他内容

关于pathlib.Path格式路径转换为字符串类型

因为通过pathlib模块操作生成的路径,不能直接应用字符串的一些操作,所以需要转换成字符串,虽然可以使用str()函数进行转换,但是安全性不高,建议使用os.fspath()方法,因为如果路径格式非法的,可以抛出一个异常。str()就不能做到这一点。

拼接符号”/”背后的秘密

/运算符由__truediv__()方法定义。 实际上,如果你看一下pathlib的源代码,你会看到类似的东西。

class PurePath(object):
 
    def __truediv__(self, key):
        return self._make_child((key,))
 

后记

从Python 3.4开始,pathlib已在标准库中提供。 使用pathlib,文件路径可以由适当的Path对象表示,而不是像以前一样用纯字符串表示。 这些对象使代码处理文件路径:

  • 更容易阅读,特别是可以使用“/”将路径连接在一起
  • 更强大,直接在对象上提供最必要的方法和属性
  • 在操作系统中更加一致,因为Path对象隐藏了不同系统的特性

在本教程中,你已经了解了如何创建Path对象、读取和写入文件、操作路径和底层文件系统,以及如何遍历多个文件路径等一系列实例。

最后,建议下去自己多加练习,我对文章中的代码都进行了验证,不会出现运行错误的情况。

——————————————————————————————

原文:https://realpython.com/python-pathlib/

译者: 陈祥安

yy2qmiM.jpg!web 扫码关注

更多精彩内容,请关注微信公众号:python学习开发。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK