geekdoc-python-zh/docs/pythoncentral/recursive-file-and-director...

8.4 KiB
Raw Permalink Blame History

Python 中的递归文件和目录操作(第 1 部分)

原文:https://www.pythoncentral.io/recursive-file-and-directory-manipulation-in-python-part-1/

如果您希望利用 Python 来操作系统上的目录树或文件,有许多工具可以提供帮助,包括 Python 的标准操作系统模块。下面是一个简单/基本的方法,可以帮助您通过文件扩展名找到系统中的某些文件。

如果您有在系统中“丢失”文件的经历,您不记得它的位置,甚至不确定它的名称,尽管您记得它的类型,这就是您可能会发现这个方法有用的地方。

在某种程度上,这份食谱结合了 Python 中的如何遍历目录树递归目录遍历:制作你的电影列表!,但我们会对其稍作调整,并在第二部分中对其进行改进。

要编写这个任务的脚本,我们可以使用os.path模块中的walk函数或os模块中的walk函数(分别使用 Python 3.x 版或 Python 3.x 版)。

用 Python 2.x 中的 os.path.walk 进行递归

os.path.walk函数有 3 个参数:

  1. 一个武断的(但却是强制性的)论点。
  2. visit -每次迭代时执行的函数。
  3. top -目录树的顶端行走。

然后遍历顶部的目录树,在每一步执行功能。让我们检查一下函数(我们将其定义为“step”),我们使用它来打印 top 下的文件的路径名,这些文件的文件扩展名可以通过arg提供。

下面是 step 的定义:

def step(ext, dirname, names):
ext = ext.lower()

for name in names:
if name.lower().endswith(ext):
print os.path.join(dirname, name)

现在让我们一行一行地分解它,但首先要指出的是,给 step 的参数是由用户直接通过os.path.walk函数、而不是传递的这一点非常重要。walk 在每次迭代中传递的三个参数是:

  1. ext -赋予os.path.walk的任意自变量。
  2. dirname -该迭代的目录名。
  3. names-dirname下所有文件的名称。

我们的 step 函数的第一行当然是我们的函数声明,包括将由os.path.walk直接传递的默认参数。

第二行确保我们的ext字符串是小写的。第三行开始我们的参数名循环,这是一个列表类型。第四行是我们如何检索带有我们想要的扩展名的文件名,使用字符串方法endswith来测试后缀。

最后一行打印通过后缀(扩展名)测试的任何文件的路径,将dirname参数连接到名称(带有适当的系统相关分隔符)。

现在,将我们的 step 函数与 walk 函数结合后,脚本看起来类似于这样:

# We only need to import this module
import os.path

# The top argument for walk. The
# Python27/Lib/site-packages folder in my case

topdir = '.'

# The arg argument for walk, and subsequently ext for step
exten = '.txt'

def step(ext, dirname, names):
ext = ext.lower()

for name in names:
if name.lower().endswith(ext):
print(os.path.join(dirname, name))

# Start the walk
os.path.walk(topdir, step, exten)

对于我的系统,我在 Python 2.7 的站点包中安装了wx_py,输出如下:


.\README.txt

.\wx-2.8-msw-unicode\docs\CHANGES.txt

.\wx-2.8-msw-unicode\docs\MigrationGuide.txt

.\wx-2.8-msw-unicode\docs\README.win32.txt

......

.\wx-2.8-msw-unicode\wx\tools\XRCed\TODO.txt

用 Python 3.x 中的 os.walk 进行递归

现在让我们用 Python 3.x 做同样的事情。

Python 3.x 中的os.walk函数工作方式不同,提供了比其他函数更多的选项。它需要 4 个参数,只有第一个是强制的。参数(及其默认值)依次为:

top

topdown(=True)

布尔型

onerror(=None)

followlinks(=False)

布尔型

我们现在唯一关心的是第一个。除了参数之外walk 函数的两个版本的最大区别可能是 Python 2.x 版本自动遍历目录树,而 Python 3.x 版本生成一个生成器函数。这意味着 Python 3.x 版本只有在我们告诉它的时候才会进行下一次迭代,我们这样做的方式是通过一个循环。

我们将把os.walk生成器写入进入step函数的循环中,而不是像步骤那样定义一个单独的函数来调用。像 Python 2.x 版本一样,os.walk产生了 3 个值,我们可以在每次迭代中使用(目录路径、目录名和文件名),但是这次它们是三元组的形式,所以我们必须相应地调整我们的方法。除此之外,我们根本不会改变扩展名后缀测试,所以脚本最终看起来像这样:

import os

# The top argument for walk
topdir = '.'

# The extension to search for
exten = '.txt'

for dirpath, dirnames, files in os.walk(topdir):
for name in files:
if name.lower().endswith(exten):
print(os.path.join(dirpath, name))


因为我的系统的 Python32/Lib/site-packages 文件夹不包含任何特殊的内容,所以这个文件夹的输出结果只是:


.\README.txt

无论“topdir”和“exten”字符串被设置为什么这都将以相同的方式工作然而这个脚本只是将文件名打印到窗口(在我们的例子中是 Python 的空闲窗口),如果有许多文件要打印,这会使我们的解释器(或 shell)窗口多行高——滚动起来有点麻烦。如果我们知道是这种情况,那么将结果写入我们可以随时查看的文本文件就容易多了。如果我们像这样加入一个with语句(比如在 Python 中的读写文件),我们可以很容易做到:


with open(logpath, 'a') as logfile:

logfile.write('%s\n' % os.path.join(dirname, name))

让我们先看看如何将它合并到 Python 2.x 版本的脚本中:


# We only need to import this module
import os.path
# The top argument for walk. The
# Python27/Lib/site-packages folder in my case.
topdir = '.'

# The arg argument for walk, and subsequently ext for step
exten = '.txt'

logname = 'findfiletype.log'

def step((ext, logpath), dirname, names):
ext = ext.lower()

for name in names:
if name.lower().endswith(ext):
# Instead of printing, open up the log file for appending
with open(logpath, 'a') as logfile:
logfile.write('%s\n' % os.path.join(dirname, name))

# Change the arg to a tuple containing the file
# extension and the log file name. Start the walk.
os.path.walk(topdir, step, (exten, logname))

正如我们在上面看到的,除了第三个变量logname和第三个参数os.path.walk之外没有什么变化。with 语句已经取代了print语句。由于os.path.walk函数的性质,step需要打开日志文件,写入日志文件,每找到一个文件名就关闭日志文件;这不会导致任何错误,但有点尴尬。我们还必须注意,因为日志文件是在追加模式下打开的,所以它将而不是覆盖已经存在的日志文件,它将只将追加到文件中。这意味着如果我们在不改变logname的情况下连续运行脚本 2 次或更多次,每次运行的结果将被添加到同一个文件中,这可能是不希望的。

修改版 Python 3.x 脚本就没那么别扭了:

import os

# The top argument for walk
topdir = '.'
# The extension to search for
exten = '.txt'
logname = 'findfiletype.log'
# What will be logged
results = str()
or dirpath, dirnames, files in os.walk(topdir):
for name in files:
if name.lower().endswith(exten):
# Save to results string instead of printing
results += '%s\n' % os.path.join(dirpath, name)

# Write results to logfile
with open(logname, 'w') as logfile:
logfile.write(results)

在这个版本中,每个找到的文件的名称被附加到results字符串,然后当搜索结束时,结果被写入日志文件。与 Python 2.x 版本不同,日志文件以模式打开,这意味着任何现有的日志文件都将被覆盖。在这两种情况下,日志文件都将被写入与脚本相同的目录中(因为我们没有指定完整的路径名)。

有了它,我们就有了一个简单的脚本,可以在文件树下找到某个扩展名的文件,并记录这些结果。在接下来的部分中,我们将在此基础上增加搜索多种文件类型、避免特定路径等功能。