Урожай в рекурсивной функции - PullRequest
51 голосов
/ 20 июля 2011

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

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

Но этот код пропускает каталоги, когда попадает к ним, вместо выдачи их содержимого.Что я делаю не так?

Ответы [ 9 ]

125 голосов
/ 20 июля 2011

Итераторы не работают рекурсивно, как это. Вы должны заново выдать каждый результат, заменив

explore(path)

с чем-то вроде

for value in explore(path):
    yield value

В Python 3.3 добавлен синтаксис yield from X, предложенный в PEP 380 , для этой цели. С его помощью вы можете сделать это вместо:

yield from explore(path)

Если вы используете генераторы в качестве сопрограмм , этот синтаксис также поддерживает использование generator.send() для передачи значений обратно в рекурсивно вызванные генераторы. Простой цикл for выше не будет.

35 голосов
/ 18 января 2012

Проблема в этой строке кода:

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)
26 голосов
/ 20 июля 2011

Используйте 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
8 голосов
/ 20 июля 2011

Изменить это:

explore(path)

К этому:

for subpath in explore(path):
    yield subpath

Или используйте os.walk, как предложил phooji (что является лучшим вариантом).

3 голосов
/ 20 июля 2011

Это вызывает explore как функцию. Что вам нужно сделать, это повторить его как генератор:

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

РЕДАКТИРОВАТЬ: вместо stat модуля, вы можете использовать os.path.isdir(path).

2 голосов
/ 20 июля 2011

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

if stat.S_ISDIR(stat_info.st_mode):
    for p in explore(path):
        yield p
0 голосов
/ 29 ноября 2018

Чтобы ответить на первоначальный вопрос в том виде, в котором он был задан, ключом является то, что оператор 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
0 голосов
/ 13 декабря 2013

Вы также можете реализовать рекурсию с помощью стека.

В этом нет особого преимущества, кроме того, что это возможно.Если вы используете 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('.'))
0 голосов
/ 01 апреля 2012

os.walk отлично подходит, если вам нужно пройти через все папки и подпапки.Если вам это не нужно, это все равно, что использовать слоновую пушку для убийства мухи.

Однако для этого конкретного случая лучше использовать os.walk.

...