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