Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】再帰関数でyieldを利用したい

2025年09月15日に「Qiita」が公開したITニュース「再帰関数でyieldを利用したい」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

再帰関数内でジェネレーターを使いたい場合、`yield from`が役立つ。この記事では、仮想的なディレクトリ構造を再帰的に探索するジェネレーターを例に、`yield from`の具体的な利用方法とメリットを初心者にも分かりやすく解説している。

出典: 再帰関数でyieldを利用したい | Qiita公開日:

ITニュース解説

プログラムを記述する際、ある処理を繰り返し行ったり、複雑な構造の中から必要な情報を取り出したりする場面が多くある。特に、自分自身を呼び出して処理を進める「再帰関数」と、必要な時に必要なだけ値を生成する「ジェネレーター」は、これらを効率的に実現するための強力な道具だ。この二つを組み合わせることで、非常に柔軟でメモリ効率の良いプログラムが書けるが、その際には「yield from」という特別な知識が必要となる。

再帰関数とは、関数が自分自身の内部で再びその関数を呼び出す仕組みを持つ関数のことを指す。これは、同じパターンの問題が、より小さなサイズで繰り返し現れるような状況で特に役立つ。例えば、フォルダの中にさらにフォルダがあり、その中にまたフォルダがあるといった「入れ子」構造のデータを扱う場合に、各フォルダに対して同じ探索処理を適用する、といった使い方が典型的だ。処理の開始点から、ある条件を満たすまで自分自身を呼び出し続け、その呼び出しの連鎖が一番深いところまで到達すると、そこから順に処理が戻ってくる形で実行される。これにより、反復処理をシンプルに記述できるというメリットがある。

ジェネレーターは、Pythonなどのプログラミング言語で「イテレーター」という、値を一つずつ順に取り出すことができるオブジェクトを簡単に作成するための機能だ。通常の関数は一度に全ての処理を実行し、結果をまとめて返すか、何も返さないかのどちらかだが、ジェネレーター関数は「yield」というキーワードを使うことで、処理を途中で一時停止し、現在の値を呼び出し元に返す。そして、次に値が要求されたときには、停止した場所から処理を再開できる。これにより、非常に大きなデータセットを扱う際に、全てのデータをメモリに読み込むことなく、必要な分だけ値を順次生成して処理できるため、メモリ使用量を大幅に抑えることができるという大きなメリットがある。

再帰関数を使って入れ子構造を探索しながら、見つけた要素をジェネレーターとして一つずつ生成していくのは非常に効率的なアプローチだ。しかし、ただ再帰関数の中で「yield」を使ってしまうだけでは、意図した通りの動作にならない場合がある。例えば、あるフォルダを探索するジェネレーター関数が、そのサブフォルダを探索するために自分自身(ジェネレーター関数)を再帰的に呼び出す場合を考えてみよう。この時、サブフォルダを探索する再帰呼び出しの結果は、また別のジェネレーターオブジェクトになる。もしこの子ジェネレーターオブジェクトを yield してしまった場合、親ジェネレーターは子ジェネレーターが生成する個々の値ではなく、子ジェネレーターオブジェクトそのものを呼び出し元に返してしまうことになる。これでは、期待する「個々のファイルやフォルダ名」を順次取得することはできない。

この問題を解決するために登場するのが「yield from」というキーワードだ。「yield from」は、別のジェネレーターやイテレーターから生成される値を、あたかも現在のジェネレーター自身が生成しているかのように、呼び出し元に「委譲」する役割を果たす。つまり、現在のジェネレーターが一時的に制御を別のジェネレーターに渡し、その別のジェネレーターが生成する全ての値を、現在のジェネレーター経由で呼び出し元に直接渡すようなイメージだ。これにより、複雑なネストしたジェネレーターの呼び出しチェーンを、よりシンプルかつ効率的に記述できるようになる。特に再帰関数とジェネレーターを組み合わせる際には、この「yield from」が非常に重要な役割を担う。

ニュース記事の例題は、仮想的なディレクトリ(フォルダ)構造をリスト形式で表現し、それを再帰的に探索するジェネレーターを作成するケースだ。この構造は、ファイル名と、それがディレクトリであればその中身を示すリストのタプルとして表現されている。例えば「documents」というディレクトリがあれば、その中身をさらに探索する必要がある。ここで「探索ジェネレーター」関数を考えよう。この関数は、与えられたディレクトリ構造のリストを受け取り、含まれるファイルやディレクトリ名を順次生成していくことを目指す。関数がリスト内の各要素をループ処理する際、もし要素が単なるファイル(末端の要素)であれば、そのファイル名を yield して呼び出し元に返す。しかし、もし要素がサブディレクトリ(さらに内部構造を持つ要素)であれば、その中身をさらに探索するため、探索ジェネレーター関数は自分自身を再帰的に呼び出し、サブディレクトリの中身を引数として渡す。この再帰呼び出しの結果は、サブディレクトリを探索するための別のジェネレーターオブジェクトとなる。ここで yield だけを使ってしまうと、親ジェネレーターはサブディレクトリから得られた「ジェネレーターオブジェクト」そのものを呼び出し元に返してしまうため、期待する個々のファイル名やディレクトリ名が得られず、意味がない。そこで yield from の出番だ。yield from を使うと、親ジェネレーターは再帰的に呼び出した子ジェネレーターに対して処理を「委譲」する。これにより、子ジェネレーターが生成する個々のファイル名やディレクトリ名といった値は、親ジェネレーターを介して、スムーズに最終的な呼び出し元に届けられる。親ジェネレーターは子ジェネレーターが全ての値を生成し終えるまで、その処理が終わるのを待ってから、自身の次の処理に移る。このメカニズムにより、仮想的なディレクトリ構造全体を、一つの統一されたジェネレーターインターフェースを通して、深さに関わらず全ての要素を一つずつ効率的に取得できるようになる。再帰処理とジェネレーターの遅延評価のメリットを組み合わせつつ、コードの記述も非常に簡潔になるのだ。

再帰関数とジェネレーターを組み合わせて複雑な構造を効率的に探索し、メモリを節約しながら結果を順次生成したい場合、「yield from」キーワードは不可欠なツールだ。これは、あるジェネレーターが別のジェネレーターから生成される値を「引き継いで」出力するメカニズムを提供し、再帰的なジェネレーターの記述を簡潔にし、意図しない挙動を防ぐ。システムエンジニアを目指す上で、大規模なデータや複雑な構造を扱う際には、この「yield from」の理解と活用が、より効率的で洗練されたプログラムを書く上で大いに役立つだろう。

関連コンテンツ