擬循環参照【第 6 回 Python × Blender】

今回は BlenderPython API の素晴らしい設計の特徴の一つを見たいと思います。



モジュール性・拡張性・自由な抽象化を目指すために、bpyモジュールは無限のネスト構造が可能となっています。



これが今回紹介したいbpyモジュールの擬循環参照(pseudo-circular referencing)機能です。

循環参照とは?

本題とは関係ないので興味のない方はここは飛ばしてもらって構わないのですが、そもそも循環参照とは何でしょうか?



循環参照の例としては 2 つのオブジェクトの間でお互いを参照しあっている場合です。例えば以下のような Python コードを見てください。

lhs_list = []
rhs_list = []

lhs_list.append(rhs_list)
rhs_list.append(lhs_list)

こうするとlhs_list = [rhs_list],rhs_list = [lhs_list]という風になります。



するとlhs_list[0]rhs_listを参照し、rhs_list[0]lhs_listを参照しているのでここで循環参照が起きています。
f:id:tamaki_py:20190529112507j:plain
こうなるとlhs_list,rhs_list共に[[[[...]]]]と無限にリストが入れ子(ネスト構造)に入ってしまうので、通常はメモリリークを起こしエラーとなります。



ただし Python のガベージコレクタ(使われなくなったオブジェクトのメモリ領域を解放してくれる)はこのような循環参照を起こしているオブジェクトも解放しエラーを防いでくれます。



明示的に__del__メソッドでオブジェクトのメモリ領域解放条件を指定したクラス以外のオブジェクトで、循環参照によるエラーは Python においては起こらないようです(詳しいことは以下のサイトを参照してください)。
emptypage.jp


bpy の擬循環参照

ここからが今日の本題です。



以下の Python スクリプトBlender の Text Editor で実行してみてください(BlenderPython スクリプトを使う方法は以下の記事を参考にしてください)。
tamaki-py.hatenablog.com

import bpy

print(id(bpy.data.objects.data))
print(id(bpy.data.objects.data.objects.data))
print(id(bpy.data.objects.data.objects.data.objects.data))
print(id(bpy.data.meshes.data))
print(id(bpy.data.meshes.data.objects.data))
print(id(bpy.data.meshes.data.objects.data.scenes.data.worlds.data.materials.data))

ターミナルを見ると実行結果は以下のようになり、

  • bpy.data.objects.data
  • bpy.data.objects.data.objects.data
  • bpy.data.objects.data.objects.data.objects.data
  • bpy.data.meshes.data
  • bpy.data.meshes.data.objects.data
  • bpy.data.meshes.data.objects.data.scenes.data.worlds.data.materials.data

これらのメモリアドレスは全て同じで、これらは全て同一のオブジェクトを示しているということが確認できます。

4664398088
4664398088
4664398088
4664398088
4664398088
4664398088

この結果は Blender Python API の強力な特徴の一つを示しています。



どういうことかと言いますと、Blender オブジェクトに対しドットシンタックス.dataという風に付け加えると、これはそのオブジェクトの親のデータブロック(parent datablock)への参照を返すということです。



これによりbpyモジュールをいちいち呼び出さずにオブジェクトをビルドしたり操作できます。