我需要遍历文件服务器上的所有文件,并且我希望能够在文件树中的任何位置停止该过程并在以后恢复它。这可以用 os.walk 完成,还是需要从头开始实现?
编辑:理想情况下,我希望解决方案是持久的,因此脚本可以停止并稍后恢复。
我需要遍历文件服务器上的所有文件,并且我希望能够在文件树中的任何位置停止该过程并在以后恢复它。这可以用 os.walk 完成,还是需要从头开始实现?
编辑:理想情况下,我希望解决方案是持久的,因此脚本可以停止并稍后恢复。
os.walk
是一个完全正常的生成器函数,这意味着您可以调用它,保存生成的生成器,然后在闲暇时对其进行迭代。例如:
w = os.walk(root)
for root, dirs, files in w:
if root == 'foo':
break
else:
# usual stuff
print('Hey, we found foo')
for root, dirs, files in w:
# usual stuff
你甚至w
可以返回一个函数,或者从一个函数中返回它,或者将它用作你最喜欢的itertools
函数的迭代器,等等。
你不能用它做的一件大事就是腌制它。所以,如果你想把它持久化到磁盘(或数据库),这样你就可以退出程序并从你离开的地方继续,或者将它发送到一个子进程来完成,或者其他什么,你不能这样做.
如果您有能力提前完成整个步行而不是懒惰地进行(即,您不需要动态修剪步行,并且步行本身的时间和存储与所需的时间和存储相形见绌你真正的工作),你可以坚持下去list(w)
。然后,您只需要跟踪该列表以及您到目前为止所获得的索引(或者只是坚持wlist[index:]
而不是wlist
and index
)。但对于某些用例,这是不可接受的。
幸运的是,walk
它是用纯 Python 实现的,而且非常简单,因此您可以从源代码中复制代码并对其进行修改以使状态可持久化。问题是状态是部分隐含的,通过 的魔法yield
,所以您必须将生成器从里到外翻转,或者将其转换为等效的纯迭代解决方案。这是一个开始:
class Walk(object):
def __init__(self, top):
self.queue = [top]
def __iter__(self):
return self
def __next__(self):
top = self.queue.pop(0)
names = os.listdir(top)
dirs, nondirs = [], []
for name in names:
if os.path.isdir(os.path.join(top, name)):
dirs.append(name)
else:
nondirs.append(name)
self.queue.extend(os.path.join(top, dir) for dir in dirs)
return top, dirs, nondirs
这不处理任何可选参数。followlinks=False
并且onerror
是微不足道的。处理动态剪枝topdown=True
并不复杂(只需存储top
和dirs
,并在下一次调用开始时将子代入队,而不是在本次调用结束时)。这样做topdown=False
会有点痛苦,但仍然不算太糟糕(您可以为标准的递归到迭代转换创建一个显式的状态堆栈,或者保留一个额外的双端队列,或者只是创建、存储和迭代一个列表新Walk
对象)。如果您不需要它们,请不要费心添加它们。
我相信这pickle
不会改变。(如果不是,它要么是微不足道的,要么是微不足道__getstate__
的__reduce__
。)如果你使用不同的持久性机制,真的,你需要坚持的是这是一个Walk
对象,它queue
是self.queue
(这只是一个列表字符串),所以这应该很容易。
最后,为了解决同样的问题,我删除了 python 的 os.walk 的内部结构,并在处理目录之前和之后放置了一些包装器。
包装器与 sqlite 数据库对话,并回忆他们已经看到的目录。这使得它可以尽可能积极地修剪树以完成已经完成的工作。
注意:对于我的用例,重新处理给定目录中的文件不是问题,如果您需要严格重新输入 os.walk 准确地从您离开的地方,那么您也需要跟踪文件 - 不要不要忘记,一旦你完成了一个目录,你可以清除文件信息以节省数据库大小。
import os
import sys
import sqlite3
class WalkPersister():
def __init__(self, db_filename, onerror=None, followlinks=False):
self._db_filename = db_filename
self._onerror = onerror
self._followlinks = followlinks
def __enter__(self):
self._con = sqlite3.connect(self._db_filename)
self._con.execute('''
CREATE TABLE IF NOT EXISTS walk_persistence (
path text PRIMARY KEY,
files int,
total_sub_files int
)
''')
return self
def __exit__(self, exception_type, exception_value, traceback):
self._con.commit()
def walk(self, top, onerror=None, followlinks=False):
# Almost entirely from https://github.com/python/cpython/blob/3.9/Lib/os.py#L344
if not self._pre_visit(top):
return
dirs = []
nondirs = []
walk_dirs = []
try:
scandir_it = os.scandir(top)
except OSError as error:
if onerror is not None:
onerror(error)
return
with scandir_it:
while True:
try:
try:
entry = next(scandir_it)
except StopIteration:
break
except OSError as error:
if onerror is not None:
onerror(error)
return
try:
is_dir = entry.is_dir()
except OSError:
# If is_dir() raises an OSError, consider that the entry is not
# a directory, same behaviour than os.path.isdir().
is_dir = False
if is_dir:
dirs.append(entry.name)
else:
nondirs.append(entry.name)
yield top, dirs, nondirs
# Recurse into sub-directories
islink, join = os.path.islink, os.path.join
for dirname in dirs:
new_path = join(top, dirname)
if followlinks or not islink(new_path):
yield from self.walk(new_path, onerror, followlinks)
self._post_visit(top, dirs, nondirs)
def _pre_visit(self, path):
cursor = self._con.execute('''
SELECT
path
FROM
walk_persistence
WHERE
path = ?
''', (path, ))
return cursor.fetchone() is None
def _post_visit(self, path, dirs, nondirs):
cursor = self._con.execute('''
SELECT
files
FROM
walk_persistence
WHERE
path LIKE ? || '%'
''', (path, ))
sub_files = sum((i[0] for i in cursor.fetchall())) + len(nondirs)
self._con.execute('''
INSERT OR REPLACE INTO
walk_persistence
VALUES (
?, ?, ?
)
''', (path, len(nondirs), sub_files))
用法:
import pathlib
import random
with WalkPersister('/home/appuser/walk.db') as walker:
for src_dname, sdirs, files in walker.walk('/data/'):
src_dname = pathlib.Path(src_dname)
for file in files:
filepath = src_dname/file
print(filepath)
if random.randint(0, 5000) == 13: raise ValueError('Demo Exception')