0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RubyでPythonのgeneratorのようなことをするには

Last updated at Posted at 2023-05-09

Rubyで実装する案件で、Pythonのgeneratorのような形で遅延読み込みをしようと思い、どんなふうに実現したかという話です。

Pythonのgenerator

Pythonのgeneratorは遅延読み込みなどを行う際に便利です。
例えば大きいjsonlファイルを扱う場合だと、以下のような感じに、データを取り出す処理と加工する処理を分けて書くことができつつ、データを1行ずつ取り出して変換することができます。
(jsonlについては https://qiita.com/yasubei/items/95e6a742c24189ded3d6 あたりを参照してください)

import json

def jsonl_generator(jsonl_path):
    with open(jsonl_path) as f:
        for l in f:
            yield json.loads(l)

def main()
    gen = jsonl_generator('大きい.jsonl')
    for dict in gen:
        print(dict['name'])

main()

RubyのFiber

Rubyで上記のようなことをするにはFiberを使うことで実現できます。
上記と同様の処理を書いてみると、以下のような感じです。
追記 にコメントいただいた内容を反映した改良版があります。

require 'json'

class JsonlFiber
    def initialize(jsonl_path)
        f = File.open(jsonl_path)
        @fiber = Fiber.new do
            until f.eof? do
                Fiber.yield(JSON.parse(f.readline)
            end
        end
    end

    def each
        while @fiber.alive? do
            yield(@fiber.resume)
        end
    end
end

def main
    JsonlFiber.new('大きい.jsonl').each do |h|
        print(h['name'])
    end
end

main

感想

RubyのFiberで書くと、Pythonのgeneratorよりもやや長くなってしまいますが、要件や好み次第で、どっちで書くのもありかなと思いました。
あと、 Fiber.yield はPythonの yield と同様な感じで、 each で使った yield はブロックの実行と、ちょっとややこしいなと思いました。

追記

コメントをいただいたので、ちょっと改良版を書いてみました。
これで案件の方で 動くかはまだ試していませんが 取り入れようとしたのですが、案件の方ではループで回ってくるタイミングと、 yield するタイミングが異なるため Fiber を使うことにしました。
ただしタイミングが同じなら、 class にして Fiber を使って each を実装するよりも、コメントいただいた to_enum を使う方法の方がコンパクトに実装できますし、 Enumerator の全メソッドが使えて汎用性が高いですね。

def jsonl_fiber(jsonl_path)
    unless block_given?
        return to_enum(__method__, jsonl_path)
    end
    f = File.open(jsonl_path)
    until f.eof? do
        yield(JSON.parse(f.readline))
    end
end

def main
    enum = jsonl_fiber('大きい.jsonl')
    enum.each do |h|
        print(h['name'])
    end
end

上にも書いた通り、この案件を対応した際に扱ったファイルがGBオーダーのものでして、1行ずつではなくもっとまとめて読んで、含まれているデータによってごにょごにょしてと、もうちょっと複雑なことになっていたため Fiber を使った次第です。
案件ではデータの塊単位でまとめて読み込んで、Fiber.yield は、読み込んだ内容に応じて加工したデータを返すところで使っていました。
例を簡単にしすぎてしまって、すみません、うまく伝わらなかったかもしれません。

0
0
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?