有没有办法有效地产生包含数百万个文件的目录中的每个文件?

时间:2011-02-23 11:44:31

标签: python list file yield

我知道os.listdir,但据我所知,它将目录中的所有文件名都存入内存,然后返回列表。我想要的是一种产生文件名的方法,对它进行处理,然后产生下一个文件名,而不是将它们全部读入内存。

有没有办法做到这一点?我担心文件名更改,添加新文件以及使用此类方法删除文件的情况。一些迭代器阻止您在迭代期间修改集合,主要是通过在开始时获取集合状态的快照,并在每个move操作上比较该状态。如果有一个迭代器能够从路径中产生文件名,那么如果有文件系统更改(添加,删除,重命名迭代目录中的文件)修改集合会引发错误吗?

可能有一些情况可能导致迭代器失败,这一切都取决于迭代器如何维持状态。使用S.Lotts示例:

filea.txt
fileb.txt
filec.txt

迭代器产生filea.txt。在processing期间,filea.txt重命名为filey.txtfileb.txt重命名为filez.txt。当迭代器尝试获取下一个文件时,如果要使用文件名filea.txt找到它的当前位置以便找到下一个文件并且filea.txt不存在,会发生什么?它可能无法恢复它在集合中的位置。同样,如果迭代器在产生fileb.txt时要获取filea.txt,它可能会查找fileb.txt的位置,失败并产生错误。

如果迭代器能够以某种方式维护索引dir.get_file(0),那么维护位置状态不会受到影响,但是某些文件可能会被遗漏,因为它们的索引可以被移动到迭代器“后面”的索引

这当然是理论上的,因为似乎没有内置(python)方法来迭代目录中的文件。但是,下面有一些很好的答案可以通过使用队列和通知来解决问题。

编辑:

关注的操作系统是Redhat。我的用例是:

进程A不断将文件写入存储位置。 进程B(我正在写的那个)将迭代这些文件,根据文件名进行一些处理,并将文件移动到另一个位置。

编辑:

有效的定义:

形容词 1.良好的基础或合理的,相关的。

(对不起S.Lott,我无法抗拒)。

我已经编辑了上面的段落。

6 个答案:

答案 0 :(得分:13)

tl; dr< update>:从Python 3.5开始(目前处于测试阶段),只需使用os.scandir < /更新>

正如我之前所写,由于“iglob”只是一个真正的迭代器的外观,你必须调用低级系统函数,以便一次一个地得到一个。 Fortyuantelly,这可以从Python实现。 如果没有告诉你,你是在Posix(Linux / mac OS X /其他Unix)或Windows系统上。在后一种情况下,您应该检查win32api是否有任何调用来读取“dir的下一个条目”或者如何继续进行。

在前一种情况下,您可以直接通过ctypes调用libc函数并获取一个文件目录条目,包括命名信息)。

关于C函数的文档在这里: http://www.gnu.org/s/libc/manual/html_node/Opening-a-Directory.html#Opening-a-Directory

http://www.gnu.org/s/libc/manual/html_node/Reading_002fClosing-Directory.html#Reading_002fClosing-Directory

不幸的是,“dirent64”C结构是在每个系统的C编译时确定的 - 我已经在我的系统上想到了这一点,而且在大多数情况下,它就像我把它放在Python下面的片段中 - 但是你可能会想要知道你的“dirent.h”以及/ usr / includes中包含的其他文件。

以下是使用ctypes和libC的片段,我将它放在一起,允许您获取每个文件名,并对其执行操作。请注意,当您对结构上定义的char数组执行str(...)时,ctypes会自动为您提供Python字符串。 (我正在使用print语句,它隐式调用Python的str)

from ctypes import *
libc = cdll.LoadLibrary( "libc.so.6")
 dir_ = c_voidp( libc.opendir("/home/jsbueno"))

class Dirent(Structure):
    _fields_ = [("d_ino",  c_voidp),
                ("off_t", c_int64),
                ("d_reclen", c_ushort),
                ("d_type", c_ubyte),
                ("d_name", c_char * 2048)
            ]

while True:
    p  = libc.readdir64(dir_)
    if not p:
        break
    entry = Dirent.from_address( p)
    print entry.d_name

更新:Python 3.5现在处于测试阶段 - 在此版本中,新的os.scandir函数调用可用作PEP 471的实现(“更好更快的目录iterator“)完全符合这里的要求,除了很多其他优化之外,在Windows下的大目录列表中,os.listdir可以提供高达9倍的速度增加(Posix系统增加2-3倍)。

答案 1 :(得分:9)

从2.5开始的glob模块Python有一个返回迭代器的iglob方法。 迭代器完全是出于不在内存中存储巨大值的目的。

glob.iglob(pathname)
Return an iterator which yields the same values as glob() without
actually storing them all simultaneously.

例如:

import glob
for eachfile in glob.iglob('*'):
    # act upon eachfile

答案 2 :(得分:8)

由于您使用的是Linux,因此您可能需要查看pyinotify。 它允许您编写一个Python脚本来监视目录中的文件系统更改 - 例如创建,修改或删除文件。

每次发生此类文件系统事件时,您都可以安排Python脚本调用函数。这大致就像产生每个文件名一次,同时能够对修改和删除作出反应。

听起来你已经有一百万个文件放在一个目录中了。在这种情况下,如果要将所有这些文件移动到新的pyinotify监视目录,则通过创建新文件生成的文件系统事件将根据需要生成文件名。

答案 3 :(得分:6)

  

我想要的是一种产生文件名的方法,对其进行处理,然后产生下一个文件名,而不将其全部读入内存。

没有方法会显示“已更改”的文件名。甚至不清楚这个“文件名更改,添加新文件,删除文件”是什么意思?你的用例是什么?

假设您有三个文件:a.ab.bc.c

你的神奇“迭代器”以a.a开头。你处理它。

神奇的“迭代器”移动到b.b。你正在处理它。

同时a.a被复制到a1.a1a.a被删除。现在怎么办?你的魔法迭代器对这些做了什么?它已经通过了a.a。由于a1.a1b.b之前,它永远不会看到它。 “文件名更改,添加新文件,删除文件”应该会发生什么?

神奇的“迭代器”移动到c.c。应该发生在其他文件上的是什么?你怎么知道删除的?


  

进程A不断将文件写入存储位置。进程B(我正在写的那个)将迭代这些文件,根据文件名进行一些处理,并将文件移动到另一个位置。

不要使用裸文件系统进行协调。

使用队列。

进程A写入文件并将添加/更改/删除纪念品排入队列。

进程B从队列中读取纪念品,然后对纪念品中指定的文件进行后续处理。

答案 4 :(得分:6)

@jsbueno的帖子非常有用,但在慢速磁盘上仍然有点慢,因为libc readdir()一次只能准备32K磁盘条目。我不是直接在python中进行系统调用的专家,但我概述了如何在C中编写代码,该代码将列出包含数百万个文件的目录,位于http://www.olark.com/spw/2011/08/you-can-list-a-directory-with-8-million-files-but-not-with-ls/的博客文章中。

理想情况是直接在python( http://www.kernel.org/doc/man-pages/online/pages/man2/getdents.2.html中调用getdents()),这样就可以在从磁盘加载目录条目时指定读取缓冲区大小。

而不是调用readdir(),据我所知,它在编译时定义了一个缓冲区大小。

答案 5 :(得分:1)

我认为由于文件IO的性质,你所要求的是不可能的。一旦python检索到目录列表,它就无法维护磁盘上实际目录的视图,也没有办法让python坚持要求操作系统通知它对目录的任何修改。

所有python可以做的是询问定期列表并对结果进行区分以查看是否有任何更改。

您可以做的最好的事情是在目录中创建一个信号量文件,让其他进程知道您的python进程希望没有其他进程修改目录。当然,如果你明确地将它们编程为它们,它们只会观察信号量。