« log |Main| QuickSort祭り(一人) »

« | Ruby | PythonとRubyでデフォルト引数の評価されるタイミングは違う »

RubyのyieldとPythonのyieldの違い

RubyのyieldとPythonのyieldは実は全然違うような気がしてきたので、整理のために書いてみます。 まず、Rubyでeachに相当する物を自分で実装してみました。

# Ruby 1
class Array
  def my_each
    self.each{|x|
      yield x
    }
  end
end

[1, 2, 3, 4, 5].my_each{|x|
  puts x
}

これに相当する物をPythonで書くと、実はyieldを使いません。

# Python 1
class Array(list):
    def each(self, f):
        for i in self:
            f(i)

def myprint(x):
    print x

Array([1, 2, 3, 4, 5]).each(
    myprint
)

これをもっとRuby 1のコードに近づけて書くと以下のようになります。Pythonにはputsに相当する関数がsys.stdout.writeという長ったらしい物なのでputsは自分で定義していますがそこは本質ではありません。重要なのは「引数xを取ってputs(x)を評価せよ」というモノ(Python流に言うと無名関数、Ruby流に言うとブロック)を渡しているということだと思います。

# Python 2
class Array(list):
    def each(self, f):
        for i in self:
            f(i)

def puts(x):
    print x

Array([1, 2, 3, 4, 5]).each(lambda x:
    puts(x)
)

逆にRuby 1の方をPython 1に近づけて書くと次のようになるようです。

# Ruby 2
class Array
  def my_each
    self.each{|x|
      yield x
    }
  end
end

myprint = proc {|x|
  puts x
}

[1, 2, 3, 4, 5].my_each(
  &myprint
)

Python 1 では関数オブジェクトを引数に渡していましたが、Ruby 2ではProcオブジェクトを渡すようです。

さて、ではPythonでyieldを使うとどうなるでしょう。

# Python 3
class Array(list):
    def each(self):
        for i in self:
            yield i

for i in Array([1, 2, 3, 4, 5]).each():
    print i

Rubyと大きく異なる点は、forが必要である点と、eachが引数を取らない点です。Rubyの「yieldを使っているメソッド」は「Procオブジェクトを受け取ってyieldの場所でyieldの後に続いているモノを引数としてそのProcオブジェクトを呼び出す」という働きでしたが、Pythonの「yieldを使っているメソッド」は「なにも受け取らず、ジェネレータを返す」という働きをします。ジェネレータはnextメソッドを持ち、nextが呼ばれるとyieldまでを実行してyieldの後に続いているモノを返り値として返します。ですので、ループを途中で中断することもできますし、中断したループを後で継続することもできます。

# Python 4
class Array(list):
    def each(self):
        for i in self:
            yield i

gen = Array([1, 2, 3, 4, 5]).each()
for i in gen:
    print i
    if i == 3:
        break

print gen.next() # -> 4

Rubyで同じことをやろうとすると、明示的にジェネレータを使って以下のようになります。

# Ruby 3
require 'generator'

g = Generator.new([1, 2, 3, 4, 5])

while g.next?
  i = g.next
  puts i
  if i == 3
    break
  end
end

puts g.next

Generator.newの引数にはProcオブジェクトを渡せるようですので、無限リストはこんな感じに実装できますね。

# Ruby 4
require 'generator'

def makeList
  Generator.new{|g|
    i = 0
    while true 
      i += 1
      g.yield i
    end
  }
end

g = makeList

while g.next?
  i = g.next
  puts i
  if i == 3
    break
  end
end

puts g.next

PythonでRuby 4に相当するコードを書くとこうです。

# Python 5
def makeList():
    i = 0
    while True:
        i += 1
        yield i

gen = makeList()
for i in gen:
    print i
    if i == 3:
        break

print gen.next() # -> 4

Rubyでは引数gにProcオブジェクト?を受け取っているようですね。Pythonではジェネレータの時には省略できて、Ruby風のループ抽象(イテレータ?)を行うときには省略せずに引数fに関数オブジェクトを受け取っていました。Rubyではちょうど逆ですね。面白いです。

トラックバック(Trackback)

Trackback URL: http://www.nishiohirokazu.org/mt/mt-tb.cgi/163

ご意見・ご感想をお送りください(フィードバック)

(フィードバックはメールで送信され、基本的に表示されませんが、内容によっては公開させていただくこともございます。ご了承ください。Your comment doesn't appear the page immediately. If the comment has value to other people, it will be put on the page or subsequent entries. Thank you.)

上の情報は、いずれも未記入でかまいません。 All of above questions are optional.