Выход в рекурсивной функции
Я пытаюсь сделать что-то со всеми файлами по заданному пути. Я не хочу собирать все имена файлов заранее, а затем что-то с ними делать, поэтому я попробовал это:
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
но этот код пропускает каталоги, когда он попадает в них, вместо того, чтобы давать их содержимое. Что я делаю не так?
8 ответов
использовать 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
итераторы не работают рекурсивно. Вы должны повторно дать каждый результат, заменив
explore(path)
что-то вроде
for value in explore(path):
yield value
Python 3.3 добавлен синтаксис yield from X
, как предлагается в PEP 380, чтобы служить этой цели. С его помощью вы можете сделать вместо этого:
yield from explore(path)
если вы используете генераторы, сопрограммы этот синтаксис также поддерживает использование generator.send()
для передачи значений обратно в рекурсивно вызванные генераторы. Простой for
петли выше не будет.
проблема в этой строке кода:
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)
изменить это:
explore(path)
для этого:
for subpath in explore(path):
yield subpath
или использовать os.walk
, как предложил phoji (что является лучшим вариантом).
что называет explore
как функция. То, что вы должны сделать, это повторить его как генератор:
if stat.S_ISDIR(stat_info.st_mode):
for p in explore(path):
yield p
else:
yield path
EDIT: вместо stat
модуль, вы можете использовать os.path.isdir(path)
.
os.прогулка отлично подходит, если вам нужно пересечь все папки и подпапки. Если вам это не нужно, это похоже на использование слоновьего ружья, чтобы убить муху.
однако, для этого конкретного случая, os.прогулка может быть лучшим подходом.
вы также можете реализовать рекурсию с помощью стека.
в этом нет никакого преимущества, кроме того, что это возможно. Если вы используете 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('.'))