導入
下図のような依存関係のあるDogとCatのオブジェクトがあるとします。Dogのcat属性にはCatクラスのオブジェクトが格納されていて、Catのdog属性にはDogクラスのオブジェクトが格納されているという状況です。
本稿の目標はこの依存関係を保ったまま、DogやCatオブジェクトのクローン(複製)を作成することです。この依存関係を持ったオブジェクト集合をもう一つ別に作成する感じですね。
どの場面でこのような作業が必要になるかというと、例えばある制御の中で、シミュレーションによる未来予測の結果をもとに現在の制御値を試行錯誤する場合など。このときは現状の動作系の"複製"を作成してその複製上でシミュレーションを実行しその結果をもとに"複製元"の制御を決めてシミュレーションを少し進める、というようなことが可能になります。
手法
上記の例のDogとCatのミニマル実装。
class Dog: def __init__(self, cat=None): self.cat = cat def clone(self): return Dog(self.cat) class Cat: def __init__(self, dog=None): self.dog = dog def clone(self): return Cat(self.dog) dog = Dog() cat = Cat() d.cat = cat c.dog = dog
愚直にやるなら...
もう一度生成をやり直せば良いです。例のようなオブジェクトが2つの場合は簡単ですが、属性に指定されるオブジェクトが事前に分からない場合やオブジェクト数が多い場合は、cloneする順序や属性への代入順序に気を付ける必要があり、なかなか大変で、結構難しいのではないでしょうか。
_dog = dog.clone() _cat = cat.clone() _dog.cat = _cat _cat.dog = _dog
pickleによる複製作成
ということで、本校ではpickleを用いて簡単にオブジェクト集合の複製を行う方法を紹介します。pickleはpythonの標準モジュールでオブジェクトの直列化と依存関係の保存、ファイル書き出しなどを行ってくれます。このpickleが賢くて、例えば循環参照がオブジェクト間に存在するケースもそれを解消しながら直列化を行なってくれます。
具体的には、pickle.dumps()でオブジェクトを直列化したバイト列を出力して、pickle.loads()でそれをもとにオブジェクトを復元してくれるのでこれを用います。
import pickle
dumped_byte = pickle.dumps((dog, cat))
_dog, _cat = pickle.loads(dumped_byte)
確認してみると、
複製元
id(dog), id(cat), id(dog.cat), id(cat.dog) >>> (4399902832, 4399903696, 4399903696, 4399902832)
pickleによる複製
id(_dog), id(_cat), id(_dog.cat), id(_cat.dog) >>> (4396410960, 4396409616, 4396409616, 4396410960)
ということで、dogとdog, catやcatが別のオブジェクトでかつ、dogとcatにもともとの依存関係が保たれていることが分かりますね。
まとめ
pickleを用いたオブジェクト集合の依存関係を保つ複製方法を紹介しました。