PyYAMLでcustom anchorをdumpする方法

#### PyYAMLにはcustom tagを設定する方法はあるが、anchorはなかったので、参考程度に記載しておく。

今回の目的は下記のようなyamlの形式をpythonで自動作成できることである
"&No001"をpython側から付与できるようにする
```yaml
data:
    - &No001 !Node
    　num: !<NDArray> [10.0, 5.0, 0.0]
```

## PyYAMLの内部構造
PyYAMLでdumpする場合、Dumperというクラスがpythonからyamlに変換処理を行う。
そのDumperが継承しているSerializerクラス内で、anchorを含むNodeの処理が行われている

```python:serializer.py:
class Serializer:
    ~
    def serialize_node(self, node, parent, index):
        # aliasがanchorを意味する
        alias = self.anchors[node]
        ~
        elif isinstance(node, MappingNode):
                implicit = (node.tag
                            == self.resolve(MappingNode, node.value, True))
                # nodeを渡し、eventを発行している
                self.emit(MappingStartEvent(alias, node.tag, implicit,
                    flow_style=node.flow_style))
```

なので、alias に指定した（今回は"&No001"）を渡すようにすればよい
#### serialize_nodeをオーバーライドする
全体のコードはこのようになる
```diff_python:CustomSerializer.py
from yaml.nodes import *
from yaml.events import *
from yaml.serializer import Serializer


class CustomSerializer(Serializer):
    def __init__(self, encoding=None, explicit_start=None, explicit_end=None, version=None, tags=None):
        super().__init__(encoding=encoding, explicit_start=explicit_start, explicit_end=explicit_end, version=version, tags=tags)

    def serialize_node(self, node, parent, index):
-       alias = self.anchors[node]
+       # anchorをaliasに渡す
+       alias = str(node.anchor) if node.anchor else None

        if node in self.serialized_nodes:
            self.emit(AliasEvent(alias))
        else:
            self.serialized_nodes[node] = True
            self.descend_resolver(parent, index)
            if isinstance(node, ScalarNode):
                detected_tag = self.resolve(ScalarNode, node.value, (True, False))
                default_tag = self.resolve(ScalarNode, node.value, (False, True))
                implicit = (node.tag == detected_tag), (node.tag == default_tag)
                self.emit(ScalarEvent(alias, node.tag, implicit, node.value,
                    style=node.style))
            elif isinstance(node, SequenceNode):
                implicit = (node.tag
                            == self.resolve(SequenceNode, node.value, True))
                self.emit(SequenceStartEvent(alias, node.tag, implicit,
                    flow_style=node.flow_style))
                index = 0
                for item in node.value:
                    self.serialize_node(item, node, index)
                    index += 1
                self.emit(SequenceEndEvent())
            elif isinstance(node, MappingNode):
                implicit = (node.tag
                            == self.resolve(MappingNode, node.value, True))
                self.emit(MappingStartEvent(alias, node.tag, implicit,
                    flow_style=node.flow_style))
                for key, value in node.value:
                    self.serialize_node(key, node, None)
                    self.serialize_node(value, node, key)
                self.emit(MappingEndEvent())
            self.ascend_resolver()

```

これでnodeのanchorからeventに渡すことはできるようになった。
#### 次に"represent_mapping関数"にはanchor引数がないため、これもオーバーライドする
※scalarもsequenceも同様に変更する

```diff_python:CustomRepresenter.py
from yaml.nodes import *
from yaml.representer import Representer

class CustomRepresenter(Representer):
-   def represent_mapping(self, tag, mapping, flow_style=None):
+   def represent_mapping(self, tag, mapping, flow_style=None, anchor=None):
        value = []
        node = MappingNode(tag, value, flow_style=flow_style)
        if self.alias_key is not None:
            self.represented_objects[self.alias_key] = node
        best_style = True
        if hasattr(mapping, 'items'):
            mapping = list(mapping.items())
            if self.sort_keys:
                try:
                    mapping = sorted(mapping)
                except TypeError:
                    pass
        for item_key, item_value in mapping:
            node_key = self.represent_data(item_key)
            node_value = self.represent_data(item_value)
            if not (isinstance(node_key, ScalarNode) and not node_key.style):
                best_style = False
            if not (isinstance(node_value, ScalarNode) and not node_value.style):
                best_style = False
            value.append((node_key, node_value))
        if flow_style is None:
            if self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style
+       # anchorがある場合nodeに渡す
+       if anchor is not None:
+           node.anchor = anchor

        return node

    def represent_scalar
        ~

    def represent_sequence
        ~
```

#### カスタムしたクラスを扱えるようにDumperも継承してCustomDumperを作成する
```diff_python:CustomDumper.py
class CustomDumper(Emitter, CustomSerializer, CustomRepresenter, Resolver):
    def __init__(self, stream,
            default_style=None, default_flow_style=False,
            canonical=None, indent=None, width=None,
            allow_unicode=None, line_break=None,
            encoding=None, explicit_start=None, explicit_end=None,
            version=None, tags=None, sort_keys=True):
        Emitter.__init__(self, stream, canonical=canonical,
                indent=indent, width=width,
                allow_unicode=allow_unicode, line_break=line_break)
-       Serializer.__init__(self, encoding=encoding,
+       CustomSerializer.__init__(self, encoding=encoding,
                explicit_start=explicit_start, explicit_end=explicit_end,
                version=version, tags=tags)
-       Representer.__init__(self, default_style=default_style,
+       CustomRepresenter.__init__(self, default_style=default_style,
                default_flow_style=default_flow_style, sort_keys=sort_keys)
        Resolver.__init__(self)
```

#### 最後に作成したCustomDumperをyaml.dumpおよびyaml.add_representerの引数で指定してあげる
```python:main.py
    class Tagging:
        def __init__(self, tag, value, anchor=None):
            self.tag = tag
            self.value = value
            self.anchor = anchor

    def custom_representer(dumper, data):
        if isinstance(data.value, dict):
            return dumper.represent_mapping(data.tag, data.value, anchor=data.anchor)
        elif isinstance(data.value, list):
            return dumper.represent_sequence(data.tag, data.value, flow_style=True, anchor=data.anchor)
        else:
            return dumper.represent_scalar(data.tag, data.value, anchor=data.anchor)
    
    yaml.add_representer(Tagging, custom_representer, Dumper=CustomDumper)

    data = {"num": Tagging("NDArray", [10.0, 5.0, 0.0])}
    list_ = [(Tagging("!Node", data, "No001"))]
    dict_ = {"data": list_}

    with open('data.yaml', 'w') as f:
        yaml.dump(data, f, Dumper=CustomDumper)
```