Python Learning #08 - Directory Operations

文件夹操作

2020-01-04 Mike Lyou

本文为个人Notes,原文为 《Python文件操作,看这篇就足够》,其翻译原文为 Working With Files in Python。本文不允许转载。

(有时间的话写成英文版本,熟悉一下英文术语)

目录:

前往指定目录

使用os包的chdir函数能够改变当前工作路径(待验证)

1
2
3
4
5
6
7
import os


os.getcwd()  #获取当前工作目录

os.chdir('d:\\')  #更改当前工作目录
os.getcwd()

获取目录列表

假设你当前的工作目录有一个叫 my_directory 的子目录,该目录包含如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
.
├── 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版本获取目录列表

在现代Python版本中,可以使用 os.scandir()pathlib.Path 来替代旧版本中的 os.listdir()

os.scandir() 调用时返回一个迭代器而不是一个列表。其(小火的理解)ScandirIterator)指向了当前目录中的所有条目。你可以遍历迭代器的内容,并打印文件名。

1
2
3
4
import os
with os.scandir('my_directory') as entries:
    for entry in entries:
        print(entry.name)

运行结果为

1
2
3
4
5
6
file1.py
file2.csv
file3.txt
sub_dir
sub_dir_b
sub_dir_c

另一个获取目录列表的方法是使用 pathlib 模块:前往原文 →

列出目录中所有文件

如果不希望列出目录,而仅列出文件,要使用 os.path

1
2
3
4
5
6
7
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() 过滤列表让其只显示文件类型而非目录类型。代码执行结果如下:

1
2
3
file1.py
file2.csv
file3.txt

一个更简单的方式来列出一个目录中所有的文件是使用 os.scandir()pathlib.Path() :

1
2
3
4
5
6
7
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 则表示这一项是一个文件。上述代码的输出如下:

1
2
3
file1.py
file2.csv
file3.txt

如何使用 pathlib.Path() 列出一个目录中的文件:前往原文 →

列出所有子目录

与上面讨论的相反,如果希望列出子目录,而不列出文件:前往原文 →

获取文件属性

Python可以很轻松的获取文件大小和修改时间等文件属性。

os.scandir()pathlib.Path() 能直接获取到包含文件属性的目录列表。这可能比使用 os.listdir() 列出文件然后获取每个文件的文件属性信息更加有效。

下面的例子显示了如何获取 my_directory 中文件的最后修改时间。以时间戳的方式输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 模块具有相应的方法。略。

创建目录

暂时跳过:原文 →

创建单个目录

创建多个目录

文件名模式匹配

使用上述方法之一获取目录中的文件列表后,你可能希望搜索和特定的模式匹配的文件。

本小节的示例将在名为 some_directory 的目录下执行,该目录具有以下的结构:

1
2
3
4
5
6
7
8
9
10
11
12
.
├── 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,你可以使用以下的命令创建上述目录结构。(跳过)

字符串方法匹配

Python有几个内置 修改和操作字符串 的方法。当在匹配文件名时,其中的两个方法 .startswith().endswith() 非常有用。要做到这点,首先要获取一个目录列表,然后遍历。

1
2
3
4
5
import os

for f_name in os.listdir('some_directory'):
    if f_name.endswith('.txt'):
        print(f_name)

上述代码找到 some_directory 中的所有文件,遍历并使用 .endswith() 来打印所有扩展名为 .txt 的文件名。运行代码在我的电脑上输出如下:

1
2
3
4
5
6
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 文件,你可以这样做:

1
2
3
4
5
6
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 的文件执行通配符搜索。

  • 据我查阅的资料,*匹配任意多个字符,而?匹配任意单个字符。此处字符为 a~zA~Z0~9共 36 个字符。

  • fnmatch() 函数匹配能力介于简单的字符串方法和强大的正则表达式之间。 如果在数据处理操作中只需要简单的通配符就能完成的时候,这通常是一个比较合理的方案。

更复杂地,如果想要搜索类似于 data_01_backup这类文件,你可以这样使用 fnmatch.fnmatch()

1
2
3
4
5
6
import os
import fnmatch

for f_name in os.listdir('some_directory'):
    if fnmatch.fnmatch(f_name, 'data_*_backup.txt'):
        print(f_name)

输出如下 :

1
2
3
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 代码文件:

1
2
3
import glob

print(glob.glob('*.py'))

glob.glob('*.py') 搜索当前目录中具有 .py 扩展名的文件,并且将它们以列表的形式返回。 glob 还支持 shell 样式的通配符来进行匹配 :

1
2
3
4
import glob

for name in glob.glob('*[0-9]*.txt'):
    print(name)

这将找到所有文件名中包含数字的文本文件(.txt) :

1
2
3
4
5
6
data_01.txt
data_01_backup.txt
data_02.txt
data_02_backup.txt
data_03.txt
data_03_backup.txt

glob 也很容易在子目录中递归的搜索文件:

1
2
3
4
import glob

for name in glob.iglob('**/*.py', recursive=True):
    print(name)

这里例子使用了 glob.iglob() 在当前目录和子目录中搜索所有的 .py 文件。传递 recursive=True 作为 .iglob() 的参数使其搜索当前目录和子目录中的 .py 文件。glob.glob()glob.iglob() 不同之处在于,iglob() 返回一个迭代器而不是一个列表。

运行上述代码会得到以下结果:

1
2
3
4
admin.py
tests.py
sub_dir/file1.py
sub_dir/file2.py

pathlib 也包含类似的方法来灵活的获取文件列表。(略)

遍历目录和处理文件

一个常见的编程任务是遍历目录树并处理目录树中的文件。让我们来探讨一下如何使用内置的 Python 函数 os.walk() 来实现这一功能。os.walk() 用于通过从上到下或从下到上遍历树来生成目录树中的文件名。处于本节的目的,我们想操作以下的目录树:

1
2
3
4
5
6
7
8
9
10
├── folder_1
│   ├── file1.py
│   ├── file2.py
│   └── file3.py
├── folder_2
│   ├── file4.py
│   ├── file5.py
│   └── file6.py
├── test1.txt
└── test2.txt

以下是一个示例,演示如何使用 os.walk() 列出目录树中的所有文件和目录。

os.walk() 默认是从上到下遍历目录:

1
2
3
4
5
6
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
4
5
6
7
8
9
10
11
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()

1
2
3
4
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() 首先打印出它在子目录中找到的文件:

1
2
3
4
5
6
7
8
9
10
11
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 参数来覆盖默认行为。(听不懂)

创建临时文件和目录

跳过。

删除文件和目录

跳过。

复制、移动和重命名文件和目录

跳过。

归档

归档是将多个文件打包成一个文件的便捷方式。 两种最常见的存档类型是ZIP和TAR。 你编写的Python程序可以创建存档文件,读取存档文件和从存档文件中提取数据。跳过。

References



Author: Mike Lyou
Link: https://blog.mikelyou.com/2020/01/04/python-learning-08-directory-operations/
Liscense: Copyright Statement: This python learning series are posted only for personal studies and communication. The whole series are not allowed to be reproduced unles otherwise indicated. I do not own copyright of some of the content. If any post accidentally infringes your copyright, it will be removed shortly after being informed. View python-learning-readme for more information.