将文件夹中的文件作为流列出以便立即处理

8 投票
4 回答
5155 浏览
提问于 2025-04-16 08:19

我有一个文件夹,里面有一百万个文件。

我想要在这个文件夹里列出文件的时候,能够立刻开始处理,而不是等到所有文件都列出来再开始,这样会浪费时间。

像Python里的os.listdir这样的常用函数是阻塞的,也就是说我的程序得等到文件列出完毕才能继续执行,这可能会花费很长时间。

那么,有什么好的方法可以快速列出这么大的文件夹呢?

4 个回答

3

对于从谷歌过来的朋友们,PEP 471 为 Python 3.5 的标准库提供了一个合适的解决方案,并且这个功能也被移植到了 Python 2.6 及以上版本和 3.2 及以上版本,作为 scandir 模块在 PIP 上使用。

来源: https://stackoverflow.com/a/34922054/435253

在 Python 3.5 及以上版本中:

  • os.walk 已经更新,使用了这个新结构,性能更好。
  • os.scandir 返回一个可以遍历 DirEntry 对象的迭代器。

在 Python 2.6/2.7 和 3.2/3.3/3.4 中:

  • scandir.walkos.walk 的一个性能更好的版本。
  • scandir.scandir 返回一个可以遍历 DirEntry 对象的迭代器。

scandir() 迭代器在 POSIX 平台上封装了 opendir/readdir,在 Windows 上封装了 FindFirstFileW/FindNextFileW

返回 DirEntry 对象的目的是为了缓存元数据,以减少系统调用的次数。(例如,DirEntry.stat(follow_symlinks=False) 在 Windows 上从不进行系统调用,因为 FindFirstFileWFindNextFileW 函数会免费提供 stat 信息)

来源: https://docs.python.org/3/library/os.html#os.scandir

3

这看起来有点不太干净,但应该能解决问题:

def listdirx(dirname='.', cmd='ls'):
    proc = subprocess.Popen([cmd, dirname], stdout=subprocess.PIPE)
    filename = proc.stdout.readline()
    while filename != '':
        yield filename.rstrip('\n')
        filename = proc.stdout.readline()
    proc.communicate()

用法:listdirx('/something/with/lots/of/files')

12

如果方便的话,可以考虑改变你的文件夹结构;但如果不方便,你可以使用ctypes来调用opendirreaddir

这里有一份代码的副本;我只是把它缩进整理了一下,添加了try/finally块,并修复了一个错误。你可能需要自己调试一下,特别是结构体的布局部分。

请注意,这段代码是不可移植的。在Windows上你需要使用不同的函数,而且我觉得不同的Unix系统之间结构体的定义也可能会有所不同。

#!/usr/bin/python
"""
An equivalent os.listdir but as a generator using ctypes
"""

from ctypes import CDLL, c_char_p, c_int, c_long, c_ushort, c_byte, c_char, Structure, POINTER
from ctypes.util import find_library

class c_dir(Structure):
    """Opaque type for directory entries, corresponds to struct DIR"""
    pass
c_dir_p = POINTER(c_dir)

class c_dirent(Structure):
    """Directory entry"""
    # FIXME not sure these are the exactly correct types!
    _fields_ = (
        ('d_ino', c_long), # inode number
        ('d_off', c_long), # offset to the next dirent
        ('d_reclen', c_ushort), # length of this record
        ('d_type', c_byte), # type of file; not supported by all file system types
        ('d_name', c_char * 4096) # filename
        )
c_dirent_p = POINTER(c_dirent)

c_lib = CDLL(find_library("c"))
opendir = c_lib.opendir
opendir.argtypes = [c_char_p]
opendir.restype = c_dir_p

# FIXME Should probably use readdir_r here
readdir = c_lib.readdir
readdir.argtypes = [c_dir_p]
readdir.restype = c_dirent_p

closedir = c_lib.closedir
closedir.argtypes = [c_dir_p]
closedir.restype = c_int

def listdir(path):
    """
    A generator to return the names of files in the directory passed in
    """
    dir_p = opendir(path)
    try:
        while True:
            p = readdir(dir_p)
            if not p:
                break
            name = p.contents.d_name
            if name not in (".", ".."):
                yield name
    finally:
        closedir(dir_p)

if __name__ == "__main__":
    for name in listdir("."):
        print name

撰写回答