ジェネレータのコピー&sendメソッド

忘れないうちにメモ。

(コードが多くて煩雑なので、トップページには表示しないようにしました。)

[pukiwiki]
*[[copyモジュール — 浅いコピーおよび深いコピー操作:http://www.python.jp/doc/2.4/lib/module-copy.html]]
まずは、普通にオブジェクトのコピー
Pythonでオブジェクトの複製を作りたいときはcopyモジュールを使います。 

_import copy
_>>> a=[1,2,3]
_>>> b=copy.copy(a)
_>>> b
_[1, 2, 3]

あくまでも値をコピーしているだけなので、元のオブジェクトを操作しても、コピー先には反映されません
_>>> a[2]=[3,4,5]
_>>> a
_[1, 2, [3, 4, 5]]
_>>> b
_[1, 2, 3]

でも、copy.copyは”浅いコピー”なので

_>>> b=copy.copy(a) #上段から続き
_>>> b
_[1, 2, [3, 4, 5]]
_>>> a[2][0]=5
_>>> a
_[1, 2, [5, 4, 5]]
_>>> b
_[1, 2, [5, 4, 5]] #bの値まで変化
_>>> a is b
_False
_>>> a[2] is b[2] 
_True

こういうことが起きないようにするには深いコピー(deepcopy)を使う
_>>> a
_[1, 2, [5, 4, 5]]
_>>> b=copy.copy(a)
_>>> c=copy.deepcopy(a)
_>>> c==a
_True
_>>> c is a
_False
_>>> c[2] is a[2]
_False
_>>> a
_[1, 2, [5, 4, 5]]
_>>> a[2][0]=3
_>>> a
_[1, 2, [3, 4, 5]]
_>>> b
_[1, 2, [3, 4, 5]] #一緒に変化してる
_>>> c
_[1, 2, [5, 4, 5]] #変化してない

*[[itertools.tee関数:http://www.python.jp/doc/2.4/lib/itertools-functions.html]]
こんなジェネレータ関数を考える
_def f():
_ i=0
_ for i in xrange(10) :
_ yield i
_ i+=1

_>>> g=f()
_>>> g.next()
_0
_>>> g.next()
_1
_>>> g.next()
_2

この状態でgを普通にコピーするとエラーが発生

_>>> g2=copy.copy(g)
_Traceback (most recent call last):
_ File “<pyshell #106>”, line 1, in <module>
_ g2=copy.copy(g)
_(以下略)

こんなときは itertoolsモジュールのtee関数を使う

_>>> import itertools
_>>> itertools.tee(g)
_(<itertools .tee object at 0x03FEEEE0>, </itertools><itertools .tee object at 0x03FEEEB8>)

gの複製2個がタプルに入って返ってくる。
第2引数はコピーする個数
_>>> g1,g2,g3= itertools.tee(g,3)
_>>> g1.next()
_3
_>>> g1.next()
_4
_>>> g1.next()
_5
_>>> list(g2)
_[3, 4, 5, 6, 7, 8, 9]

元のgは操作できなくなる
_>>> g.next()
_Traceback (most recent call last):
_ File “<pyshell #117>”, line 1, in <module>
_ g.next()
_StopIteration

ジェネレータはcopy.copyできないが、itertools.tee objectならcopy.copyできる

_>>> g1,g2=itertools.tee(f())
_>>> g1
_<itertools .tee object at 0x03FFBAA8>
_>>> g3=copy.copy(g1)
_>>> list(g1)
_[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_>>> list(g3)
_[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

itertools.tee objectを、さらにteeできるので、copy.copyは使う必要が無いかも。
_>>> g2.next()
_0
_>>> g2.next()
_1
_>>> g3,g4=itertools.tee(g2)
_>>> list(g3)
_[2, 3, 4, 5, 6, 7, 8, 9]
_>>> list(g4)
_[2, 3, 4, 5, 6, 7, 8, 9]
*[[ググる:バックトラック]]
で、こんなことをして何が楽しいかというと、何らかの処理のロールバックとか、バックトラックに使えるかも、と思ったので。

(具体例は、まだ作ってないですけれども。)
*generatorオブジェクへ信号を送るsendメソッド
[[7 PEP 342: New Generator Features:http://docs.python.org/whatsnew/pep-342.html]]
“In 2.5, yield is now an expression, returning a value that can be assigned to a variable or otherwise operated on:~
val = (yield i)

-[[じじぃの日記、ツッコミ可2007-01-05:http://jijixi.azito.com/cgi-bin/diary/index.rb?date=20070105]]

こんなのを書いてみる
_>>> def f ():
_ i=1
_ while i :
_ i=(yield i)

_>>> g.next()
_1              # いきなりsendを呼ぶと怒られます
_>>> g.send(100)
_100
_>>> g.send(1000)
_1000
_>>> >>> g.send(0)
_Traceback (most recent call last):
_ File ““, line 1, in
_ g.send(0)
_StopIteration

ジェネレータの挙動をある程度コントロールできます。

*generatorオブジェクトで例外を発生させるthrowメソッド
_>>> g=f()
_>>> g.next()
_1
_>>> g.throw(StopIteration)
_Traceback (most recent call last):
_ File ““, line 1, in
_ g.throw(StopIteration)
_ File ““, line 4, in f
_ i=(yield i)
_StopIteration

強制的にリセットしたい時とか。

かなり凝ったこともできそうだけれど、あまり使わないような気も。

[/pukiwiki]

コメントを残す

メールアドレスが公開されることはありません。