我正在尝试对给定路径下的所有文件执行某些操作。我不想事先收集所有的文件名,然后用它们做一些事情,所以我尝试了这个:
import os
import stat
def explore(p):
s = ''
list = os.listdir(p)
for a in list:
path = p + '/' + a
stat_info = os.lstat(path )
if stat.S_ISDIR(stat_info.st_mode):
explore(path)
else:
yield path
if __name__ == "__main__":
for x in explore('.'):
print '-->', x
但是这个代码在击中目录时跳过目录,而不是放弃它们的内容。我做错了什么?
答案 0 :(得分:124)
迭代器不会像那样递归地工作。您必须通过替换
来重新产生每个结果explore(path)
类似
for value in explore(path):
yield value
Python 3.3添加了PEP 380中提出的语法yield from X
,以实现此目的。有了它,你可以这样做:
yield from explore(path)
如果您正在使用generators as coroutines,则此语法还支持使用generator.send()
将值传递回递归调用的生成器。上面的简单for
循环不会。
答案 1 :(得分:35)
问题在于这行代码:
explore(path)
它做了什么?
explore
path
explore
运行,创建生成器explore(path)
的位置。 。 强> 为何被丢弃?它没有被赋予任何东西,它没有被迭代 - 它完全被忽略了。
如果你想对结果做点什么,那么你必须对它们做点什么! ;)
修复代码的最简单方法是:
for name in explore(path):
yield name
当您确信自己了解正在发生的事情时,您可能希望改为使用os.walk()
。
一旦迁移到Python 3.3(假设所有工作都按计划完成),您将能够使用新的yield from
语法,并且最简单的方法是修复您的代码:
yield from explore(path)
答案 2 :(得分:26)
使用os.walk
而不是重新发明轮子。
特别是,按照库文档中的示例,这是一个未经测试的尝试:
import os
from os.path import join
def hellothere(somepath):
for root, dirs, files in os.walk(somepath):
for curfile in files:
yield join(root, curfile)
# call and get full list of results:
allfiles = [ x for x in hellothere("...") ]
# iterate over results lazily:
for x in hellothere("..."):
print x
答案 3 :(得分:8)
改变这个:
explore(path)
对此:
for subpath in explore(path):
yield subpath
或者使用os.walk
,如phooji建议的那样(这是更好的选择)。
答案 4 :(得分:3)
像函数一样调用explore
。你应该做的是像生成器一样迭代它:
if stat.S_ISDIR(stat_info.st_mode):
for p in explore(path):
yield p
else:
yield path
编辑:您可以使用stat
代替os.path.isdir(path)
模块。
答案 5 :(得分:2)
试试这个:
if stat.S_ISDIR(stat_info.st_mode):
for p in explore(path):
yield p
答案 6 :(得分:0)
但是,对于这个具体案例,os.walk可能是一种更好的方法。
答案 7 :(得分:0)
您还可以使用堆栈实现递归。
除了可能的事实之外,实际上没有任何优势。如果你首先使用python,性能提升可能是不值得的。
import os
import stat
def explore(p):
'''
perform a depth first search and yield the path elements in dfs order
-implement the recursion using a stack because a python can't yield within a nested function call
'''
list_t=type(list())
st=[[p,0]]
while len(st)>0:
x=st[-1][0]
print x
i=st[-1][1]
if type(x)==list_t:
if i>=len(x):
st.pop(-1)
else:
st[-1][1]+=1
st.append([x[i],0])
else:
st.pop(-1)
stat_info = os.lstat(x)
if stat.S_ISDIR(stat_info.st_mode):
st.append([['%s/%s'%(x,a) for a in os.listdir(x)],0])
else:
yield x
print list(explore('.'))
答案 8 :(得分:0)
要回答最初提出的问题,关键是yield
语句需要从递归中传播回来(就像return
一样)。这是os.walk()
的有效重新实现。我在伪VFS实现中使用它,在此我另外替换了os.listdir()
和类似的调用。
import os, os.path
def walk (top, topdown=False):
items = ([], [])
for name in os.listdir(top):
isdir = os.path.isdir(os.path.join(top, name))
items[isdir].append(name)
result = (top, items[True], items[False])
if topdown:
yield result
for folder in items[True]:
for item in walk(os.path.join(top, folder), topdown=topdown):
yield item
if not topdown:
yield result