web-dev-qa-db-ja.com

再帰関数での収量

特定のパスにあるすべてのファイルに何かをしようとしています。すべてのファイル名を事前に収集してからそれらを使用する必要はないので、これを試しました。

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

しかし、このコードは、ディレクトリにヒットすると、コンテンツを生成するのではなく、ディレクトリをスキップします。何が悪いのですか?

52
Ali

使用する - 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
25
phooji

イテレータはそのように再帰的に機能しません。置き換えることにより、各結果を再生成する必要があります

_explore(path)
_

のようなもので

_for value in explore(path):
    yield value
_

Python 3.PEP 38 で提案されている構文_yield from X_を追加して、この目的を果たします。代わりにこれを行うことができます:

_yield from explore(path)
_

generators as coroutines を使用している場合、この構文は generator.send() の使用もサポートしており、再帰的に呼び出されるジェネレーターに値を返します。上記の単純なforループはそうではありません。

126
Jeremy Banks

問題は次のコード行です。

_explore(path)
_

それは何をするためのものか?

  • 新しいexplorepathを呼び出します
  • explore実行、ジェネレーターの作成
  • ジェネレータはexplore(path)が実行された場所に戻ります。。。
  • そして破棄されます

なぜ捨てられるのですか?何にも割り当てられておらず、繰り返しも行われていません。完全に無視されました。

結果で何かをしたいのなら、まあ、あなたはそれらで何かをしなければならない! ;)

コードを修正する最も簡単な方法は次のとおりです。

_for name in explore(path):
    yield name
_

何が起こっているかを理解していると確信できる場合は、代わりにos.walk()を使用することをお勧めします。

Python 3.3(すべてが計画どおりに機能すると想定)に移行すると、新しい_yield from_構文を使用できるようになり、その時点でコードを修正する最も簡単な方法はになる:

_yield from explore(path)
_
36
Ethan Furman

これを変える:

explore(path)

これに:

for subpath in explore(path):
    yield subpath

またはos.walk、phoojiが示唆するように(これはより良いオプションです)。

8
Dietrich Epp

関数のようにexploreを呼び出します。あなたがすべきことはジェネレータのようにそれを繰り返すことです:

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

編集:statモジュールの代わりに、os.path.isdir(path)を使用できます。

3
MRAB

これを試して:

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

スタックを使用して再帰を実装することもできます。

これを行うことには、それが可能であるという事実以外に、何の利点もありません。最初に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
user1149913

os.walkは、すべてのフォルダーとサブフォルダーを走査する必要がある場合に最適です。それが必要なければ、象銃を使ってハエを殺すようなものです。

ただし、この特定のケースでは、os.walkの方が優れたアプローチである可能性があります。

0
Robson França

元の質問に答えるには、重要なのは、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
Hlórriði