Table of Contents
Python
正则表达式
正则表达式入门(转载)
看到了一篇写的很清晰的《正则表达式入门》,转载如下:
正则表达式说白了就是一堆约定俗成的匹配规则。如果从微观入手,你会发现有背不完的规则;如果从宏观入手,你会发现万变不离其宗。所以我们将从宏观到微观依次说起,依次为大家总结三大宏观规则、两大微观规则,再附带一部分高级内容。基本可以通过一篇文章让大家理解正则表达式的主要用法。
先说三大宏观规则。
我们用大写字母表示抽象的正则规则,主要讨论正则之间的关系,忽略具体的内容
一、交集规则。
如果有两个正则表达式E
和F
,那么EF
也是一个正则,表示同时匹配E
和F
的内容。这跟编程中的逻辑与是一个意思,跟集合中的交集也是一个意思。你也可以连接任意多个正则,比如EFGH
。
二、并集规则。
如果有两个正则表达式E
和F
,那么E|F
也是一个正则,表示匹配E
或者匹配F
。这跟编程中的逻辑或是一个意思,跟集合中的并集也是一个意思。 你可以使用|
连接任意多个正则表达式,比如E|F|G|H
。
如果你要连接非常多的正则,那就得写非常多的竖线,看起来非常乱。所以人们还约定了一种简化记法[EFGH]
。也就是你写成[EFGH]
跟写成E|F|G|H
效果是一样,但前者更简短,更清晰。
三、补集规则
编程有与或非,集合有交并补。正则表达式有没有类似逻辑非或者集合中的补集呢?有,但记法比较复杂。如果你要排除匹配E
和F
的内容,需要写成[^EF]
。
我们已经知道[EF]
可以匹配那些匹配E
或者匹配F
的内空,在前加上一个^
表示取反。因为取了反,原来的逻辑或关系变成了逻辑与(具体请参见德摩根定律)。所以[^EF]
匹配那些不能匹配E
而且不能匹配F
的内容。
以上交并补规则单个看都不复杂,但它们可以任意组合,用起来就有点复杂。
比如A|BC|[^D]E
表示匹配A
或者同时匹配B
和C
或者不能匹配D
但要匹配E的三种内容。这么长的规则只用A|BC|[^D]E
就能准确表达,没有任何歧义,这就是正则的魅力。
这里有一个问题。A|BC|[^D]E
表示A
、BC
和[^D]E
的并集,还是表示A|BC
跟[^D]E
并集,还是表示A
跟BC|[^D]E
的并集呢?这就涉及到结合优先级的问题了。
正确的答案是第一种,被|
分割的部分是平级的。如果你想表示A|BC
跟[^D]E
的并集,那你需要写成(A|BC)|[^D]E
。对了,遇事不决加括号!
在前面的讨论中,ABCDE
都是抽像的正则,我们并不关心具体的规则内容。而对应的交、并补规则对所有正则都管用。现在我们开始讨论两大微观规则。
第一条,单字符规则。
所谓单字符就是一次匹配一个字符,但字符的取值可能是五花八门。
一个字母a
, 一个数字1
都是正则,分别匹配包含a
和包含1
的内容。
如果我们想匹配数字1234
,那么根据交集规则,我们写成1234
就可以了。
如果我们想匹配所有可能出现的数字,则可以根据并集规则写成0|1|2|3|4|5|6|7|8|9
。是不有点长。我们可以简化成[0123456789]
。一下子少了很多坚线。
慢着,如果想匹配所有可能出现的小写字母呢?难不成要写成[abc此处省略20个字母xyz]
?太长了😂
正则表达式为此提供了一种更加简化方法——连字符,可以使用减号-表示连续出现的字符,只需写出头尾。所以我们可以把[0123456789]
进一步简化成[0-9]
,把[abc...xyz]
简化成[a-z]
。
如果要匹配所有字母,不区分大小写,可以写成[a-zA-Z]
,如果还想顺手匹配所有数字,可以写成[a-zA-Z0-9]
。
因为[0-9]
很常用,大家又进一步简化成了\d
(对应单词 digit)。并非所有的正则引擎都支持这个\d
,有的默认不支持,需要开启 perl 兼容的正则引擎才行。
因为[a-zA-Z]
也很常用,大家就把它简化成\a
(对应单词 alpha)。
同样的,如果想匹配大小写字母、数字和下划线(也就是所有单词字符),可以写成[a-zA-Z0-9_]
。也是因为太常用,大家将其简化为\w
(对应单词 word)。
如果想匹配一些空白字符,可以写成[ \t\r\n\v\f]
,这个正则会匹配空格、水平制表符、回车、换行、 垂直制表符和 Page break 记。这里用到了跟 c 语言 printf 函数一样的转义字符。同样因为使用广泛,被简化为\s
(对应单词 space)。
我们说过,正则支持取反操作。[0-9]
表示匹配所有数字,那[^0-9]
就表示匹配所有非数字字符。因为使用广泛,人们将其简写成\D
。大家注意,[0-9]
简写成\d
(小写字母),对应的[^0-9]
简写成\D
(大写字母)。以此类推,\a
取反是\A
、\w
取反是\W
、\s
取反是\S
。一下子都记住了吧。
有了连字符和并集规则,理论上我们可以匹配所有字符。但是 Unicode 有上百万字符,难道我们都要写到方括号里吗?不能够。
我们可以利用取反操作。只要排除少量不常用字符,就可以匹配剩下的大多数字符了。但排除哪个呢?最终人们决定排除\n
。为什么呢?因为一般而言,正则都是逐行匹配的,一次匹配一行内容,不会遇到换行符。最终可以用[^\n]
表示匹配所有字符。同样因为太常用,这一写法被简化成句点.
。 也就是说在正则表达式中,一个.
可以匹配\n
以外的所有字符。
最后需要额外说一下字符反斜杠\
。我们前面提到的\d
、\w
都使用反斜杠进行转义。如果要匹配\
就得写成\\\
。因为好多语言的字符串也是使用反斜杠进行转义(比如用\n
表示换行),所以你会在代码中看到像"\\\\\\\"
这样的写法。如果你是初学者,一定会这样的鬼画符吓到。其实很简单。 第一个反斜杠用于转义第二个反斜杠,表示一个反斜杠字符;第三个转义第四个。第一个和第三个反斜杠是给语言编译器用的。如果你用printf之类的函数将这段打印到标准输出,你会看到\\\
,又是两个反斜杠。 这次转义是给正则引擎用的,用于表示匹配\
这个字符。
那么多斜杠确实容易出错。好多语言都提供所谓 raw 字符串,这种字符串不支持转义功能,也就不需要写额外的反斜杠。比如在 go 语言中"\\\\\\\"
可以写成\
\“(注意两侧的“`)是不是清爽多了🤪。
以上基本上就是单字符规则的所有内容。下面我们继续讨论第二条,多字符规则。
如果想匹配两位数字,可以利用交集规则,写成\d\d
,此正则会先匹配一个数字再匹配一个数字,最终匹配的是两位数字。如果想匹配三位数字,需要写成\d\d\d
,四位数字写成\d\d\d\d
。
那如果想匹配一位或者两位数字或者三位数字或者四位数字(也就是四位以内的数字),需要写成
\d|\d\d|\d\d\d|\d\d\d\d
有点长,但是 it works!如果想匹配所有的八位以内的数字呢?那就得写很长很长了。为此,人们又想了个简化的办法。这次引入了大括号{}
。
刚才的正则是可以简化成\d{1,4}
,展开就是\d|\d\d|\d\d\d|\d\d\d\d
。大括号中第一个数字表示最短匹配的次数,第二个数字表示最长匹配的次数。
如果想匹配八位以内的数字,就可以写成\d{1,8}
,是不是很简洁呢?
如果只想匹配一个八位数,则可以写成\d{8,8}
。重复写两个8好像有点多余,还是简化成\d{8}
吧。
那能不能匹配任意长度的数字呢? 理论上应该写成\d{1,∞}
,只是这个∞不好写,干脆省略,写成\d{1,}
算了。所以\d{1,}
表示可以匹配一位、两位、一直到任意长度的数字。也就是说{1,}
表示前面的匹配内容至少出现一次。因为这个至少出现一次也是特别常用,人们又进一步将其简化成+
,最终我们的正则变成了\d+
,优雅的不行。
那能不能实现匹配出现零次这种语义呢? 可以,只要将大括号内第一个数字写成0就行。所以a{0,}
可以匹配a、aa……aaaa……等各种情况。也就是说{0,}
表示前面的匹配的内容出现多次或者不出现。 同样十分常用,被人们简化成了*
。所以原来的正则可以简化成a*
。
最后就是{0,1}
这种情况了,显然表示出现零次或者一次。 不用说,懒人们将其简化成了?
。所以ab?
只能匹配a和ab两种情况。
看到这里,你基本已经理解了正则的常用功能。如果还不确定,就返回去再读几遍。最后我们引申出一些高级内容。
先说一下贪心。
给定一段 html <h1>this is a title</h1><p>this is content</p>
。
如果我们想匹配h1标签的内容,我们可以写成<h1>.*</h1>
。这里写了两遍h1。 显然,不想重复。我看到/h1后面有一个>,我能不能将正则改成<h1>.*>
呢?
大家可以自己试一下。正则引擎会一直匹配到</p>
里面的>
。这是为什么呢?因为正则表达式默认使用贪心模式,一次性匹配尽可能长的内容。所以在找到/h1后面的>之后还会继续向前找。
那有办法修正这种行为吗?有,使用?。你可以将正则改为<h1>.*?>
,这样引擎就会在第一次遇到>的地方停下来。这里的?跟之前说的「出现零次或一次」可不是一个意思哈。跟在*后面表示非贪心模式。其实也是为了避免引入太多的特殊符号,所以复用了?,这一定程度上会给初学者带来困扰。没办法,大家只能克服了。
再说一下引用。
给定一段 html <h1>this is a title</h1><h1>this is content</h2>
。这里第二个<h1>
没有闭合,结束标签写成了h2
。如果只想匹配正常结束的h1,那可以写成<(h1)>.*</\1>
。
这里有两点。第一,前面的h1两边加了括号。第二,在结尾的地方使用了\1
来引用前面加括号的内容。 正则引擎会为每个括号分配一个编号(从1开始记数),并记录括号的内容,大家可以使用+数字的方式来引用。这样的正则等价于<h1>.*</h1>
。
咋一看也没什么大不了的。但如果要匹配很多成对标签的话,引用的优势就体现出来了。例如<(h1|p|artice|div)>.*?</\1>
可以匹配h1、p、article和div四种闭合标签。Do not repeat yourself.
最后说一下环视。
环视说起来有点抽像 。例子不太好举,这里我引用 stack over flow 的一篇回答内容。
比如我们有字符串foobarbarfoo。下面我将用大写字母表示想要匹配的内容。为了跟英语原文对应,我们规定当前字符右边为前(未处理),左边为后(已处理)。
如果只想匹配第一个出现的bar,也就是fooBARbarfoo。肯定不能只写成bar,因为第二个bar也会匹配到。我们希望正则引擎在每碰到一个bar的时候继续向前(右)看看还有没有bar,如果还有则说明不是第一个。所以,需要写成bar(?=bar)
。 括号里以?
开头,=
表示检查是否出现, 因为是继续向前(右)看,所以叫做前向肯定环视(Look ahead positive)。
如果想匹配第二个bar也就是foobarBARfoo,则需要写成bar(?!bar)
。也就是说查到bar只后还要继续向前(右)看,没有bar才算匹配到。因为是没有,所以叫前向否定环视(Look ahead negative)。
我还可以通过向后(左)看的办法来解决类似的问题。
如果想匹配第一个bar,也就是fooBARbarfoo,我们可以写成(?<=foo)bar
,这是告诉正则引擎在找到bar之后还要回顾一下有没有遇到foo,只有碰到才算匹配成功。因为需向后(左)确定匹配成功,所以叫后向肯定环视(Look behind positive)。
如果想匹配第二个bar,也就是foobarBARfoo,我们可以写成(?<!foo)bar
,这是让引擎在找到bar之后回顾一下有没有遇到过foo,没有碰到才算匹配成功。因为需要向后(左)确定匹配不成功,所以叫后向否定环视(Look behind negative)。
我们稍微回顾一下。所有的环视都需要用括号括起来,以?
开始。匹配之后继续向前(右)检查叫前叫前向环视,如果需要确保另一模式也匹配,叫肯定环视,用=
,否则是否定环视,用!
;匹配之后继续向后(左)检查叫后向环视,为了跟前向有所区别,所以在?
之后加了一个<
,为大家指明方向,肯定和否标记则跟前向一样。
理解了环视,我们还可以做一些更有意思的事情。
第一个,可以匹配单词的边界。一个单词两边都有空格。单词的左边界是一个虚拟的位置,它右边的字符肯定是匹配\w
,它左边的内容肯定匹配\W
。我们可以要求正则引擎同时向前看和向后看。所以可以写成(?<=\W)(?=\w)
。这里两个括号之间是空的,表示只匹配位置,不消耗内容。同样的,右边的边界也是个虚拟位置,它左边的字符肯定是匹配\w
,它右边的字符肯定匹配\W的位置,所以可以写成(?<=\w)(?=\W)
。把这两个正则使用并集规则合到一起就是(?<=\W)(?=\w)|(?<=\w)(?=\W)
,就可以匹配单词的左右边界。同样因为常用,人们把它简化成了\b
。
也就是说\b
匹配单词边界。正则\bbar
只会匹配字符串foo bar中的bar,而不会匹配foobar中的bar。
第二个,可以匹配一行的开始和结束。一行的开始,顾名思义,就是第一个字符之前的位置,在它之前没有字符,在它之后是任意字符,所以我们可以写成(?<!.)(?=.)
。对于一行的结束,我们可以如法炮制,写成(?<=.)(?!.)
。同样因为常用,此二者被分别简化成了^
和$
。
因为一行的开始跟结束非常特殊,正则引擎可以直接标记,根本用不到环视这样的大招。 我将它们放到一起讲只是为了逻辑上的统一。
差不多该搁笔了。所谓言有尽而意无穷。我们讲重构、讲抽象,说到底就是要站在更高的视角看问题,要有全局意识,要有大局观。学习正则就是一个很好的例子,如果不从整体上去认识它,就会陷入死记硬背各种模式境地,费时费力容易出错不说,最关键的是会消磨你的学习热情。所以,我们在埋头学习的时候一定要时常浮出水面透透气,多思考、多总结,这样才能事半功倍。与君共勉
库
Pandas
Pandas中文教程
Pandas官方文档
xlwings
xlwings中文文档
Python 文件操作(转载)
原文地址:https://zhuanlan.zhihu.com/p/56909212
本文为译文,原文链接 working-with-files-in-python
本人博客: 编程禅师
Python中有几个内置模块和方法来处理文件。这些方法被分割到例如os
, os.path
, shutil
和 pathlib
等等几个模块中。文章将列举Python中对文件最常用的操作和方法。
在这篇文章中,你将学习如何:
- 获取文件属性
- 创建目录
- 文件名模式匹配
- 遍历目录树
- 创建临时文件和目录
- 删除文件和目录
- 复制、移动和重命名文件和目录
- 创建和解压ZIP和TAR档案
- 使用
fileinput
模块打开多个文件
Python中文件数据的读和写
使用Python对文件进行读和写是十分简单的。为此,你首先必须使用合适的模式打开文件。这里有一个如何打开文本文件并读取其内容的例子。
with open('data.txt', 'r') as f:
data = f.read()
print('context: {}'.format(data))
open() 接收一个文件名和一个模式作为它的参数,r
表示以只读模式打开文件。想要往文件中写数据的话,则用w
作为参数。
with open('data.txt', 'w') as f:
data = 'some data to be written to the file'
f.write(data)
在上述例子中,open()
打开用于读取或写入的文件并返回文件句柄(本例子中的 f
),该句柄提供了可用于读取或写入文件数据的方法。阅读 Working With File I/O in Python 获取更多关于如何读写文件的信息。
获取目录列表
假设你当前的工作目录有一个叫 my_directory 的子目录,该目录包含如下内容:
.
├── file1.py
├── file2.csv
├── file3.txt
├── sub_dir
│ ├── bar.py
│ └── foo.py
├── sub_dir_b
│ └── file4.txt
└── sub_dir_c
├── config.py
└── file5.txt
Python内置的 os 模块有很多有用的方法能被用来列出目录内容和过滤结果。为了获取文件系统中特定目录的所有文件和文件夹列表,可以在遗留版本的Python中使用 os.listdir()
或 在Python 3.x 中使用 os.scandir()
。 如果你还想获取文件和目录属性(如文件大小和修改日期),那么 os.scandir()
则是首选的方法。
使用遗留版本的Python获取目录列表
import os
entries = os.listdir('my_directory')
os.listdir()
返回一个Python列表,其中包含path参数所指目录的文件和子目录的名称。
['file1.py', 'file2.csv', 'file3.txt', 'sub_dir', 'sub_dir_b', 'sub_dir_c']
目录列表现在看上去不容易阅读,对 os.listdir()
的调用结果使用循环打印有助于查看。
for entry in entries:
print(entry)
"""
file1.py
file2.csv
file3.txt
sub_dir
sub_dir_b
sub_dir_c
"""
使用现代版本的Python获取目录列表
在现代Python版本中,可以使用 os.scandir()
和 pathlib.Path
来替代 os.listdir()
。
os.scandir()在Python 3.5 中被引用,其文档为 PEP 471 。
os.scandir()`调用时返回一个迭代器而不是一个列表。
import os
entries = os.scandir('my_directory')
print(entries)
# <posix.ScandirIterator at 0x105b4d4b0>
ScandirIterator指向了当前目录中的所有条目。你可以遍历迭代器的内容,并打印文件名。
import os
with os.scandir('my_directory') as entries:
for entry in entries:
print(entry.name)
这里 os.scandir()
和with
语句一起使用,因为它支持上下文管理协议。使用上下文管理器关闭迭代器并在迭代器耗尽后自动释放获取的资源。在 my_directory
打印文件名的结果就和在 os.listdir()
例子中看到的一样:
file1.py
file2.csv
file3.txt
sub_dir
sub_dir_b
sub_dir_c
另一个获取目录列表的方法是使用 pathlib 模块:
from pathlib import Path
entries = Path('my_directory')
for entry in entries.iterdir():
print(entry.name)
pathlib.Path() 返回的是 PosixPath 或 WindowsPath 对象,这取决于操作系统。
pathlib.Path()对象有一个 .iterdir()
的方法用于创建一个迭代器包含该目录下所有文件和目录。由 .iterdir()
生成的每个条目都包含文件或目录的信息,例如其名称和文件属性。pathlib
在Python3.4时被第一次引入,并且是对Python一个很好的加强,它为文件系统提供了面向对象的接口。
在上面的例子中,你调用 pathlib.Path()
并传入了一个路径参数。然后调用 .iterdir()
来获取 my_directory
下的所有文件和目录列表。
pathlib提供了一组类,以简单并且面向对象的方式提供了路径上的大多数常见的操作。使用 pathlib比起使用 os 中的函数更加有效。和 os相比,使用 pathlib 的另一个好处是减少了操作文件系统路径所导入包或模块的数量。想要了解更多信息,可以阅读 Python 3’s pathlib Module: Taming the File System 。
运行上述代码会得到如下结果:
file1.py
file2.csv
file3.txt
sub_dir
sub_dir_b
sub_dir_c
使用 pathlib.Path() 或 os.scandir() 来替代 os.listdir() 是获取目录列表的首选方法,尤其是当你需要获取文件类型和文件属性信息的时候。pathlib.Path() 提供了在 os 和 shutil 中大部分处理文件和路径的功能,并且它的方法比这些模块更加有效。我们将讨论如何快速的获取文件属性。
| 函数 | 描述 | | ———————— | ——————————————————– | | os.listdir() | 以列表的方式返回目录中所有的文件和文件夹 | | os.scandir() | 返回一个迭代器包含目录中所有的对象,对象包含文件属性信息 | | pathlib.Path().iterdir() | 返回一个迭代器包含目录中所有的对象,对象包含文件属性信息 |
这些函数返回目录中所有内容的列表,包括子目录。这可能并总是你一直想要的结果,下一节将向你展示如何从目录列表中过滤结果。
列出目录中的所有文件
这节将向你展示如何使用 os.listdir() ,os.scandir() 和 pathlib.Path() 打印出目录中文件的名称。为了过滤目录并仅列出 os.listdir() 生成的目录列表的文件,要使用 os.path :
import os
basepath = 'my_directory'
for entry in os.listdir(basepath):
# 使用os.path.isfile判断该路径是否是文件类型
if os.path.isfile(os.path.join(base_path, entry)):
print(entry)
在这里调用 os.listdir() 返回指定路径中所有内容的列表,接着使用 os.path.isfile() 过滤列表让其只显示文件类型而非目录类型。代码执行结果如下:
file1.py
file2.csv
file3.txt
一个更简单的方式来列出一个目录中所有的文件是使用 os.scandir() 或 pathlib.Path() :
import os
basepath = 'my_directory'
with os.scandir(basepath) as entries:
for entry in entries:
if entry.is_file():
print(entry.name)
使用 os.scandir() 比起 os.listdir() 看上去更清楚和更容易理解。对 ScandirIterator 的每一项调用 entry.isfile() ,如果返回 True 则表示这一项是一个文件。上述代码的输出如下:
file1.py
file3.txt
file2.csv
接着,展示如何使用 pathlib.Path() 列出一个目录中的文件:
from pathlib import Path
basepath = Path('my_directory')
for entry in basepath.iterdir():
if entry.is_file():
print(entry.name)
在 .iterdir() 产生的每一项调用 .is_file() 。产生的输出结果和上面相同:
file1.py
file3.txt
file2.csv
如果将for循环和if语句组合成单个生成器表达式,则上述的代码可以更加简洁。关于生成器表达式,推荐一篇Dan Bader 的文章。
修改后的版本如下:
from pathlib import Path
basepath = Path('my_directory')
files_in_basepath = (entry for entry in basepath.iterdir() if entry.is_file())
for item in files_in_basepath:
print(item.name)
上述代码的执行结果和之前相同。本节展示使用 os.scandir() 和 pathlib.Path() 过滤文件或目录比使用 os.listdir() 和 os.path 更直观,代码看起来更简洁。
列出子目录
如果要列出子目录而不是文件,请使用下面的方法。现在展示如何使用 os.listdir() 和 os.path() :
import os
basepath = 'my_directory'
for entry in os.listdir(basepath):
if os.path.isdir(os.path.join(basepath, entry)):
print(entry)
当你多次调用 os.path,join() 时,以这种方式操作文件系统就会变得很笨重。在我电脑上运行此代码会产生以下输出:
sub_dir
sub_dir_b
sub_dir_c
下面是如何使用 os.scandir() :
import os
basepath = 'my_directory'
with os.scandir(basepath) as entries:
for entry in entries:
if entry.is_dir():
print(entry.name)
与文件列表中的示例一样,此处在 os.scandir() 返回的每一项上调用 .is_dir() 。如果这项是目录,则 is_dir() 返回 True,并打印出目录的名称。输出结果和上面相同:
sub_dir_c
sub_dir_b
sub_dir
下面是如何使用 pathlib.Path() :
from pathlib import Path
basepath = Path('my_directory')
for entry in basepath.iterdir():
if entry.is_dir():
print(entry.name)
在 .iterdir() 迭代器返回的每一项上调用 is_dir() 检查是文件还是目录。如果该项是目录,则打印其名称,并且生成的输出与上一示例中的输出相同:
sub_dir_c
sub_dir_b
sub_dir
* * *
获取文件属性
Python可以很轻松的获取文件大小和修改时间等文件属性。可以通过使用 os.stat() , os.scandir() 或 pathlib.Path 来获取。
os.scandir() 和 pathlib.Path() 能直接获取到包含文件属性的目录列表。这可能比使用 os.listdir() 列出文件然后获取每个文件的文件属性信息更加有效。
下面的例子显示了如何获取 my_directory 中文件的最后修改时间。以时间戳的方式输出:
import os
with os.scandir('my_directory') as entries:
for entry in entries:
info = entry.stat()
print(info.st_mtime)
"""
1548163662.3952665
1548163689.1982062
1548163697.9175904
1548163721.1841028
1548163740.765162
1548163769.4702623
"""
os.scandir() 返回一个 ScandirIterator 对象。ScandirIterator 对象中的每一项有 .stat() 方法能获取关于它指向文件或目录的信息。.stat() 提供了例如文件大小和最后修改时间的信息。在上面的示例中,代码打印了 st_time 属性,该属性是上次修改文件内容的时间。
pathlib 模块具有相应的方法,用于获取相同结果的文件信息:
from pathlib import Path
basepath = Path('my_directory')
for entry in basepath.iterdir():
info = entry.stat()
print(info.st_mtime)
"""
1548163662.3952665
1548163689.1982062
1548163697.9175904
1548163721.1841028
1548163740.765162
1548163769.4702623
"""
在上面的例子中,循环 .iterdir() 返回的迭代器并通过对其中每一项调用 .stat() 来获取文件属性。st_mtime 属性是一个浮点类型的值,表示的是时间戳。为了让 st_time 返回的值更容易阅读,你可以编写一个辅助函数将其转换为一个 datetime 对象:
import datetime
from pathlib import Path
def timestamp2datetime(timestamp, convert_to_local=True, utc=8, is_remove_ms=True)
"""
转换 UNIX 时间戳为 datetime对象
:param timestamp: 时间戳
:param convert_to_local: 是否转为本地时间
:param utc: 时区信息,中国为utc+8
:param is_remove_ms: 是否去除毫秒
:return: datetime 对象
"""
if is_remove_ms:
timestamp = int(timestamp)
dt = datetime.datetime.utcfromtimestamp(timestamp)
if convert_to_local:
dt = dt + datetime.timedelta(hours=utc)
return dt
def convert_date(timestamp, format='%Y-%m-%d %H:%M:%S'):
dt = timestamp2datetime(timestamp)
return dt.strftime(format)
basepath = Path('my_directory')
for entry in basepath.iterdir():
if entry.is_file()
info = entry.stat()
print('{} 上次修改时间为 {}'.format(entry.name, timestamp2datetime(info.st_mtime)))
首先得到 my_directory 中文件的列表以及它们的属性,然后调用 convert_date() 来转换文件最后修改时间让其以一种人类可读的方式显示。convert_date() 使用 .strftime() 将datetime类型转换为字符串。
上述代码的输出结果:
file3.txt 上次修改时间为 2019-01-24 09:04:39
file2.csv 上次修改时间为 2019-01-24 09:04:39
file1.py 上次修改时间为 2019-01-24 09:04:39
将日期和时间转换为字符串的语法可能会让你感到混乱。如果要了解更多的信息,请查询相关的官方文档 。另一个方式则是阅读 http://strftime.org 。
创建目录
你编写的程序迟早需要创建目录以便在其中存储数据。 os 和 pathlib 包含了创建目录的函数。我们将会考虑如下方法:
| 方法 | 描述 | | ——————– | ————————– | | os.mkdir() | 创建单个子目录 | | os.makedirs() | 创建多个目录,包括中间目录 | | Pathlib.Path.mkdir() | 创建单个或多个目录 |
创建单个目录
要创建单个目录,把目录路径作为参数传给 os.mkdir() :
import os
os.mkdir('example_directory')
如果该目录已经存在,os.mkdir() 将抛出 FileExistsError 异常。或者,你也可以使用 pathlib 来创建目录:
from pathlib import Path
p = Path('example_directory')
p.mkdir()
如果路径已经存在,mkdir() 会抛出 FileExistsError 异常:
FileExistsError: [Errno 17] File exists: 'example_directory'
为了避免像这样的错误抛出, 当发生错误时捕获错误并让你的用户知道:
from pathlib import Path
p = Path('example_directory')
try:
p.mkdir()
except FileExistsError as e:
print(e)
或者,你可以给 .mkdir() 传入 exist_ok=True 参数来忽略 FileExistsError 异常:
from pathlib import Path
p = Path('example_directory')
p.mkdir(exist_ok=True)
如果目录已存在,则不会引起错误。
创建多个目录
os.makedirs() 和 os.mkdir() 类似。两者之间的区别在于,os.makedirs() 不仅可以创建单独的目录,还可以递归的创建目录树。换句话说,它可以创建任何必要的中间文件夹,来确保存在完整的路径。
os.makedirs() 和在bash中运行 mkdir -p 类似。例如,要创建一组目录像 2018/10/05,你可以像下面那样操作:
import os
os.makedirs('2018/10/05', mode=0o770)
上述代码创建了 2018/10/05 的目录结构并为所有者和组用户提供读、写和执行权限。默认的模式为 0o777 ,增加了其他用户组的权限。有关文件权限以及模式的应用方式的更多详细信息,请参考 文档 。
运行 tree 命令确认我们应用的权限:
$ tree -p -i .
.
[drwxrwx---] 2018
[drwxrwx---] 10
[drwxrwx---] 05
上述代码打印出当前目录的目录树。 tree 通常被用来以树形结构列出目录的内容。传入 -p 和 -i 参数则会以垂直列表打印出目录名称以及其文件权限信息。-p 用于输出文件权限,-i 则用于让 tree 命令产生一个没有缩进线的垂直列表。
正如你所看到的,所有的目录都拥有 770 权限。另一个方式创建多个目录是使用 pathlib.Path 的 .mkdir() :
from pathlib import Path
p = Path('2018/10/05')
p.mkdir(parents=True, exist_ok=True)
通过给 Path.mkdir() 传递 parents=True 关键字参数使它创建 05 目录和使其路径有效的所有父级目录。
在默认情况下,os.makedirs() 和 pathlib.Path.mkdir() 会在目标目录存在的时候抛出 OSError 。通过每次调用函数时传递 exist_ok=True 作为关键字参数则可以覆盖此行为(从Python3.2开始)。
运行上述代码会得到像下面的结构:
└── 2018
└── 10
└── 05
我更喜欢在创建目录时使用 pathlib ,因为我可以使用相同的函数方法来创建一个或多个目录。
* * *
文件名模式匹配
使用上述方法之一获取目录中的文件列表后,你可能希望搜索和特定的模式匹配的文件。
下面这些是你可以使用的方法和函数:
* endswith() 和 startswith() 字符串方法
* fnmatch.fnmatch()
* glob.glob()
* pathlib.Path.glob()
这些方法和函数是下面要讨论的。本小节的示例将在名为 some_directory 的目录下执行,该目录具有以下的结构:
.
├── admin.py
├── data_01_backup.txt
├── data_01.txt
├── data_02_backup.txt
├── data_02.txt
├── data_03_backup.txt
├── data_03.txt
├── sub_dir
│ ├── file1.py
│ └── file2.py
└── tests.py
如果你正在使用 Bash shell,你可以使用以下的命令创建上述目录结构:
mkdir some_directory
cd some_directory
mkdir sub_dir
touch sub_dir/file1.py sub_dir/file2.py
touch data_{01..03}.txt data_{01..03}_backup.txt admin.py tests.py
这将会创建 some_directory 目录并进入它,接着创建 sub_dir 。下一行在 sub_dir 创建 file1.py 和 file2.py ,最后一行使用扩展创建其它所有文件。想要学习更多关于shell扩展,请阅读 这里 。
使用字符串方法
Python有几个内置 修改和操作字符串 的方法。当在匹配文件名时,其中的两个方法 .startswith() 和 .endswith() 非常有用。要做到这点,首先要获取一个目录列表,然后遍历。
import os
for f_name in os.listdir('some_directory'):
if f_name.endswith('.txt'):
print(f_name)
上述代码找到 some_directory 中的所有文件,遍历并使用 .endswith() 来打印所有扩展名为 .txt 的文件名。运行代码在我的电脑上输出如下:
data_01.txt
data_01_backup.txt
data_02.txt
data_02_backup.txt
data_03.txt
data_03_backup.txt
使用 fnmatch 进行简单文件名模式匹配
字符串方法匹配的能力是有限的。fnmatch 有对于模式匹配有更先进的函数和方法。我们将考虑使用 fnmatch.fnmatch() ,这是一个支持使用 * 和 ? 等通配符的函数。例如,使用 fnmatch 查找目录中所有 .txt 文件,你可以这样做:
import os
import fnmatch
for f_name in os.listdir('some_directory'):
if fnmatch.fnmatch(f_name, '*.txt'):
print(f_name)
迭代 some_directory 中的文件列表,并使用 .fnmatch() 对扩展名为 .txt 的文件执行通配符搜索。
更先进的模式匹配
假设你想要查找符合特定掉件的 .txt 文件。例如,你可能指向找到包含单次 data 的 .txt文件,一组下划线之间的数字,以及文件名中包含单词 backup 。就类似于 data_01_backup, data_02_backup, 或 data_03_backup 。
你可以这样使用 fnmatch.fnmatch() :
import os
import fnmatch
for f_name in os.listdir('some_directory'):
if fnmatch.fnmatch(f_name, 'data_*_backup.txt'):
print(f_name)
这里就仅仅打印出匹配 data_*_backup.txt 模式的文件名称。模式中的 * 将匹配任何字符,因此运行这段代码则将查找文件名以 data 开头并以 backup.txt 的所有文本文件,就行下面的输出所示 :
data_01_backup.txt
data_02_backup.txt
data_03_backup.txt
使用 glob 进行文件名模式匹配
另一个有用的模式匹配模块是 glob 。
.glob() 在 glob 模块中的左右就像 fnmatch.fnmatch(),但是与 fnmach.fnmatch() 不同的是,它将以 . 开头的文件视为特殊文件。
UNIX和相关系统在文件列表中使用通配符像 ? 和 * 表示全匹配。
例如,在UNIX shell中使用 mv .py python_files 移动所有 .py 扩展名 的文件从当前目录到 python_files 。这 * 是一个通配符表示任意数量的字符,.py 是一个全模式。Windows操作系统中不提供此shell功能。但 glob 模块在Python中添加了此功能,使得Windows程序可以使用这个特性。
这里有一个使用 glob 模块在当前目录下查询所有Python代码文件:
import glob
print(glob.glob('*.py'))
glob.glob(‘*.py’) 搜索当前目录中具有 .py 扩展名的文件,并且将它们以列表的形式返回。 glob 还支持 shell 样式的通配符来进行匹配 :
import glob
for name in glob.glob('*[0-9]*.txt'):
print(name)
这将找到所有文件名中包含数字的文本文件(.txt) :
data_01.txt
data_01_backup.txt
data_02.txt
data_02_backup.txt
data_03.txt
data_03_backup.txt
glob 也很容易在子目录中递归的搜索文件:
import glob
for name in glob.iglob('**/*.py', recursive=True):
print(name)
这里例子使用了 glob.iglob() 在当前目录和子目录中搜索所有的 .py 文件。传递 recursive=True 作为 .iglob() 的参数使其搜索当前目录和子目录中的 .py 文件。glob.glob() 和 glob.iglob() 不同之处在于,iglob() 返回一个迭代器而不是一个列表。
运行上述代码会得到以下结果:
admin.py
tests.py
sub_dir/file1.py
sub_dir/file2.py
pathlib 也包含类似的方法来灵活的获取文件列表。下面的例子展示了你可以使用 .Path.glob() 列出以字母 p 开始的文件类型的文件列表。
from pathlib import Path
p = Path('.')
for name in p.glob('*.p*'):
print(name)
调用 p.glob(‘.p‘) 会返回一个指向当前目录中所有扩展名以字母 p 开头的文件的生成器对象。
Path.glob() 和上面讨论过的 os.glob() 类似。正如你看到的, pathlib 混合了许多 os , os.path 和 glob 模块的最佳特性到一个模块中,这使得使用起来很方便。
回顾一下,这是我们在本节中介绍的功能表:
| 函数 | 描述 | | ———————————- | ———————————————————- | | startswith() | 测试一个字符串是否以一个特定的模式开始,返回 True 或 False | | endswith() | 测试一个字符串是否以一个特定的模式结束,返回 True 或 False | | fnmatch.fnmatch(filename, pattern) | 测试文件名是否匹配这个模式,返回 True 或 False | | glob.glob() | 返回一个匹配该模式的文件名列表 | | pathlib.Path.glob() | 返回一个匹配该模式的生成器对象 |
* * *
#### 遍历目录和处理文件
一个常见的编程任务是遍历目录树并处理目录树中的文件。让我们来探讨一下如何使用内置的Python函数 os.walk() 来实现这一功能。os.walk() 用于通过从上到下或从下到上遍历树来生成目录树中的文件名。处于本节的目的,我们想操作以下的目录树:
├── folder_1
│ ├── file1.py
│ ├── file2.py
│ └── file3.py
├── folder_2
│ ├── file4.py
│ ├── file5.py
│ └── file6.py
├── test1.txt
└── test2.txt
以下是一个示例,演示如何使用 os.walk() 列出目录树中的所有文件和目录。
os.walk() 默认是从上到下遍历目录:
python import os
for dirpath, dirname, files in os.walk('.'):
print(f'Found directory: {dirpath}')
for file_name in files:
print(file_name)
os.walk() 在每个循环中返回三个值:
1. 当前文件夹的名称
2. 当前文件夹中子文件夹的列表
3. 当前文件夹中文件的列表
在每次迭代中,会打印出它找到的子目录和文件的名称:
Found directory: .
test1.txt
test2.txt
Found directory: ./folder_1
file1.py
file3.py
file2.py
Found directory: ./folder_2
file4.py
file5.py
file6.py
要以自下而上的方式遍历目录树,则将 topdown=False 关键字参数传递给 os.walk() :
for dirpath, dirnames, files in os.walk('.', topdown=False):
print(f'Found directory: {dirpath}')
for file_name in files:
print(file_name)
传递 topdown=False 参数将使 os.walk() 首先打印出它在子目录中找到的文件:
Found directory: ./folder_1
file1.py
file3.py
file2.py
Found directory: ./folder_2
file4.py
file5.py
file6.py
Found directory: .
test1.txt
test2.txt
如你看见的,程序在列出根目录的内容之前列出子目录的内容。 这在在你想要递归删除文件和目录的情况下非常有用。 你将在以下部分中学习如何执行此操作。 默认情况下,os.walk 不会访问通过软连接创建的目录。 可以通过使用 followlinks = True 参数来覆盖默认行为。
创建临时文件和目录
Python提供了 tempfile 模块来便捷的创建临时文件和目录。
tempfile 可以在你程序运行时打开并存储临时的数据在文件或目录中。 tempfile 会在你程序停止运行后删除这些临时文件。
现在,让我们看看如何创建一个临时文件:
from tempfile import TemporaryFile
# 创建一个临时文件并为其写入一些数据
fp = TemporaryFile('w+t')
fp.write('Hello World!')
# 回到开始,从文件中读取数据
fp.seek(0)
data = fp.read()
print(data)
# 关闭文件,之后他将会被删除
fp.close()
第一步是从 tempfile 模块导入 TemporaryFile 。 接下来,使用 TemporaryFile() 方法并传入一个你想打开这个文件的模式来创建一个类似于对象的文件。这将创建并打开一个可用作临时存储区域的文件。
在上面的示例中,模式为 w + t,这使得 tempfile 在写入模式下创建临时文本文件。 没有必要为临时文件提供文件名,因为在脚本运行完毕后它将被销毁。
写入文件后,您可以从中读取并在完成处理后将其关闭。 一旦文件关闭后,将从文件系统中删除。 如果需要命名使用 tempfile 生成的临时文件,请使用 tempfile.NamedTemporaryFile() 。
使用 tempfile 创建的临时文件和目录存储在用于存储临时文件的特殊系统目录中。 Python将在目录列表搜索用户可以在其中创建文件的目录。
在Windows上,目录按顺序为 C:\TEMP,C:\TMP,\TEMP 和 \TMP。 在所有其他平台上,目录按顺序为 / tmp,/var/tmp 和 /usr/tmp 。 如果上述目录中都没有,tempfile 将在当前目录中存储临时文件和目录。
.TemporaryFile() 也是一个上下文管理器,因此它可以与with语句一起使用。 使用上下文管理器会在读取文件后自动关闭和删除文件:
with TemporaryFile('w+t') as fp:
fp.write('Hello universe!')
fp.seek(0)
fp.read()
# 临时文件现在已经被关闭和删除
这将创建一个临时文件并从中读取数据。 一旦读取文件的内容,就会关闭临时文件并从文件系统中删除。
tempfile 也可用于创建临时目录。 让我们看一下如何使用 tempfile.TemporaryDirectory()来做到这一点:
import tempfile
import os
tmp = ''
with tempfile.TemporaryDirectory() as tmpdir:
print('Created temporary directory ', tmpdir)
tmp = tmpdir
print(os.path.exists(tmpdir))
print(tmp)
print(os.path.exists(tmp))
调用 tempfile.TemporaryDirectory() 会在文件系统中创建一个临时目录,并返回一个表示该目录的对象。 在上面的示例中,使用上下文管理器创建目录,目录的名称存储在 tmpdir 变量中。 第三行打印出临时目录的名称,os.path.exists(tmpdir) 来确认目录是否实际在文件系统中创建。
在上下文管理器退出上下文后,临时目录将被删除,并且对 os.path.exists(tmpdir)的调用将返回False,这意味着该目录已成功删除。
删除文件和目录
您可以使用 os,shutil 和 pathlib 模块中的方法删除单个文件,目录和整个目录树。 以下将介绍如何删除你不再需要的文件和目录。
Python中删除文件
要删除单个文件,请使用 pathlib.Path.unlink(),os.remove() 或 os.unlink()。
os.remove() 和 os.unlink() 在语义上是相同的。 要使用 os.remove()删除文件,请执行以下操作:
import os
data_file = 'C:\\Users\\vuyisile\\Desktop\\Test\\data.txt'
os.remove(data_file)
使用 os.unlink() 删除文件与使用 os.remove() 的方式类似:
import os
data_file = 'C:\\Users\\vuyisile\\Desktop\\Test\\data.txt'
os.unlink(data_file)
在文件上调用 .unlink() 或 .remove() 会从文件系统中删除该文件。 如果传递给它们的路径指向目录而不是文件,这两个函数将抛出 OSError 。 为避免这种情况,可以检查你要删除的内容是否是文件,并在确认是文件时执行删除操作,或者可以使用异常处理来处理 OSError :
import os
data_file = 'home/data.txt'
# 如果类型是文件则进行删除
if os.path.is_file(data_file):
os.remove(data_file)
else:
print(f'Error: {data_file} not a valid filename')
os.path.is_file() 检查 data_file 是否实际上是一个文件。 如果是,则通过调用 os.remove() 删除它。 如果 data_file 指向文件夹,则会向控制台输出错误消息。
以下示例说明如何在删除文件时使用异常处理来处理错误:
import os
data_file = 'home/data.txt'
# 使用异常处理
try:
os.remove(data_file)
except OSError as e:
print(f'Error: {data_file} : {e.strerror}')
上面的代码尝试在检查其类型之前先删除该文件。 如果 data_file 实际上不是文件,则抛出的 OSError 将在except子句中处理,并向控制台输出错误消息。 打印出的错误消息使用 Python f-strings 格式化。
最后,你还可以使用 pathlib.Path.unlink() 删除文件:
from pathlib import Path
data_file = Path('home/data.txt')
try:
data_file.unlink()
except IsADirectoryError as e:
print(f'Error: {data_file} : {e.strerror}')
这将创建一个名为 data_file 的 Path 对象,该对象指向一个文件。 在 data_file 上调用.unlink()将删除 home / data.txt 。 如果 data_file 指向目录,则引发 IsADirectoryError 。 值得注意的是,上面的Python程序和运行它的用户具有相同的权限。 如果用户没有删除文件的权限,则会引发 PermissionError 。
删除目录
标准库提供了一下函数来删除目录:
* os.rmdir()
* pathlib.Path.rmdir()
* shutil.rmtree()
要删除单个目录或文件夹可以使用 os.rmdir() 或 pathlib.Path.rmdir() 。这两个函数只在你删除空目录的时候有效。如果目录不为空,则会抛出 OSError 。下面演示如何删除一个文件夹:
import os
trash_dir = 'my_documents/bad_dir'
try:
os.rmdir(trash_dir)
except OSError as e:
print(f'Error: {trash_dir} : {e.strerror}')
现在,trash_dir 已经通过 os.rmdir() 被删除了。如果目录不为空,则会在屏幕上打印错误信息:
Traceback (most recent call last):
File '<stdin>', line 1, in <module>
OSError: [Errno 39] Directory not empty: 'my_documents/bad_dir'
同样,你也可使用 pathlib 来删除目录:
from pathlib import Path
trash_dir = Path('my_documents/bad_dir')
try:
trash_dir.rmdir()
except OSError as e:
print(f'Error: {trash_dir} : {e.strerror}')
这里创建了一个 Path 对象指向要被删除的目录。如果目录为空,调用 Path 对象的 .rmdir() 方法删除它。
删除完整的目录树
要删除非空目录和完整的目录树,Python提供了 shutil.rmtree() :
import shutil
trash_dir = 'my_documents/bad_dir'
try:
shutil.rmtree(trash_dir)
except OSError as e:
print(f'Error: {trash_dir} : {e.strerror}')
当调用 shutil.rmtree() 时,trash_dir 中的所有内容都将被删除。 在某些情况下,你可能希望以递归方式删除空文件夹。 你可以使用上面讨论的方法之一结合 os.walk() 来完成此操作:
import os
for dirpath, dirnames, files in os.walk('.', topdown=False):
try:
os.rmdir(dirpath)
except OSError as ex:
pass
这将遍历目录树并尝试删除它找到的每个目录。 如果目录不为空,则引发OSError并跳过该目录。 下表列出了本节中涉及的功能:
| 函数 | 描述 | | ——————— | ———————————— | | os.remove() | 删除单个文件,不能删除目录 | | os.unlink() | 和os.remove()一样,职能删除单个文件 | | pathlib.Path.unlink() | 删除单个文件,不能删除目录 | | os.rmdir() | 删除一个空目录 | | pathlib.Path.rmdir() | 删除一个空目录 | | shutil.rmtree() | 删除完整的目录树,可用于删除非空目录 |
复制、移动和重命名文件和目录
Python附带了 shutil 模块。 shutil 是shell实用程序的缩写。 它为文件提供了许多高级操作,来支持文件和目录的复制,归档和删除。 在本节中,你将学习如何移动和复制文件和目录。
复制文件
shutil 提供了一些复制文件的函数。 最常用的函数是 shutil.copy() 和 shutil.copy2() 。 使用shutil.copy() 将文件从一个位置复制到另一个位置,请执行以下操作:
import shutil
src = 'path/to/file.txt'
dst = 'path/to/dest_dir'
shutil.copy(src, dst)
shutil.copy() 与基于UNIX的系统中的 cp 命令相当。 shutil.copy(src,dst) 会将文件 src 复制到 dst 中指定的位置。 如果 dst 是文件,则该文件的内容将替换为 src 的内容。 如果 dst 是目录,则 src 将被复制到该目录中。 shutil.copy() 仅复制文件的内容和文件的权限。 其他元数据(如文件的创建和修改时间)不会保留。
要在复制时保留所有文件元数据,请使用 shutil.copy2() :
import shutil
src = 'path/to/file.txt'
dst = 'path/to/dest_dir'
shutil.copy2(src, dst)
使用 .copy2() 保留有关文件的详细信息,例如上次访问时间,权限位,上次修改时间和标志。
复制目录
虽然 shutil.copy() 只复制单个文件,但 shutil.copytree() 将复制整个目录及其中包含的所有内容。 shutil.copytree(src,dest) 接收两个参数:源目录和将文件和文件夹复制到的目标目录。
以下是如何将一个文件夹的内容复制到其他位置的示例:
import shutil
dst = shutil.copytree('data_1', 'data1_backup')
print(dst) # data1_backup
在此示例中,.copytree() 将 data_1 的内容复制到新位置 data1_backup 并返回目标目录。 目标目录不能是已存在的。 它将被创建而不带有其父目录。 shutil.copytree() 是备份文件的一个好方法。
移动文件和目录
要将文件或目录移动到其他位置,请使用 shutil.move(src,dst) 。
src 是要移动的文件或目录,dst 是目标:
import shutil
dst = shutil.move('dir_1/', 'backup/')
print(dst) # 'backup'
如果 backup/ 存在,则 shutil.move(‘dir_1/’,’backup/’) 将 dir_1/ 移动到 backup/ 。 如果 backup/ 不存在,则 dir_1/ 将重命名为 backup 。
重命名文件和目录
Python包含用于重命名文件和目录的 os.rename(src,dst):
import os
os.rename('first.zip', 'first_01.zip')
上面的行将 first.zip 重命名为 first_01.zip 。 如果目标路径指向目录,则会抛出 OSError 。
重命名文件或目录的另一种方法是使用 pathlib 模块中的 rename():
from pathlib import Path
data_file = Path('data_01.txt')
data_file.rename('data.txt')
要使用 pathlib 重命名文件,首先要创建一个 pathlib.Path() 对象,该对象包含要替换的文件的路径。 下一步是在路径对象上调用 rename() 并传入你要重命名的文件或目录的新名称。
#### 归档
归档是将多个文件打包成一个文件的便捷方式。 两种最常见的存档类型是ZIP和TAR。 你编写的Python程序可以创建存档文件,读取存档文件和从存档文件中提取数据。 你将在本节中学习如何读取和写入两种压缩格式。
读取ZIP文件
zipfile 模块是一个底层模块,是Python标准库的一部分。 zipfile 具有可以轻松打开和提取ZIP文件的函数。 要读取ZIP文件的内容,首先要做的是创建一个 ZipFile 对象。ZipFile 对象类似于使用 open() 创建的文件对象。ZipFile 也是一个上下文管理器,因此支持with语句:
import zipfile
with zipfile.ZipFile('data.zip', 'r') as zipobj:
pass
这里创建一个 ZipFile 对象,传入ZIP文件的名称并以读取模式下打开。 打开ZIP文件后,可以通过 zipfile 模块提供的函数访问有关存档文件的信息。 上面示例中的 data.zip 存档是从名为 data 的目录创建的,该目录包含总共5个文件和1个子目录:
.
|
├── sub_dir/
| ├── bar.py
| └── foo.py
|
├── file1.py
├── file2.py
└── file3.py
要获取存档文件中的文件列表,请在 ZipFile 对象上调用 namelist() :
import zipfile
with zipfile.ZipFile('data.zip', 'r') as zipobj:
zipobj.namelist()
这会生成一个文件列表:
['file1.py', 'file2.py', 'file3.py', 'sub_dir/', 'sub_dir/bar.py', 'sub_dir/foo.py']
.namelist() 返回存档文件中文件和目录的名称列表。要检索有关存档文件中文件的信息,使用 .getinfo() :
import zipfile
with zipfile.ZipFile('data.zip', 'r') as zipobj:
bar_info = zipobj.getinfo('sub_dir/bar.py')
print(bar_info.file_size)
这将输出:
15277
.getinfo() 返回一个 ZipInfo 对象,该对象存储有关存档文件的单个成员的信息。 要获取有关存档文件中文件的信息,请将其路径作为参数传递给 .getinfo() 。 使用 getinfo() ,你可以检索有关存档文件成员的信息,例如上次修改文件的日期,压缩大小及其完整文件名。 访问 .file_size 将以字节为单位检索文件的原始大小。
以下示例说明如何在Python REPL中检索有关已归档文件的更多详细信息。 假设已导入 zipfile 模块,bar_info 与在前面的示例中创建的对象相同:
>>> bar_info.date_time
(2018, 10, 7, 23, 30, 10)
>>> bar_info.compress_size
2856
>>> bar_info.filename
'sub_dir/bar.py'
bar_info 包含有关 bar.py 的详细信息,例如压缩的大小及其完整路径。
第一行显示了如何检索文件的上次修改日期。 下一行显示了如何在归档后获取文件的大小。 最后一行显示了存档文件中 bar.py 的完整路径。
ZipFile 支持上下文管理器协议,这就是你可以将它与with语句一起使用的原因。 操作完成后会自动关闭 ZipFile 对象。 尝试从已关闭的 ZipFile 对象中打开或提取文件将导致错误。
提取ZIP文件
zipfile 模块允许你通过 .extract() 和 .extractall() 从ZIP文件中提取一个或多个文件。
默认情况下,这些方法将文件提取到当前目录。 它们都采用可选的路径参数,允许指定要将文件提取到的其他指定目录。 如果该目录不存在,则会自动创建该目录。 要从压缩文件中提取文件,请执行以下操作:
>>> import zipfile
>>> import os
>>> os.listdir('.')
['data.zip']
>>> data_zip = zipfile.ZipFile('data.zip', 'r')
>>> # 提取单个文件到当前目录
>>> data_zip.extract('file1.py')
'/home/test/dir1/zip_extract/file1.py'
>>> os.listdir('.')
['file1.py', 'data.zip']
>>> # 提所有文件到指定目录
>>> data_zip.extractall(path='extract_dir/')
>>> os.listdir('.')
['file1.py', 'extract_dir', 'data.zip']
>>> os.listdir('extract_dir')
['file1.py', 'file3.py', 'file2.py', 'sub_dir']
>>> data_zip.close()
第三行代码是对 os.listdir() 的调用,它显示当前目录只有一个文件 data.zip 。
接下来,以读取模式下打开 data.zip 并调用 .extract() 从中提取 file1.py 。 .extract() 返回提取文件的完整文件路径。 由于没有指定路径,.extract() 会将 file1.py 提取到当前目录。
下一行打印一个目录列表,显示当前目录现在包括除原始存档文件之外的存档文件。 之后显示了如何将整个存档提取到指定目录中。.extractall() 创建 extract_dir 并将 data.zip 的内容提取到其中。 最后一行关闭ZIP存档文件。
##### 从加密的文档提取数据
zipfile 支持提取受密码保护的ZIP。 要提取受密码保护的ZIP文件,请将密码作为参数传递给 .extract() 或.extractall() 方法:
>>> import zipfile
>>> with zipfile.ZipFile('secret.zip', 'r') as pwd_zip:
... # 从加密的文档提取数据
... pwd_zip.extractall(path='extract_dir', pwd='Quish3@o')
将以读取模式打开 secret.zip 存档。 密码提供给 .extractall() ,并且压缩文件内容被提取到 extract_dir 。 由于with语句,在完成提取后,存档文件会自动关闭。
创建新的存档文件
要创建新的ZIP存档,请以写入模式(w)打开 ZipFile 对象并添加要归档的文件:
>>> import zipfile
>>> file_list = ['file1.py', 'sub_dir/', 'sub_dir/bar.py', 'sub_dir/foo.py']
>>> with zipfile.ZipFile('new.zip', 'w') as new_zip:
... for name in file_list:
... new_zip.write(name)
在该示例中,new_zip 以写入模式打开,file_list 中的每个文件都添加到存档文件中。 with语句结束后,将关闭 new_zip 。 以写入模式打开ZIP文件会删除压缩文件的内容并创建新存档文件。
要将文件添加到现有的存档文件,请以追加模式打开 ZipFile 对象,然后添加文件:
>>> with zipfile.ZipFile('new.zip', 'a') as new_zip:
... new_zip.write('data.txt')
... new_zip.write('latin.txt')
这里打开在上一个示例中以追加模式创建的 new.zip 存档。 在追加模式下打开 ZipFile 对象允许将新文件添加到ZIP文件而不删除其当前内容。 将文件添加到ZIP文件后,with语句将脱离上下文并关闭ZIP文件。
打开TAR存档文件
TAR文件是像ZIP等未压缩的文件存档。 它们可以使用 gzip,bzip2 和 lzma 压缩方法进行压缩。 TarFile 类允许读取和写入TAR存档。
下面是从存档中读取:
import tarfile
with tarfile.open('example.tar', 'r') as tar_file:
print(tar_file.getnames())
tarfile 对象像大多数类似文件的对象一样打开。 它们有一个 open() 函数,它采用一种模式来确定文件的打开方式。
使用“r”,“w”或“a”模式分别打开未压缩的TAR文件以进行读取,写入和追加。 要打开压缩的TAR文件,请将模式参数传递给 tarfile.open(),其格式为 filemode [:compression] 。 下表列出了可以打开TAR文件的可能模式:
| 模式 | 行为 | | —– | —————————– | | r | 以无压缩的读取模式打开存档 | | r:gz | 以gzip压缩的读取模式打开存档 | | r:bz2 | 以bzip2压缩的读取模式打开存档 | | w | 以无压缩的写入模式打开存档 | | w:gz | 以gzip压缩的写入模式打开存档 | | w:xz | 以lzma压缩的写入模式打开存档 | | a | 以无压缩的追加模式打开存档 |
.open() 默认为’r’模式。 要读取未压缩的TAR文件并检索其中的文件名,请使用 .getnames() :
>>> import tarfile
>>> tar = tarfile.open('example.tar', mode='r')
>>> tar.getnames()
['CONTRIBUTING.rst', 'README.md', 'app.py']
这以列表的方式返回存档中内容的名字。
注意:为了向你展示如何使用不同的tarfile对象方法,示例中的TAR文件在交互式REPL会话中手动打开和关闭。
通过这种方式与TAR文件交互,你可以查看运行每个命令的输出。 通常,你可能希望使用上下文管理器来打开类似文件的对象。
此外可以使用特殊属性访问存档中每个条目的元数据:
>>> for entry in tar.getmembers():
... print(entry.name)
... print(' Modified:', time.ctime(entry.mtime))
... print(' Size :', entry.size, 'bytes')
... print()
CONTRIBUTING.rst
Modified: Sat Nov 1 09:09:51 2018
Size : 402 bytes
README.md
Modified: Sat Nov 3 07:29:40 2018
Size : 5426 bytes
app.py
Modified: Sat Nov 3 07:29:13 2018
Size : 6218 bytes
在此示例中,循环遍历 .getmembers() 返回的文件列表,并打印出每个文件的属性。.getmembers() 返回的对象具有可以通过编程方式访问的属性,例如归档中每个文件的名称,大小和上次修改时间。 在读取或写入存档后,必须关闭它以释放系统资源。
从TAR存档中提取文件
在本节中,你将学习如何使用以下方法从TAR存档中提取文件:
* .extract()
* .extractfile()
* .extractall()
要从TAR存档中提取单个文件,请使用 extract() ,传入文件名:
>>> tar.extract('README.md')
>>> os.listdir('.')
['README.md', 'example.tar']
README.md 文件从存档中提取到文件系统。 调用 os.listdir() 确认 README.md 文件已成功提取到当前目录中。 要从存档中解压缩或提取所有内容,请使用 .extractall() :
>>> tar.extractall(path="extracted/")
.extractall() 有一个可选的 path 参数来指定解压缩文件的去向。 这里,存档被提取到 extracted 目录中。 以下命令显示已成功提取存档:
$ ls
example.tar extracted README.md
$ tree
.
├── example.tar
├── extracted
| ├── app.py
| ├── CONTRIBUTING.rst
| └── README.md
└── README.md
1 directory, 5 files
$ ls extracted/
app.py CONTRIBUTING.rst README.md
要提取文件对象以进行读取或写入,请使用 .extractfile() ,它接收 文件名或 TarInfo 对象作为参数。 .extractfile() 返回一个可以读取和使用的类文件对象:
>>> f = tar.extractfile('app.py')
>>> f.read()
>>> tar.close()
打开的存档应在读取或写入后始终关闭。 要关闭存档,请在存档文件句柄上调用 .close() ,或在创建 tarfile对象时使用with语句,以便在完成后自动关闭存档。 这将释放系统资源,并将你对存档所做的任何更改写入文件系统。
创建新的TAR存档
创建新的TAR存档,你可以这样操作:
>>> import tarfile
>>> file_list = ['app.py', 'config.py', 'CONTRIBUTORS.md', 'tests.py']
>>> with tarfile.open('packages.tar', mode='w') as tar:
... for file in file_list:
... tar.add(file)
>>> # Read the contents of the newly created archive
>>> with tarfile.open('package.tar', mode='r') as t:
... for member in t.getmembers():
... print(member.name)
app.py
config.py
CONTRIBUTORS.md
tests.py
首先,你要创建要添加到存档的文件列表,这样你就不必手动添加每个文件。
下一行使用with光线文管理器在写入模式下打开名为 packages.tar 的新存档。 以写入模式(’w’)打开存档使你可以将新文件写入存档。 将删除存档中的所有现有文件,并创建新存档。
创建并填充存档后,with上下文管理器会自动关闭它并将其保存到文件系统。 最后三行打开刚刚创建的存档,并打印出其中包含的文件的名称。
要将新文件添加到现有存档,请以追加模式(’a’)打开存档:
>>> with tarfile.open('package.tar', mode='a') as tar:
... tar.add('foo.bar')
>>> with tarfile.open('package.tar', mode='r') as tar:
... for member in tar.getmembers():
... print(member.name)
app.py
config.py
CONTRIBUTORS.md
tests.py
foo.bar
在追加模式下打开存档允许你向其添加新文件而不删除其中已存在的文件。
使用压缩存档
tarfile 可以读取和写入使用 gzip,bzip2 和 lzma 压缩的TAR存档文件。 要读取或写入压缩存档,请使用tarfile.open() ,为压缩类型传递适当的模式。
例如,要读取或写入使用 gzip 压缩的TAR存档的数据,请分别使用 ‘r:gz’ 或 ‘w:gz’ 模式:
>>> files = ['app.py', 'config.py', 'tests.py']
>>> with tarfile.open('packages.tar.gz', mode='w:gz') as tar:
... tar.add('app.py')
... tar.add('config.py')
... tar.add('tests.py')
>>> with tarfile.open('packages.tar.gz', mode='r:gz') as t:
... for member in t.getmembers():
... print(member.name)
app.py
config.py
tests.py
‘w:gz’ 以写模式模式打开 gzip 压缩的存档,’r:gz’ 以读模式打开 gzip 压缩的存档。 无法在追加模式下打开压缩存档。 要将文件添加到压缩存档,你必须创建新存档。
一个更简单的方式创建存档
Python标准库还支持使用 shutil 模块中的高级方法创建TAR和ZIP存档。 shutil 中的归档实用工具允许你创建,读取和提取ZIP和TAR归档。 这些实用工具依赖于较底层的 tarfile 和 zipfile 模块。
使用 shutil.make_archive() 创建存档
shutil.make_archive() 至少接收两个参数:归档的名称和归档格式。
默认情况下,它将当前目录中的所有文件压缩为 format 参数中指定的归档格式。 你可以传入可选的 root_dir 参数来压缩不同目录中的文件。 .make_archive() 支持 zip ,tar ,bztar 和 gztar 存档格式。
以下是使用 shutil 创建TAR存档的方法:
import shutil
# shutil.make_archive(base_name, format, root_dir)
shutil.make_archive('data/backup', 'tar', 'data/')
这将复制 data / 中的所有内容,并在文件系统中创建名为 backup.tar 的存档并返回其名称。 要提取存档,请调用 .unpack_archive() :
shutil.unpack_archive('backup.tar', 'extract_dir/')
调用 .unpack_archive() 并传入存档名称和目标目录,将 backup.tar 的内容提取到 extract_dir/ 中。 ZIP存档可以以相同的方式创建和提取。
读取多个文件
Python支持通过 fileinput 模块从多个输入流或文件列表中读取数据。 此模块允许你快速轻松地循环遍历一个或多个文本文件的内容。 以下是使用 fileinput 的典型方法:
import fileinput
for line in fileinput.input()
process(line)
fileinput 默认从传递给 sys.argv 的命令行参数获取其输入。
使用 fileinput 循环遍历多个文件
让我们使用 fileinput 构建一个普通的UNIX工具 cat 的原始版本。 cat 工具按顺序读取文件,将它们写入标准输出。 当在命令行参数中给出多个文件时,cat 将连接文本文件并在终端中显示结果:
# File: fileinput-example.py
import fileinput
import sys
files = fileinput.input()
for line in files:
if fileinput.isfirstline():
print(f'\n--- Reading {fileinput.filename()} ---')
print(' -> ' + line, end='')
print()
在当前目录中有两个文本文件,运行此命令会产生以下输出:
$ python3 fileinput-example.py bacon.txt cupcake.txt
--- Reading bacon.txt ---
-> Spicy jalapeno bacon ipsum dolor amet in in aute est qui enim aliquip,
-> irure cillum drumstick elit.
-> Doner jowl shank ea exercitation landjaeger incididunt ut porchetta.
-> Tenderloin bacon aliquip cupidatat chicken chuck quis anim et swine.
-> Tri-tip doner kevin cillum ham veniam cow hamburger.
-> Turkey pork loin cupidatat filet mignon capicola brisket cupim ad in.
-> Ball tip dolor do magna laboris nisi pancetta nostrud doner.
--- Reading cupcake.txt ---
-> Cupcake ipsum dolor sit amet candy I love cheesecake fruitcake.
-> Topping muffin cotton candy.
-> Gummies macaroon jujubes jelly beans marzipan.
fileinput 允许你检索有关每一行的更多信息,例如它是否是第一行(.isfirstline()),行号(.lineno())和文件名(.filename())。 你可以在 这里 读更多关于它的内容。
总结
你现在知道如何使用Python对文件和文件组执行最常见的操作。 你已经了解使用不同的内置模块来读取,查找和操作文件。
你现在可以用Python来实现:
* 获取目录内容和文件属性
* 创建目录和目录树
* 使用匹配模式匹配文件名
* 创建临时文件和目录
* 移动,重命名,复制和删除文件或目录
* 从不同类型的存档文件中读取和提取数据
* 使用 _fileinput_ 同时读取多个文件