Выход в рекурсивной функции

Я пытаюсь сделать что-то со всеми файлами по заданному пути. Я не хочу собирать все имена файлов заранее, а затем что-то с ними делать, поэтому я попробовал это:

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).


попробуйте это:

if stat.S_ISDIR(stat_info.st_mode):
    for p in explore(path):
        yield p

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('.'))