pymc4のソースコード読んでみた - “treedict”, Initial Model Class, sampling and random variable [aafa32d]
コミット
2018/05/30のコミットです。
Initial Model Class, sampling and random variable · pymc-devs/pymc4@aafa32d · GitHub
主に、pymc4の根幹となる Model
と RandomVariable
クラスが作成されています。
以下ファイルが追加されています。
- .vscode/settings.json
- pymc4/init.json
- pymc4/model.py
- pymc4/random_variable.py
- pymc4/sample.py
- test.ipynb
一つ一つを細かくみたいので、今回は model.py
の Model
クラスだけを見てみようと思ったのですが、Model
クラス内で使用している treedict
クラスがあるのでまずはこっちを読んでみます(できるだけコード量が少なく、依存性が小さいクラスから読み進める方針です)
treedict - model.py
以下が treedict
クラスです。
class treedict(dict): """A dict that passes mutable extending operations used in Model to parent dict instance. Extending treedict you will also extend its parent """ def __init__(self, iterable=(), parent=None, **kwargs): super(treedict, self).__init__(iterable, **kwargs) assert isinstance(parent, dict) or parent is None self.parent = parent if self.parent is not None: self.parent.update(self) # typechecking here works bad __setitem__ = withparent(dict.__setitem__) update = withparent(dict.update) def tree_contains(self, item): # needed for `add_random_variable` method if isinstance(self.parent, treedict): return (dict.__contains__(self, item) or self.parent.tree_contains(item)) elif isinstance(self.parent, dict): return (dict.__contains__(self, item) or self.parent.__contains__(item)) else: return dict.__contains__(self, item)
- まずはクラスのコメントから読んでみます。
A dict that passes mutable extending operations used in Model to parent dict instance. Extending treedict you will also extend its parent
つまり、 python組み込みの dict
クラスを継承して、親クラス(=parent)の dict
まで拡張するように変更した自作クラスのようです。また Model
クラス内で使用されているとのことです。
次に __init__
処理に注目してみます。
- super().init で初期化
parent
がdict
もしくは None であることをassertチェックparent
がdict
もしくはNone
であれば引数のparent
をself
にセットする
def __init__(self, iterable=(), parent=None, **kwargs): super(treedict, self).__init__(iterable, **kwargs) assert isinstance(parent, dict) or parent is None self.parent = parent if self.parent is not None: self.parent.update(self)
例えば以下のようになります。
parent
がNoneの場合
child1 = treedict({'a': 1, 'b': 2}) child1 # => {'a': 1, 'b': 2} child1.parent # => None
parent
が dict
インスタンスの場合
child2 = treedict({'a': 1, 'b': 2}, parent={'x': 1, 'y': 2}) child2 # => {'a': 1, 'b': 2} child2.parent # => {'a': 1, 'b': 2, 'x': 1, 'y': 2}
- 別メソッドの
withparent
を呼んでいるみたいなので、次回のコードリーディングに持ち越します。
# typechecking here works bad __setitem__ = withparent(dict.__setitem__) update = withparent(dict.update)
tree_contains
メソッドを見てみます。組み込みのdict
クラスの__contains__
メソッドは対象の値が自分の key: value の中に存在するかチェックできますが、parent
まで値をたどることができません。ですので、このtreedict
メソッドでは親のparent
まで値をたどるようにしているようです。
def tree_contains(self, item): # needed for `add_random_variable` method if isinstance(self.parent, treedict): return (dict.__contains__(self, item) or self.parent.tree_contains(item)) elif isinstance(self.parent, dict): return (dict.__contains__(self, item) or self.parent.__contains__(item)) else: return dict.__contains__(self, item)
3つの if-elif-else の処理を見てみます。
parent
がtreedict
の場合: 対象の値がself
内、、もしくはparent
に存在するかチェックparent
がdict
の場合: 対象の値がself
内、、もしくはparent
に存在するかチェック- それ以外の場合(=
parent
の探索が不要): 対象の値がself
内に存在するかチェック
1
と 2
の違いは、 parent
が treedict
か dict
かによって呼ぶメソッドを切り替えているだけです。