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



忘れないうちにメモ。

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

copyモジュール — 浅いコピーおよび深いコピー操作

まずは、普通にオブジェクトのコピー
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関数

こんなジェネレータ関数を考える

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

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)

こんなのを書いてみる

>>> 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 "<pyshell #54>", line 1, in <module>
   g.send(0)
StopIteration

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

generatorオブジェクトで例外を発生させるthrowメソッド

>>> g=f()
>>> g.next()
1
>>> g.throw(StopIteration)
Traceback (most recent call last):
 File "<pyshell #57>", line 1, in <module>
   g.throw(StopIteration)
 File "<pyshell #46>", line 4, in f
   i=(yield i)
StopIteration

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

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

Tags:

Related posts

タグ:

コメントは受け付けていません。