将文件夹中的文件作为流列出以便立即处理
我有一个文件夹,里面有一百万个文件。
我想要在这个文件夹里列出文件的时候,能够立刻开始处理,而不是等到所有文件都列出来再开始,这样会浪费时间。
像Python里的os.listdir这样的常用函数是阻塞的,也就是说我的程序得等到文件列出完毕才能继续执行,这可能会花费很长时间。
那么,有什么好的方法可以快速列出这么大的文件夹呢?
4 个回答
对于从谷歌过来的朋友们,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.walk
是os.walk
的一个性能更好的版本。scandir.scandir
返回一个可以遍历DirEntry
对象的迭代器。
scandir()
迭代器在 POSIX 平台上封装了 opendir
/readdir
,在 Windows 上封装了 FindFirstFileW
/FindNextFileW
。
返回 DirEntry
对象的目的是为了缓存元数据,以减少系统调用的次数。(例如,DirEntry.stat(follow_symlinks=False)
在 Windows 上从不进行系统调用,因为 FindFirstFileW
和 FindNextFileW
函数会免费提供 stat
信息)
这看起来有点不太干净,但应该能解决问题:
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')
如果方便的话,可以考虑改变你的文件夹结构;但如果不方便,你可以使用ctypes来调用opendir
和readdir
。
这里有一份代码的副本;我只是把它缩进整理了一下,添加了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