Repository: opendilab/treevalue (language: python)

Issue:
New features in version 0.1.0
Implemented in #5 .

- [x] Dump of the tree value graph.
- [x] Suport `pickle.dumps`  of `TreeValue` objects
- [x] Binary-based full data dump
- [x] Binary-based full data load
- [x] Native-python-based full data load
- [x] Self calculation with original address
- [x] Self operator with original address
- [x] Add more features in `general_treevalue`, such as `NotImplement`
- [x] CLI support (dump/load, graph generation, graphviz code dump, etc)


New features in version 0.1.0
Implemented in #5 .

- [x] Dump of the tree value graph.
- [x] Suport `pickle.dumps`  of `TreeValue` objects
- [x] Binary-based full data dump
- [x] Binary-based full data load
- [x] Native-python-based full data load
- [x] Self calculation with original address
- [x] Self operator with original address
- [x] Add more features in `general_treevalue`, such as `NotImplement`
- [x] CLI support (dump/load, graph generation, graphviz code dump, etc)




Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index b9b5a6b8dc..5549398d17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1193,6 +1193,7 @@ fabric.properties
 .idea/caches/build_file_checksums.ser
 
 /test_*
+/*.btv
 .python-version
 /docs/build
 /public
diff --git a/README.md b/README.md
index aa95a3f357..e9b56d0b90 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,10 @@ The result should be
     └── 'd' --> 4
 ```
 
+And `t` is structure should be like this
+
+![t_graph](https://opendilab.github.io/treevalue/main/_images/simple_demo.dat.svg)
+
 For more quick start explanation and further usage, take a look at:
 
 * [Quick Start](https://opendilab.github.io/treevalue/main/tutorials/quick_start/index.html)
diff --git a/docs/Makefile b/docs/Makefile
index 7e5a238e8c..a6fe2ef4bd 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -14,9 +14,15 @@ GRAPHVIZ    := $(MAKE) -f "${GRAPHVIZ_MK}" SOURCE=${SOURCEDIR}
 DEMOS_MK    := ${SOURCEDIR}/demos.mk
 DEMOS       := $(MAKE) -f "${DEMOS_MK}" SOURCE=${SOURCEDIR}
 
+_CURRENT_PATH       := ${PATH}
+_PROJ_DIR           := $(shell readlink -f ${CURDIR}/..)
+_LIBS_DIR           := $(shell readlink -f ${SOURCEDIR}/_libs)
+_SHIMS_DIR          := $(shell readlink -f ${SOURCEDIR}/_shims)
+
 .EXPORT_ALL_VARIABLES:
 
-PYTHONPATH = $(shell readlink -f ${CURDIR}/..)
+PYTHONPATH = ${_PROJ_DIR}:${_LIBS_DIR}
+PATH       = ${_SHIMS_DIR}:${_CURRENT_PATH}
 NO_CONTENTS_BUILD = true
 
 # Catch-all target: route all unknown targets to Sphinx using the new
diff --git a/docs/source/_libs/.keep b/docs/source/_libs/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/source/_libs/dm.py b/docs/source/_libs/dm.py
new file mode 100644
index 0000000000..e4daed3f86
--- /dev/null
+++ b/docs/source/_libs/dm.py
@@ -0,0 +1,14 @@
+import importlib.util
+import os
+from types import ModuleType
+
+
+def get_module(filename) -> ModuleType:
+    _, _simple_filename = os.path.split(filename)
+    _module_name, _ = _simple_filename.split('.', maxsplit=1)
+
+    spec = importlib.util.spec_from_file_location(_module_name, filename)
+    module = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(module)
+
+    return module
diff --git a/docs/source/_shims/treevalue b/docs/source/_shims/treevalue
new file mode 100755
index 0000000000..fe923a81d6
--- /dev/null
+++ b/docs/source/_shims/treevalue
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+from treevalue.entry.cli import treevalue_cli
+
+if __name__ == '__main__':
+    treevalue_cli()
diff --git a/docs/source/api_doc/config/meta.rst b/docs/source/api_doc/config/meta.rst
index b93b407462..f9aa2a32c2 100644
--- a/docs/source/api_doc/config/meta.rst
+++ b/docs/source/api_doc/config/meta.rst
@@ -3,4 +3,36 @@ treevalue.config.meta
 
 .. automodule:: treevalue.config.meta
 
+\_\_TITLE\_\_
+------------------
 
+.. autodata:: treevalue.config.meta.__TITLE__
+    :annotation:
+
+
+\_\_VERSION\_\_
+------------------
+
+.. autodata:: treevalue.config.meta.__VERSION__
+    :annotation:
+
+
+\_\_DESCRIPTION\_\_
+----------------------
+
+.. autodata:: treevalue.config.meta.__DESCRIPTION__
+    :annotation:
+
+
+\_\_AUTHOR\_\_
+------------------
+
+.. autodata:: treevalue.config.meta.__AUTHOR__
+    :annotation:
+
+
+\_\_AUTHOR_EMAIL\_\_
+----------------------
+
+.. autodata:: treevalue.config.meta.__AUTHOR_EMAIL__
+    :annotation:
diff --git a/docs/source/api_doc/tree/common.rst b/docs/source/api_doc/tree/common.rst
index 443946c104..f8aaea853f 100644
--- a/docs/source/api_doc/tree/common.rst
+++ b/docs/source/api_doc/tree/common.rst
@@ -7,7 +7,7 @@ BaseTree
 -----------
 
 .. autoclass:: treevalue.tree.common.base.BaseTree
-    :members: __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__
+    :members: __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__, copy_from
 
 
 .. _apidoc_tree_common_tree:
@@ -16,7 +16,7 @@ Tree
 ---------
 
 .. autoclass:: treevalue.tree.common.tree.Tree
-    :members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__
+    :members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__, copy_from
 
 
 .. _apidoc_tree_common_raw:
@@ -33,5 +33,5 @@ TreeView
 -------------
 
 .. autoclass:: treevalue.tree.common.view.TreeView
-    :members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__
+    :members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__, copy_from
 
diff --git a/docs/source/api_doc/tree/func.rst b/docs/source/api_doc/tree/func.rst
index 0adf6629e1..ab356e3408 100644
--- a/docs/source/api_doc/tree/func.rst
+++ b/docs/source/api_doc/tree/func.rst
@@ -50,20 +50,12 @@ TreeMode
 MISSING_NOT_ALLOW
 -------------------------
 
-.. data:: treevalue.tree.func.MISSING_NOT_ALLOW
+.. autodata:: treevalue.tree.func.func.MISSING_NOT_ALLOW
+    :annotation:
 
-    Default value of the ``missing`` arguments \
-    of ``func_treelize``, ``method_treelize`` and \
-    ``classmethod_treelize``, \
-    which means missing is not allowed \
-    (raise ``KeyError`` when missing is detected).
 
 AUTO_DETECT_RETURN_TYPE
 ----------------------------
 
-.. data:: treevalue.tree.func.AUTO_DETECT_RETURN_TYPE
-
-    Default value of the ``return_type`` arguments \
-    of ``method_treelize`` and ``classmethod_treelize``, \
-    which means return type will be auto configured to
-    the current class.
+.. autodata:: treevalue.tree.func.func.AUTO_DETECT_RETURN_TYPE
+    :annotation:
diff --git a/docs/source/api_doc/tree/general.rst b/docs/source/api_doc/tree/general.rst
index 2505d66cfc..73e515d77a 100644
--- a/docs/source/api_doc/tree/general.rst
+++ b/docs/source/api_doc/tree/general.rst
@@ -7,7 +7,7 @@ FastTreeValue
 -------------------
 
 .. autoclass:: treevalue.tree.general.FastTreeValue
-    :members: _attr_extern, json, view, clone, __add__, __radd__, __sub__, __rsub__, __mul__, __rmul__, __matmul__, __rmatmul__, __truediv__, __rtruediv__, __floordiv__, __rfloordiv__, __mod__, __rmod__, __pow__, __rpow__, __and__, __rand__, __or__, __ror__, __xor__, __rxor__, __lshift__, __rlshift__, __rshift__, __rrshift__, __pos__, __neg__, __invert__, __getitem__, __setitem__, __delitem__, __call__, __getattr__, __setattr__, __delattr__, __repr__, __iter__, __hash__, __eq__, map, type, mask, filter, __str__, reduce, rise, union, subside
+    :members: _attr_extern, json, view, clone, __add__, __radd__, __sub__, __rsub__, __mul__, __rmul__, __matmul__, __rmatmul__, __truediv__, __rtruediv__, __floordiv__, __rfloordiv__, __mod__, __rmod__, __pow__, __rpow__, __and__, __rand__, __or__, __ror__, __xor__, __rxor__, __lshift__, __rlshift__, __rshift__, __rrshift__, __pos__, __neg__, __invert__, __getitem__, __setitem__, __delitem__, __call__, __getattr__, __setattr__, __delattr__, __repr__, __iter__, __hash__, __eq__, map, type, mask, filter, __str__, reduce, rise, union, subside, __getstate__, __setstate__, __iadd__, __isub__, __imul__, __imatmul__, __ifloordiv__, __itruediv__, __ipow__, __imod__, __iand__, __ior__, __ixor__, __ilshift__, __irshift__, graph, graphics, func
 
 
 .. _apidoc_tree_general_generaltreevalue:
diff --git a/docs/source/api_doc/tree/graphics.demo.py b/docs/source/api_doc/tree/graphics.demo.py
new file mode 100644
index 0000000000..d6ebd88006
--- /dev/null
+++ b/docs/source/api_doc/tree/graphics.demo.py
@@ -0,0 +1,27 @@
+import numpy as np
+
+from treevalue import FastTreeValue, graphics, TreeValue
+
+
+class MyFastTreeValue(FastTreeValue):
+    pass
+
+
+if __name__ == '__main__':
+    t = MyFastTreeValue({
+        'a': 1,
+        'b': np.array([[5, 6], [7, 8]]),
+        'x': {
+            'c': 3,
+            'd': 4,
+            'e': np.array([[1, 2], [3, 4]])
+        },
+    })
+    t2 = TreeValue({'ppp': t.x, 'x': {'t': t, 'y': t.x}})
+
+    g = graphics(
+        (t, 't'), (t2, 't2'),
+        title="This is a demo of 2 trees.",
+        cfg={'bgcolor': '#ffffff00'},
+    )
+    g.render('graphics.dat.gv', format='svg')
diff --git a/docs/source/api_doc/tree/graphics_dup_value.demo.py b/docs/source/api_doc/tree/graphics_dup_value.demo.py
new file mode 100644
index 0000000000..592e9c4a87
--- /dev/null
+++ b/docs/source/api_doc/tree/graphics_dup_value.demo.py
@@ -0,0 +1,40 @@
+import numpy as np
+
+from treevalue import FastTreeValue, graphics
+
+
+class MyFastTreeValue(FastTreeValue):
+    pass
+
+
+if __name__ == '__main__':
+    t = MyFastTreeValue({
+        'a': [4, 3, 2, 1],
+        'b': np.array([[5, 6], [7, 8]]),
+        'x': {
+            'c': np.array([[5, 7], [8, 6]]),
+            'd': {'a', 'b', 'c'},
+            'e': np.array([[1, 2], [3, 4]])
+        },
+    })
+    t1 = MyFastTreeValue({
+        'aa': t.a,
+        'bb': np.array([[5, 6], [7, 8]]),
+        'xx': {
+            'cc': t.x.c,
+            'dd': t.x.d,
+            'ee': np.array([[1, 2], [3, 4]])
+        },
+    })
+
+    g = graphics(
+        (t, 't'), (t1, 't1'),
+        (MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+        # Here is the dup value, with several types
+        # np.ndarray and list type will use the same value node,
+        # but set type is not in this tuple, so will not share the same node.
+        dup_value=(np.ndarray, list),
+        title="This is a demo of 2 trees with dup value.",
+        cfg={'bgcolor': '#ffffff00'},
+    )
+    g.render('graphics_dup_value.dat.gv', format='svg')
diff --git a/docs/source/api_doc/tree/tree.rst b/docs/source/api_doc/tree/tree.rst
index 3e900466b3..7a25371655 100644
--- a/docs/source/api_doc/tree/tree.rst
+++ b/docs/source/api_doc/tree/tree.rst
@@ -7,7 +7,7 @@ TreeValue
 ---------------
 
 .. autoclass:: treevalue.tree.tree.tree.TreeValue
-    :members: __init__, __getattr__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__
+    :members: __init__, __getattr__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__, __getstate__, __setstate__
 
 
 .. _apidoc_tree_tree_jsonify:
@@ -101,8 +101,87 @@ reduce\_
 NO_RISE_TEMPLATE
 --------------------
 
-.. data:: treevalue.tree.tree.utils.NO_RISE_TEMPLATE
+.. autodata:: treevalue.tree.tree.utils.NO_RISE_TEMPLATE
+    :annotation:
+
+
+.. _apidoc_tree_tree_graphics:
+
+graphics
+----------------
+
+.. autofunction:: treevalue.tree.tree.graph.graphics
+
+Here is an example of ``graphics`` function. The source code is
+
+.. literalinclude:: graphics.demo.py
+    :language: python
+    :linenos:
+
+The generated graphviz source code should be
+
+.. literalinclude:: graphics.dat.gv
+    :language: text
+    :linenos:
+
+The graph should be
+
+.. image:: graphics.dat.gv.svg
+    :align: center
+
+Also, ``graphics`` function can support value duplication. For if \
+the value nodes are using the same object, they will be displayed \
+in the same node of the generated graph, such as the source code
+below
+
+.. literalinclude:: graphics_dup_value.demo.py
+    :language: python
+    :linenos:
+
+The graph of the case with ``dup_value`` should be
+
+.. image:: graphics_dup_value.dat.gv.svg
+    :align: center
+
+The return value's type of function ``graphics`` is \
+class ``graphviz.dot.Digraph``, from the opensource \
+library ``graphviz``, for further information of \
+this project and ``graphviz.dot.Digraph``'s usage, \
+take a look at:
+
+* `Official site of Graphviz <https://graphviz.org/>`_.
+* `User Guide of Graphviz <https://graphviz.readthedocs.io/en/stable/manual.html#formats>`_.
+* `API Reference of Graphviz <https://graphviz.readthedocs.io/en/stable/api.html>`_.
+
+
+.. _apidoc_tree_tree_dump:
+
+dump
+-------------
+
+.. autofunction:: treevalue.tree.tree.io.dump
+
+
+.. _apidoc_tree_tree_dumps:
+
+dumps
+-------------
+
+.. autofunction:: treevalue.tree.tree.io.dumps
+
+
+.. _apidoc_tree_tree_load:
+
+load
+-------------
+
+.. autofunction:: treevalue.tree.tree.io.load
+
+
+.. _apidoc_tree_tree_loads:
+
+loads
+-------------
+
+.. autofunction:: treevalue.tree.tree.io.loads
 
-    Means no template is given to the rise function, \
-    and the decorated function will automatically try \
-    to match the format patterns as template.
diff --git a/docs/source/api_doc/utils/build_graph.demo.py b/docs/source/api_doc/utils/build_graph.demo.py
new file mode 100644
index 0000000000..33d8656509
--- /dev/null
+++ b/docs/source/api_doc/utils/build_graph.demo.py
@@ -0,0 +1,8 @@
+from treevalue.utils import build_graph
+
+if __name__ == '__main__':
+    t = {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}}
+    g = build_graph((t, 't'), graph_title="Demo of build_graph.")
+
+    print(g.source)
+    print(g.render('build_graph_demo.dat.gv', format='svg'))
diff --git a/docs/source/api_doc/utils/build_graph_complex.demo.py b/docs/source/api_doc/utils/build_graph_complex.demo.py
new file mode 100644
index 0000000000..698ac28b4a
--- /dev/null
+++ b/docs/source/api_doc/utils/build_graph_complex.demo.py
@@ -0,0 +1,9 @@
+from treevalue.utils import build_graph
+
+if __name__ == '__main__':
+    t1 = {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}}
+    t2 = {'f': 4, 'y': t1['x'], 'z': {'e': [5, 7], 'f': "string"}}
+    g = build_graph((t1, 't1'), (t2, 't2'), graph_title="Complex demo of build_graph.")
+
+    print(g.source)
+    print(g.render('build_graph_complex_demo.dat.gv', format='svg'))
diff --git a/docs/source/api_doc/utils/clazz.rst b/docs/source/api_doc/utils/clazz.rst
index 40d4aaaf10..300ff204b4 100644
--- a/docs/source/api_doc/utils/clazz.rst
+++ b/docs/source/api_doc/utils/clazz.rst
@@ -24,3 +24,9 @@ common_direct_base
 
 .. autofunction:: treevalue.utils.clazz.common_direct_base
 
+
+get_class_full_name
+----------------------
+
+.. autofunction:: treevalue.utils.clazz.get_class_full_name
+
diff --git a/docs/source/api_doc/utils/color.rst b/docs/source/api_doc/utils/color.rst
new file mode 100644
index 0000000000..6d14174814
--- /dev/null
+++ b/docs/source/api_doc/utils/color.rst
@@ -0,0 +1,10 @@
+treevalue.utils.color
+===========================
+
+Color
+----------------
+
+.. autoclass:: treevalue.utils.color.Color
+    :members: __init__, alpha, rgb, hsv, hls, __repr__, __str__, __getstate__, __setstate__, __hash__, __eq__, from_hsv, from_hls
+
+
diff --git a/docs/source/api_doc/utils/exception.rst b/docs/source/api_doc/utils/exception.rst
new file mode 100644
index 0000000000..79c0f6d7b0
--- /dev/null
+++ b/docs/source/api_doc/utils/exception.rst
@@ -0,0 +1,8 @@
+treevalue.utils.exception
+================================
+
+str_traceback
+-----------------
+
+.. autofunction:: treevalue.utils.exception.str_traceback
+
diff --git a/docs/source/api_doc/utils/func.rst b/docs/source/api_doc/utils/func.rst
index b9b8482b84..eded631ff4 100644
--- a/docs/source/api_doc/utils/func.rst
+++ b/docs/source/api_doc/utils/func.rst
@@ -12,3 +12,39 @@ dynamic_call
 
 .. autofunction:: treevalue.utils.func.dynamic_call
 
+
+static_call
+------------------
+
+.. autofunction:: treevalue.utils.func.static_call
+
+
+pre_process
+------------------
+
+.. autofunction:: treevalue.utils.func.pre_process
+
+
+post_process
+------------------
+
+.. autofunction:: treevalue.utils.func.post_process
+
+
+raising
+-------------------
+
+.. autofunction:: treevalue.utils.func.raising
+
+
+warning\_
+---------------------
+
+.. autofunction:: treevalue.utils.func.warning_
+
+
+freduce
+--------------
+
+.. autofunction:: treevalue.utils.func.freduce
+
diff --git a/docs/source/api_doc/utils/imports.rst b/docs/source/api_doc/utils/imports.rst
new file mode 100644
index 0000000000..c3efc5a773
--- /dev/null
+++ b/docs/source/api_doc/utils/imports.rst
@@ -0,0 +1,20 @@
+treevalue.utils.imports
+============================
+
+import\_object
+---------------------
+
+.. autofunction:: treevalue.utils.imports.import_object
+
+
+quick\_import\_object
+----------------------------
+
+.. autofunction:: treevalue.utils.imports.quick_import_object
+
+
+iter\_import\_objects
+-------------------------
+
+.. autofunction:: treevalue.utils.imports.iter_import_objects
+
diff --git a/docs/source/api_doc/utils/index.rst b/docs/source/api_doc/utils/index.rst
index 0d7a8b04ac..103f54ea12 100644
--- a/docs/source/api_doc/utils/index.rst
+++ b/docs/source/api_doc/utils/index.rst
@@ -5,8 +5,12 @@ treevalue.utils
     :maxdepth: 3
 
     clazz
+    color
     enum
+    exception
     final
     func
+    imports
+    random
     singleton
     tree
diff --git a/docs/source/api_doc/utils/random.rst b/docs/source/api_doc/utils/random.rst
new file mode 100644
index 0000000000..81a2ca08b2
--- /dev/null
+++ b/docs/source/api_doc/utils/random.rst
@@ -0,0 +1,20 @@
+treevalue.utils.random
+============================
+
+seed_random
+------------------
+
+.. autofunction:: treevalue.utils.random.seed_random
+
+
+random_hex
+-------------------
+
+.. autofunction:: treevalue.utils.random.random_hex
+
+
+random_hex_with_timestamp
+---------------------------
+
+.. autofunction:: treevalue.utils.random.random_hex_with_timestamp
+
diff --git a/docs/source/api_doc/utils/tree.rst b/docs/source/api_doc/utils/tree.rst
index f6d5a132c4..79648b5096 100644
--- a/docs/source/api_doc/utils/tree.rst
+++ b/docs/source/api_doc/utils/tree.rst
@@ -5,3 +5,48 @@ build_tree
 -----------------
 
 .. autofunction:: treevalue.utils.tree.build_tree
+
+
+build_graph
+-------------------
+
+.. autofunction:: treevalue.utils.tree.build_graph
+
+Here is an example of ``build_graph`` function. The source code is
+
+.. literalinclude:: build_graph.demo.py
+    :language: python
+    :linenos:
+
+The generated graphviz source code should be
+
+.. literalinclude:: build_graph_demo.dat.gv
+    :language: text
+    :linenos:
+
+The graph should be
+
+.. image:: build_graph_demo.dat.gv.svg
+    :align: center
+
+Also, multiple rooted graph is supported, this function will detect
+the pointer of the objects. Just like another complex source code below.
+
+.. literalinclude:: build_graph_complex.demo.py
+    :language: python
+    :linenos:
+
+The exported graph should be
+
+.. image:: build_graph_complex_demo.dat.gv.svg
+    :align: center
+
+The return value's type of function ``graphics`` is \
+class ``graphviz.dot.Digraph``, from the opensource \
+library ``graphviz``, for further information of \
+this project and ``graphviz.dot.Digraph``'s usage, \
+take a look at:
+
+* `Official site of Graphviz <https://graphviz.org/>`_.
+* `User Guide of Graphviz <https://graphviz.readthedocs.io/en/stable/manual.html#formats>`_.
+* `API Reference of Graphviz <https://graphviz.readthedocs.io/en/stable/api.html>`_.
diff --git a/docs/source/best_practice/numpy/with_treevalue.py b/docs/source/best_practice/numpy/with_treevalue.py
index 2076e6e18b..195e857062 100644
--- a/docs/source/best_practice/numpy/with_treevalue.py
+++ b/docs/source/best_practice/numpy/with_treevalue.py
@@ -1,22 +1,17 @@
 import numpy as np
 
-from treevalue import FastTreeValue, func_treelize
+from treevalue import FastTreeValue
 
 T, B = 3, 4
-power = func_treelize()(np.power)
-stack = func_treelize(subside=True)(np.stack)
-split = func_treelize(rise=True)(np.split)
-
-
-@func_treelize()
-def astype(item, *args):
-    return item.astype(*args)
+power = FastTreeValue.func()(np.power)
+stack = FastTreeValue.func(subside=True)(np.stack)
+split = FastTreeValue.func(rise=True)(np.split)
 
 
 def with_treevalue(batch_):
     batch_ = [FastTreeValue(b) for b in batch_]
     batch_ = stack(batch_)
-    batch_ = astype(batch_, np.float32)
+    batch_ = batch_.astype(np.float32)
     batch_.b = power(batch_.b, 2) + 1.0
     batch_.c.noise = np.random.random(size=(B, 3, 4, 5))
     mean_b = batch_.b.mean()
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f35d8896d1..945f967bb1 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -28,6 +28,8 @@
 # Get current location
 _DOC_PATH = os.path.dirname(os.path.abspath(__file__))
 _PROJ_PATH = os.path.abspath(os.path.join(_DOC_PATH, '..', '..'))
+_LIBS_PATH = os.path.join(_DOC_PATH, '_libs')
+_SHIMS_PATH = os.path.join(_DOC_PATH, '_shims')
 os.chdir(_PROJ_PATH)
 
 # Set environment, remove the pre-installed package
@@ -40,7 +42,8 @@
 if not os.environ.get("NO_CONTENTS_BUILD"):
     _env = dict(os.environ)
     _env.update(dict(
-        PYTHONPATH=_PROJ_PATH,
+        PYTHONPATH=':'.join([_PROJ_PATH, _LIBS_PATH]),
+        PATH=':'.join([_SHIMS_PATH, os.environ.get('PATH', '')]),
     ))
 
     pip_cmd = (where.first('pip'), 'install', '-r', os.path.join(_PROJ_PATH, 'requirements.txt'))
diff --git a/docs/source/demos.mk b/docs/source/demos.mk
index 5c13f09390..e4bd6e03b3 100644
--- a/docs/source/demos.mk
+++ b/docs/source/demos.mk
@@ -23,10 +23,12 @@ SHELL_RESULTS  := $(addsuffix .sh.txt, $(basename ${SHELL_DEMOS} ${SHELL_DEMOXS}
 
 %.demo.sh.txt: %.demo.sh
 	cd "$(shell dirname $(shell readlink -f $<))" && \
+		PYTHONPATH="$(shell dirname $(shell readlink -f $<)):${PYTHONPATH}" \
 		$(SHELL) "$(shell readlink -f $<)" > "$(shell readlink -f $@)"
 
 %.demox.sh.txt: %.demox.sh
 	cd "$(shell dirname $(shell readlink -f $<))" && \
+		PYTHONPATH="$(shell dirname $(shell readlink -f $<)):${PYTHONPATH}" \
 		$(SHELL) "$(shell readlink -f $<)" 1> "$(shell readlink -f $@)" \
 		2> "$(shell readlink -f $(addsuffix .err, $(basename $@)))"; \
 		echo $$? > "$(shell readlink -f $(addsuffix .exitcode, $(basename $@)))"
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 1535891c30..a7b078ca63 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -18,6 +18,7 @@ structure processing when the calculation is tree-based.
     tutorials/main_idea/index
     tutorials/basic_usage/index
     tutorials/advanced_usage/index
+    tutorials/cli_usage/index
 
 
 .. toctree::
diff --git a/docs/source/tutorials/advanced_usage/diy_class_demo.demo.py b/docs/source/tutorials/advanced_usage/diy_class_demo.demo.py
index 2c155606ef..96926c1a0a 100644
--- a/docs/source/tutorials/advanced_usage/diy_class_demo.demo.py
+++ b/docs/source/tutorials/advanced_usage/diy_class_demo.demo.py
@@ -1,3 +1,5 @@
+import os
+
 from treevalue import classmethod_treelize, TreeValue, method_treelize
 
 
@@ -21,8 +23,8 @@ def sum(cls, *args):
     t2 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
     t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
 
-    print('t1.append(t2).append(t3):')
-    print(t1.append(t2).append(t3))
+    print('t1.append(t2).append(t3):',
+          t1.append(t2).append(t3), sep=os.linesep)
 
-    print('MyTreeValue.sum(t1, t2, t3):')
-    print(MyTreeValue.sum(t1, t2, t3))
+    print('MyTreeValue.sum(t1, t2, t3):',
+          MyTreeValue.sum(t1, t2, t3), sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/diy_class_self_demo.demo.py b/docs/source/tutorials/advanced_usage/diy_class_self_demo.demo.py
new file mode 100644
index 0000000000..80ef0480e5
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/diy_class_self_demo.demo.py
@@ -0,0 +1,23 @@
+import os
+
+from treevalue import TreeValue, method_treelize
+
+
+class MyTreeValue(TreeValue):
+    # return type will be automatically detected as `MyTreeValue`
+    @method_treelize(self_copy=True)
+    def append(self, b):
+        return self + b
+
+
+if __name__ == '__main__':
+    t1 = MyTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+    t2 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
+    t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
+
+    print('t1:', t1, sep=os.linesep)
+    _t1_id = id(t1)
+
+    print('t1.append(t2).append(t3):',
+          t1.append(t2).append(t3), sep=os.linesep)
+    assert id(t1) == _t1_id
diff --git a/docs/source/tutorials/advanced_usage/diy_class_x_demo_1.demo.py b/docs/source/tutorials/advanced_usage/diy_class_x_demo_1.demo.py
new file mode 100644
index 0000000000..090639e206
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/diy_class_x_demo_1.demo.py
@@ -0,0 +1,15 @@
+import os
+
+from treevalue import general_tree_value
+
+
+class MyTreeValue(general_tree_value()):
+    pass
+
+
+if __name__ == '__main__':
+    t1 = MyTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+    t2 = MyTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
+
+    # __add__ operator can be directly used
+    print('t1 + t2:', t1 + t2, sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/diy_class_x_demo_2.demo.py b/docs/source/tutorials/advanced_usage/diy_class_x_demo_2.demo.py
new file mode 100644
index 0000000000..9699494772
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/diy_class_x_demo_2.demo.py
@@ -0,0 +1,34 @@
+import os
+
+from treevalue import general_tree_value, FastTreeValue
+
+
+class AddZeroTreeValue(general_tree_value(methods=dict(
+    __add__=dict(missing=0, mode='outer'),
+    __radd__=dict(missing=0, mode='outer'),
+    __iadd__=dict(missing=0, mode='outer'),
+))):
+    pass
+
+
+class MulOneTreeValue(general_tree_value(methods=dict(
+    __mul__=dict(missing=1, mode='outer'),
+    __rmul__=dict(missing=1, mode='outer'),
+    __imul__=dict(missing=1, mode='outer'),
+))):
+    pass
+
+
+if __name__ == '__main__':
+    t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3}})
+    t2 = FastTreeValue({'a': 11, 'x': {'c': 30, 'd': 47}})
+
+    # __add__ with default value 0
+    at1 = t1.type(AddZeroTreeValue)
+    at2 = t2.type(AddZeroTreeValue)
+    print('at1 + at2:', at1 + at2, sep=os.linesep)
+
+    # __mul__ with default value 1
+    mt1 = t1.type(MulOneTreeValue)
+    mt2 = t2.type(MulOneTreeValue)
+    print('mt1 * mt2:', mt1 * mt2, sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/diy_class_x_demo_3.demo.py b/docs/source/tutorials/advanced_usage/diy_class_x_demo_3.demo.py
new file mode 100644
index 0000000000..3d9b4437bb
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/diy_class_x_demo_3.demo.py
@@ -0,0 +1,47 @@
+import os
+
+from treevalue import general_tree_value
+
+
+class DemoTreeValue(general_tree_value(methods=dict(
+    # - operator will be disabled
+    __sub__=NotImplemented,
+    __rsub__=NotImplemented,
+    __isub__=NotImplemented,
+
+    # / operator will raise ArithmeticError
+    __truediv__=ArithmeticError('True div is not supported'),
+    __rtruediv__=ArithmeticError('True div is not supported'),
+    __itruediv__=ArithmeticError('True div is not supported'),
+
+    # +t will be changed to t * 2
+    __pos__=lambda x: x * 2,
+))):
+    pass
+
+
+if __name__ == '__main__':
+    t1 = DemoTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+    t2 = DemoTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
+
+    # __add__ can be used normally
+    print('t1 + t2:', t1 + t2, sep=os.linesep)
+
+    # __sub__ will cause TypeError due to the NotImplemented
+    try:
+        _ = t1 - t2
+    except TypeError as err:
+        print('t1 - t2:', err, sep=os.linesep)
+    else:
+        assert False, 'Should not reach here!'
+
+    # __truediv__ will cause ArithmeticError
+    try:
+        _ = t1 / t2
+    except ArithmeticError as err:
+        print('t1 / t2:', err, sep=os.linesep)
+    else:
+        assert False, 'Should not reach here!'
+
+    # __pos__ will be like t1 * 2
+    print('+t1:', +t1, sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/diy_class_x_tv.demo.py b/docs/source/tutorials/advanced_usage/diy_class_x_tv.demo.py
new file mode 100644
index 0000000000..6dc146b5b6
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/diy_class_x_tv.demo.py
@@ -0,0 +1,8 @@
+import pathlib
+import sys
+
+from treevalue import FastTreeValue
+
+if __name__ == '__main__':
+    _module = sys.modules[FastTreeValue.__module__]
+    print(pathlib.Path(_module.__file__).read_text())
diff --git a/docs/source/tutorials/advanced_usage/dump_compression_demo.demo.py b/docs/source/tutorials/advanced_usage/dump_compression_demo.demo.py
new file mode 100644
index 0000000000..d81e391843
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/dump_compression_demo.demo.py
@@ -0,0 +1,20 @@
+import gzip
+import os
+
+from treevalue import FastTreeValue, dumps, loads
+
+if __name__ == '__main__':
+    t = FastTreeValue({'a': 'ab', 'b': 'Cd', 'x': {'c': 'eF', 'd': 'GH'}})
+    st = t.upper
+    print('st:', st, sep=os.linesep)  # st is a function tree
+    print('st():', st(), sep=os.linesep)  # st() if an upper-case-string tree
+    print()
+
+    binary = dumps(st, compress=(gzip.compress, gzip.decompress))
+    print('Length of binary:', len(binary))
+
+    # compression function is not needed here
+    dst = loads(binary, type_=FastTreeValue)
+    print('dst:', dst, sep=os.linesep)
+    assert st() == dst()  # st() should be equal to dst()
+    print('dst():', dst(), sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/dump_demo_1.demo.py b/docs/source/tutorials/advanced_usage/dump_demo_1.demo.py
new file mode 100644
index 0000000000..eea16d71a9
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/dump_demo_1.demo.py
@@ -0,0 +1,18 @@
+import os
+import tempfile
+
+from treevalue import TreeValue, dump, load
+
+if __name__ == '__main__':
+    t = TreeValue({'a': 'ab', 'b': 'Cd', 'x': {'c': 'eF', 'd': 'GH'}})
+    print('t:', t, sep=os.linesep)
+
+    with tempfile.NamedTemporaryFile() as tf:
+        with open(tf.name, 'wb') as wf:  # dump t to file
+            dump(t, file=wf)
+
+        with open(tf.name, 'rb') as rf:  # load dt from file
+            dt = load(file=rf)
+
+    assert dt == t
+    print('dt:', dt, sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/dump_demo_2.demo.py b/docs/source/tutorials/advanced_usage/dump_demo_2.demo.py
new file mode 100644
index 0000000000..cdc3877ce7
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/dump_demo_2.demo.py
@@ -0,0 +1,13 @@
+import os
+
+from treevalue import dumps, FastTreeValue, loads
+
+if __name__ == '__main__':
+    t = FastTreeValue({'a': 'ab', 'b': 'Cd', 'x': {'c': 'eF', 'd': 'GH'}})
+    print('t:', t, sep=os.linesep)
+
+    binary = dumps(t)
+    dt = loads(binary, type_=FastTreeValue)
+
+    assert dt == t
+    print('dt:', dt, sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/index.rst b/docs/source/tutorials/advanced_usage/index.rst
index cbaf4bfb15..0b0ea47edc 100644
--- a/docs/source/tutorials/advanced_usage/index.rst
+++ b/docs/source/tutorials/advanced_usage/index.rst
@@ -914,6 +914,83 @@ For further informaon of function ``typetrans``, \
 take a look at :ref:`apidoc_tree_tree_typetrans`.
 
 
+IO Utilities
+-----------------------
+
+Simple Serialize and Deserialize
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In this version, ``pickle.dumps`` and ``pickle.loads`` operations \
+are supported, just like the code below.
+
+.. literalinclude:: pickle_demo_1.demo.py
+    :language: python
+
+The output is
+
+.. literalinclude:: pickle_demo_1.demo.py.txt
+    :language: text
+
+
+Advanced Dump and Load
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In this project, we implemented our ``load`` and ``dump`` \
+function which can be used like this
+
+.. literalinclude:: dump_demo_1.demo.py
+    :language: python
+
+The output should be
+
+.. literalinclude:: dump_demo_1.demo.py.txt
+    :language: text
+
+.. note::
+    In this ``load`` (``loads`` function is the same), \
+    you must assign a ``TreeValue`` class by the ``type_`` \
+    argument because the dumped data will not \
+    save the type of trees. When the type is not assigned, the \
+    default class will be simply ``TreeValue``.
+
+
+Also, ``dumps`` function and ``loads`` function are provided \
+to deal with binary data.
+
+.. literalinclude:: dump_demo_2.demo.py
+    :language: python
+
+The output should be
+
+.. literalinclude:: dump_demo_2.demo.py.txt
+    :language: text
+
+.. note::
+
+    The main differences between our ``dump`` / ``load`` and \
+    ``pickle`` 's ``dump`` / ``load`` are:
+
+    * Ours are based on \
+      `dill <https://github.com/uqfoundation/dill>`_ project, \
+      it can serialize and deserialize more types than \
+      native ``pickle``, such as functions and classes.
+    * Compression and decompression can be defined when dumping, \
+      and when the compressed binary data is going to be loaded, \
+      decompression function is not necessary, the decompression \
+      can be carried on with the function defined in dumping \
+      process.
+
+    Here is an example:
+
+    .. literalinclude:: dump_compression_demo.demo.py
+        :language: python
+
+    The output should be
+
+    .. literalinclude:: dump_compression_demo.demo.py.txt
+        :language: text
+
+
 Object Oriented Usage
 ----------------------------
 
@@ -955,6 +1032,24 @@ The result should be like below.
     :language: text
     :linenos:
 
+.. note::
+
+    Actually, self-calculation method can be defined like the \
+    code below
+
+    .. literalinclude:: diy_class_self_demo.demo.py
+        :language: python
+
+    The result should be
+
+    .. literalinclude:: diy_class_self_demo.demo.py.txt
+        :language: text
+
+    We can see that the calculation result will replace the \
+    ``self`` object, without changes of the address. Just \
+    enable the ``self_copy`` option is okay.
+
+
 For further information of function ``method_treelize`` \
 and ``classmethod_treelize``, just take a look at:
 
@@ -962,6 +1057,78 @@ and ``classmethod_treelize``, just take a look at:
 * :ref:`apidoc_tree_func_classmethodtreelize`
 
 
+More Flexible Ways to DIY Class
+---------------------------------
+
+When you implement ``TreeValue`` class like the section above \
+(take a look at here :ref:`tutorials_advancedusage_diy`), \
+you have to define your own methods and operators one by one, \
+because in class ``TreeValue``, only the minimum necessary \
+methods and operators are provided. But when you use \
+``FastTreeValue`` class, operators like ``+`` and methods like \
+``map`` can be used directly. Let's see how ``FastTreeValue`` \
+is defined in source code:
+
+.. literalinclude:: diy_class_x_tv.demo.py.txt
+    :language: python
+    :linenos:
+
+The definition is quite simple, just inherit from the \
+return value of function ``general_tree_value``, and most of \
+the operators and methods can be used.
+
+Like the ``FastTreeValue`` class, we can define our ``TreeValue`` \
+class like the code below:
+
+.. literalinclude:: diy_class_x_demo_1.demo.py
+    :language: python
+    :linenos:
+
+The output should be
+
+.. literalinclude:: diy_class_x_demo_1.demo.py.txt
+    :language: text
+    :linenos:
+
+
+In some cases, you can update the behaviours of some \
+operator or methods, like the code below
+
+.. literalinclude:: diy_class_x_demo_2.demo.py
+    :language: python
+    :linenos:
+
+The output should be
+
+.. literalinclude:: diy_class_x_demo_2.demo.py.txt
+    :language: text
+    :linenos:
+
+More than this, you can do the following things \
+in your DIY ``TreeValue`` class based on \
+function ``general_tree_value``:
+
+- change treelize arguments (like the example code above)
+- disable some operators and methods
+- change behaviour of some operators and methods
+
+Here is another complex demo code
+
+.. literalinclude:: diy_class_x_demo_3.demo.py
+    :language: python
+    :linenos:
+
+The output should be
+
+.. literalinclude:: diy_class_x_demo_3.demo.py.txt
+    :language: text
+    :linenos:
+
+For more details about ``general_tree_value``, \
+take a look at :ref:`apidoc_tree_general_generaltreevalue` \
+and its source code to find out all the implemented \
+operators and methods.
+
 
 DIY TreeValue Utility Class
 -------------------------------
@@ -986,3 +1153,43 @@ and ``classmethod_treelize``, just take a look at:
 * :ref:`apidoc_tree_func_utilsclass`
 * :ref:`apidoc_tree_func_classmethodtreelize`
 
+
+Draw Graph For TreeValue
+-----------------------------
+
+You can easily draw a graph of the ``TreeValue`` objects \
+with function ``graphics``. Like the following example \
+code.
+
+.. literalinclude:: ../../api_doc/tree/graphics.demo.py
+    :language: python
+    :linenos:
+
+The generated code and graph should be like below ( \
+name of the image file should be ``graphics.dat.gv.svg``, \
+name of the graphviz source code file should be \
+``graphics.dat.gv`` ).
+
+.. literalinclude:: ../../api_doc/tree/graphics.dat.gv
+    :language: text
+    :linenos:
+
+.. image:: ../../api_doc/tree/graphics.dat.gv.svg
+    :align: center
+
+.. note::
+
+    The return value's type of function ``graphics`` is \
+    class ``graphviz.dot.Digraph``, from the opensource \
+    library ``graphviz``, for further information of \
+    this project and ``graphviz.dot.Digraph``'s usage, \
+    take a look at:
+
+    * `Official site of Graphviz <https://graphviz.org/>`_.
+    * `User Guide of Graphviz <https://graphviz.readthedocs.io/en/stable/manual.html#formats>`_.
+    * `API Reference of Graphviz <https://graphviz.readthedocs.io/en/stable/api.html>`_.
+
+For further information of function ``graphics``, \
+just take a look at
+
+* :ref:`apidoc_tree_tree_graphics`.
diff --git a/docs/source/tutorials/advanced_usage/pickle_demo_1.demo.py b/docs/source/tutorials/advanced_usage/pickle_demo_1.demo.py
new file mode 100644
index 0000000000..1082c49034
--- /dev/null
+++ b/docs/source/tutorials/advanced_usage/pickle_demo_1.demo.py
@@ -0,0 +1,13 @@
+import os
+import pickle
+
+from treevalue import TreeValue
+
+if __name__ == '__main__':
+    t = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': [3, 4]}})
+    binary = pickle.dumps(t)
+    print('t:', t, sep=os.linesep)
+
+    tx = pickle.loads(binary)
+    assert tx == t
+    print('tx:', tx, sep=os.linesep)
diff --git a/docs/source/tutorials/advanced_usage/rise_demo_1.demo.py b/docs/source/tutorials/advanced_usage/rise_demo_1.demo.py
index 0c86247ac1..c6801c2b1b 100644
--- a/docs/source/tutorials/advanced_usage/rise_demo_1.demo.py
+++ b/docs/source/tutorials/advanced_usage/rise_demo_1.demo.py
@@ -7,7 +7,7 @@
     t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
     t4 = TreeValue({'a': 0, 'b': -17, 'x': {'c': -8, 'd': 15}})
     t5 = TreeValue({'a': 3, 'b': 9, 'x': {'c': 11, 'd': -17}})
-    st = {'first': (t1, t2), 'second': [t3, {'x': t4, 'y': t5}]}
+    st = {'first': (t1, t2), 'second': (t3, {'x': t4, 'y': t5})}
     tx = subside(st)
 
     # Rising process
diff --git a/docs/source/tutorials/advanced_usage/rise_demo_2.demo.py b/docs/source/tutorials/advanced_usage/rise_demo_2.demo.py
index 7c47e390f4..138782f30a 100644
--- a/docs/source/tutorials/advanced_usage/rise_demo_2.demo.py
+++ b/docs/source/tutorials/advanced_usage/rise_demo_2.demo.py
@@ -2,23 +2,28 @@
 
 if __name__ == '__main__':
     # The same demo as the subside docs
-    t1 = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-    t2 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
+    t11 = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+    t12 = TreeValue({'a': 3, 'b': 9, 'x': {'c': 15, 'd': 41}})
+    t21 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
+    t22 = TreeValue({'a': -31, 'b': 82, 'x': {'c': 47, 'd': 32}})
     t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
     t4 = TreeValue({'a': 0, 'b': -17, 'x': {'c': -8, 'd': 15}})
     t5 = TreeValue({'a': 3, 'b': 9, 'x': {'c': 11, 'd': -17}})
-    st = {'first': (t1, t2), 'second': [t3, {'x': t4, 'y': t5}]}
+    st = {'first': [(t11, t21), (t12, t22)], 'second': (t3, {'x': t4, 'y': t5})}
     tx = subside(st)
 
     # Rising process, with template
     # only the top-leveled dict will be extracted,
     # neither tuple, list nor low-leveled dict will not be extracted
     # because they are not defined in `template` argument
-    st2 = rise(tx, template={'first': None, 'second': [None, None]})
+    st2 = rise(tx, template={'first': [None], 'second': (None, None)})
     print('st2:', st2)
 
-    print("st2['first']:")
-    print(st2['first'])
+    print("st2['first'][0]:")
+    print(st2['first'][0])
+
+    print("st2['first'][1]:")
+    print(st2['first'][1])
 
     print("st2['second'][0]:")
     print(st2['second'][0])
diff --git a/docs/source/tutorials/basic_usage/calculation_self.demo.py b/docs/source/tutorials/basic_usage/calculation_self.demo.py
new file mode 100644
index 0000000000..082f37b074
--- /dev/null
+++ b/docs/source/tutorials/basic_usage/calculation_self.demo.py
@@ -0,0 +1,19 @@
+import os
+
+from treevalue import FastTreeValue
+
+if __name__ == '__main__':
+    t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+    t2 = FastTreeValue({'a': 3, 'b': 7, 'x': {'c': 14, 'd': -5}})
+
+    print('t1:', t1, sep=os.linesep)
+    print('t2:', t2, sep=os.linesep)
+    print('t1 + t2:', t1 + t2, sep=os.linesep)
+    _original_ids = (id(t1), id(t2))
+    print()
+
+    t1 += t2
+    print('After t1 += t2')
+    print('t1:', t1, sep=os.linesep)
+    print('t2:', t2, sep=os.linesep)
+    assert (id(t1), id(t2)) == _original_ids
diff --git a/docs/source/tutorials/basic_usage/index.rst b/docs/source/tutorials/basic_usage/index.rst
index e5ebad2fa9..ac36187ef4 100644
--- a/docs/source/tutorials/basic_usage/index.rst
+++ b/docs/source/tutorials/basic_usage/index.rst
@@ -19,11 +19,6 @@ The result should be
     :language: text
     :linenos:
 
-A simple tree value structure is created successfully with the structure below.
-
-.. image:: ../quick_start/create_a_tree.gv.svg
-    :align: center
-
 
 Edit the tree
 ------------------
@@ -99,6 +94,23 @@ The structures of the trees involved in ``__add__`` calculation is like below.
 
 Actually, More common operators are supported in treevalue.
 
+.. note::
+
+    In newer versions of treevalue, self operations \
+    are supported like the code below.
+
+    .. literalinclude:: calculation_self.demo.py
+        :language: python
+
+    The output should be
+
+    .. literalinclude:: calculation_self.demo.py.txt
+        :language: text
+
+    We can see that when ``t1 + t2`` is called, \
+    a new tree with the sums will be created \
+    without ``t1``'s change, but when ``t1 += t2`` is called, \
+    the values in ``t1`` will be replaced to the sums.
 
 .. _tutorials_basicusage_func:
 
diff --git a/docs/source/tutorials/cli_usage/export_demo_1.demo.sh b/docs/source/tutorials/cli_usage/export_demo_1.demo.sh
new file mode 100644
index 0000000000..62217ec122
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/export_demo_1.demo.sh
@@ -0,0 +1,2 @@
+treevalue export -t 'tree_demo.t1' -o export_t1.dat.btv
+ls -al export_t1.dat.btv
diff --git a/docs/source/tutorials/cli_usage/export_demo_2.demo.sh b/docs/source/tutorials/cli_usage/export_demo_2.demo.sh
new file mode 100644
index 0000000000..a1d70d6ce7
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/export_demo_2.demo.sh
@@ -0,0 +1,2 @@
+treevalue export -t 'tree_demo.*' -o 'me_t1.dat.btv' -o 'me_t2.dat.btv' -o 'me_t3.dat.btv'
+ls -al me_*.btv
diff --git a/docs/source/tutorials/cli_usage/export_demo_3.demo.sh b/docs/source/tutorials/cli_usage/export_demo_3.demo.sh
new file mode 100644
index 0000000000..8aa1f2848a
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/export_demo_3.demo.sh
@@ -0,0 +1,2 @@
+treevalue export -t 'tree_demo.t1' -c gzip -o compress_t1.dat.btv
+ls -al compress_t1.dat.btv
diff --git a/docs/source/tutorials/cli_usage/export_demo_4.demo.sh b/docs/source/tutorials/cli_usage/export_demo_4.demo.sh
new file mode 100644
index 0000000000..c85665a7fa
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/export_demo_4.demo.sh
@@ -0,0 +1,2 @@
+treevalue export -t 'tree_demo.t1' -r -o raw_t1.dat.btv
+ls -al raw_t1.dat.btv
diff --git a/docs/source/tutorials/cli_usage/export_demo_5.demo.sh b/docs/source/tutorials/cli_usage/export_demo_5.demo.sh
new file mode 100644
index 0000000000..8d12b559d1
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/export_demo_5.demo.sh
@@ -0,0 +1,3 @@
+treevalue export -t 'large_tree_demo.t1' -r -o c_o_large_t1.dat.btv
+treevalue export -t 'large_tree_demo.t1'    -o c_x_large_t1.dat.btv
+ls -al c_*_large_t1.dat.btv
diff --git a/docs/source/tutorials/cli_usage/export_help_demo.demo.sh b/docs/source/tutorials/cli_usage/export_help_demo.demo.sh
new file mode 100644
index 0000000000..b261cfe759
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/export_help_demo.demo.sh
@@ -0,0 +1,1 @@
+treevalue export -h
\ No newline at end of file
diff --git a/docs/source/tutorials/cli_usage/graph_demo_1.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_1.demo.sh
new file mode 100644
index 0000000000..3509a48c3f
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_1.demo.sh
@@ -0,0 +1,1 @@
+treevalue graph -t 'tree_demo.t1' -o 'only_t1.dat.svg'
\ No newline at end of file
diff --git a/docs/source/tutorials/cli_usage/graph_demo_10.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_10.demo.sh
new file mode 100644
index 0000000000..62f9f72c85
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_10.demo.sh
@@ -0,0 +1,6 @@
+treevalue graph \
+  -t 'share_demo.*' \
+  -o 'shared_values.dat.svg' \
+  -T 'Shared Values' \
+  -c 'bgcolor=#ffffff00' \
+  -d list -d numpy.ndarray
diff --git a/docs/source/tutorials/cli_usage/graph_demo_2.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_2.demo.sh
new file mode 100644
index 0000000000..710798c2a3
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_2.demo.sh
@@ -0,0 +1,1 @@
+treevalue graph -t 'tree_demo.*' -o 't1_t2_t3.dat.svg'
\ No newline at end of file
diff --git a/docs/source/tutorials/cli_usage/graph_demo_3.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_3.demo.sh
new file mode 100644
index 0000000000..9e705f2f7e
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_3.demo.sh
@@ -0,0 +1,1 @@
+treevalue graph -t 'node_share_demo.*' -o 'shared_nodes.dat.svg'
diff --git a/docs/source/tutorials/cli_usage/graph_demo_4.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_4.demo.sh
new file mode 100644
index 0000000000..aa1d02fb2f
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_4.demo.sh
@@ -0,0 +1,5 @@
+treevalue graph \
+  -t 'node_share_demo.*' \
+  -o 'shared_nodes_with_cfg.dat.svg' \
+  -T 'Graph to Show the Shared Nodes' \
+  -c 'bgcolor=#ffffff00'
diff --git a/docs/source/tutorials/cli_usage/graph_demo_5.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_5.demo.sh
new file mode 100644
index 0000000000..6d42ebe4ee
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_5.demo.sh
@@ -0,0 +1,5 @@
+treevalue graph \
+  -t 'node_share_demo.*' \
+  -o 'shared_nodes.dat.png' \
+  -T 'PNG Formatted Graph' \
+  -c 'bgcolor=#ffffff00'
diff --git a/docs/source/tutorials/cli_usage/graph_demo_6.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_6.demo.sh
new file mode 100644
index 0000000000..059328cce6
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_6.demo.sh
@@ -0,0 +1,5 @@
+treevalue graph \
+  -t 'node_share_demo.*' \
+  -o 'shared_nodes.dat.gv' \
+  -T 'PNG Formatted Graph' \
+  -c 'bgcolor=#ffffff00'
diff --git a/docs/source/tutorials/cli_usage/graph_demo_7.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_7.demo.sh
new file mode 100644
index 0000000000..bf30c896fb
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_7.demo.sh
@@ -0,0 +1,5 @@
+treevalue graph \
+  -t 'node_share_demo.*' \
+  -O \
+  -T 'PNG Formatted Graph' \
+  -c 'bgcolor=#ffffff00'
diff --git a/docs/source/tutorials/cli_usage/graph_demo_8.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_8.demo.sh
new file mode 100644
index 0000000000..c012eeffde
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_8.demo.sh
@@ -0,0 +1,5 @@
+treevalue graph \
+  -t 'share_demo.*' \
+  -o 'no_shared_values.dat.svg' \
+  -T 'No Shared Values' \
+  -c 'bgcolor=#ffffff00'
diff --git a/docs/source/tutorials/cli_usage/graph_demo_9.demo.sh b/docs/source/tutorials/cli_usage/graph_demo_9.demo.sh
new file mode 100644
index 0000000000..b08af46636
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_demo_9.demo.sh
@@ -0,0 +1,6 @@
+treevalue graph \
+  -t 'share_demo.*' \
+  -o 'shared_all_values.dat.svg' \
+  -T 'Shared All Values' \
+  -c 'bgcolor=#ffffff00' \
+  -D
diff --git a/docs/source/tutorials/cli_usage/graph_help_demo.demo.sh b/docs/source/tutorials/cli_usage/graph_help_demo.demo.sh
new file mode 100644
index 0000000000..02e7a067f1
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/graph_help_demo.demo.sh
@@ -0,0 +1,1 @@
+treevalue graph -h
\ No newline at end of file
diff --git a/docs/source/tutorials/cli_usage/help_demo.demo.sh b/docs/source/tutorials/cli_usage/help_demo.demo.sh
new file mode 100644
index 0000000000..712aef3180
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/help_demo.demo.sh
@@ -0,0 +1,1 @@
+treevalue -h
\ No newline at end of file
diff --git a/docs/source/tutorials/cli_usage/index.rst b/docs/source/tutorials/cli_usage/index.rst
new file mode 100644
index 0000000000..b09963c46f
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/index.rst
@@ -0,0 +1,413 @@
+CLI Usage
+======================
+
+From treevalue version ``0.1.0``, simple CLI (Command \
+Line Interface) is provided to simplify some operations and \
+optimize the use experience. This page will introduce \
+the usage of cli.
+
+Version Display
+-------------------------------
+
+You can see the version information with the following \
+command typing into the terminal:
+
+.. literalinclude:: version_demo.demo.sh
+    :language: shell
+    :linenos:
+
+The output should be
+
+.. literalinclude:: version_demo.demo.sh.txt
+    :language: text
+    :linenos:
+
+.. note::
+    You can check if the treevalue package is installed \
+    properly with this command line since version ``0.1.0``.
+
+
+Help Information Display
+-------------------------------
+
+You can see the help information of this cli by the \
+following command line:
+
+.. literalinclude:: help_demo.demo.sh
+    :language: shell
+    :linenos:
+
+The output should be
+
+.. literalinclude:: help_demo.demo.sh.txt
+    :language: text
+    :linenos:
+
+There are several sub commands in ``treevalue`` cli, and they \
+will be introduced in the following sections.
+
+Exporting Trees to Binary
+---------------------------------
+
+First, let's see what is in ``treevalue export`` sub command.
+
+.. literalinclude:: export_help_demo.demo.sh.txt
+    :language: text
+    :linenos:
+
+In this sub command, we can export a tree value object to \
+binary file with :ref:`apidoc_tree_tree_dump`, \
+:ref:`apidoc_tree_tree_dumps` function define in \
+module :doc:`../../api_doc/tree/tree`.
+
+.. note::
+
+    For further information, take a look at:
+
+    * API Documentation of :ref:`apidoc_tree_tree_dump`.
+    * API Documentation of :ref:`apidoc_tree_tree_dumps`.
+
+Export One Tree
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Before exporting, we prepare a python source code \
+file ``tree_demo.py`` with the content of
+
+.. literalinclude:: tree_demo.py
+    :language: python
+    :linenos:
+
+And then we can to export ``t1`` to binary file, like this
+
+.. literalinclude:: export_demo_1.demo.sh
+    :language: shell
+    :linenos:
+
+A binary file named ``export_t1.dat.btv`` will be exported.
+
+.. literalinclude:: export_demo_1.demo.sh.txt
+    :language: text
+    :linenos:
+
+The binary file is exported with \
+the :ref:`apidoc_tree_tree_dump` function, and the \
+compression algorithm used is the default \
+one ``zlib``.
+
+
+Export Multiple Trees Once
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We can export multiple trees at one time with the following \
+command
+
+.. literalinclude:: export_demo_2.demo.sh
+    :language: shell
+    :linenos:
+
+Three binary files named ``me_t1.dat.btv``, \
+``me_t2.dat.btv`` and ``me_t3.dat.btv`` will be exported.
+
+.. literalinclude:: export_demo_2.demo.sh.txt
+    :language: text
+    :linenos:
+
+
+.. _cli_usage_export:
+
+Export Tree with Compression
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We can assign compression algorithm by the ``-c`` option, \
+like this
+
+.. literalinclude:: export_demo_3.demo.sh
+    :language: shell
+    :linenos:
+
+The binary file named ``compress_t1.dat.btv`` will be exported \
+with the ``gzip`` compression, and will be able to be loaded \
+with :ref:`treevalue graph cli <cli_usage_graph>` \
+and :ref:`apidoc_tree_tree_load` function without \
+the assignment of decompression algorithm.
+
+.. literalinclude:: export_demo_3.demo.sh.txt
+    :language: text
+    :linenos:
+
+
+I DO NOT NEED COMPRESSION
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you do not need compression due to the reasons like \
+the time scarcity, you can easily disable the compression.
+
+.. literalinclude:: export_demo_4.demo.sh
+    :language: shell
+    :linenos:
+
+The dumped binary file ``raw_t1.dat.btv`` will not be \
+compressed due to the ``-r`` option.
+
+.. literalinclude:: export_demo_4.demo.sh.txt
+    :language: text
+    :linenos:
+
+.. note::
+
+    You may feel puzzled that why the size of the \
+    uncompressed version is even smaller \
+    than the compressed version. The reason is easy, when the \
+    :ref:`apidoc_tree_tree_load` and :ref:`apidoc_tree_tree_loads` \
+    functions are called, it will crack the decompression \
+    function from the binary data, which means the decompression \
+    function is actually stored in binary data.
+
+    Because of this principle, when the original data is not \
+    big enough, the compressed version may be a little bit \
+    bigger than the uncompressed one. But when the original \
+    data is huge, the compression will show its advantage, \
+    like this source file ``large_tree_demo.py``.
+
+    .. literalinclude:: large_tree_demo.py
+        :language: python
+        :linenos:
+
+    When we try to dump the ``t1`` in it, the result will be \
+    satisfactory.
+
+    .. literalinclude:: export_demo_5.demo.sh
+        :language: python
+        :linenos:
+
+    .. literalinclude:: export_demo_5.demo.sh.txt
+        :language: text
+        :linenos:
+
+
+All the binary file generated in this section (\
+:ref:`cli_usage_export`) can:
+
+* Be loaded by function :ref:`apidoc_tree_tree_load` \
+  and :ref:`apidoc_tree_tree_loads` in python source code.
+* Be loaded by :ref:`treevalue graph cli <cli_usage_graph>` \
+  and generate its graph.
+
+
+.. _cli_usage_graph:
+
+Generate Graph from Trees
+---------------------------------
+
+First, let's see what is in ``treevalue graph`` sub command.
+
+.. literalinclude:: graph_help_demo.demo.sh.txt
+    :language: text
+    :linenos:
+
+In this sub command, we can draw a tree value object to \
+a graph formatted ``png``, ``svg`` or just ``gv`` code \
+with :ref:`apidoc_tree_tree_graphics` function define in \
+module :doc:`../../api_doc/tree/tree`. Also, the dumped \
+binary trees can be imported and then drawn to graphs.
+
+.. note::
+
+    For further information, take a look at:
+
+    * API Documentation of :ref:`apidoc_tree_tree_load`.
+    * API Documentation of :ref:`apidoc_tree_tree_loads`.
+    * API Documentation of :ref:`apidoc_tree_tree_graphics`.
+
+Draw One Graph For One Tree
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before drawing the first graph, we prepare the ``tree_demo.py`` \
+mentioned in :ref:`cli_usage_export`.
+
+.. literalinclude:: tree_demo.py
+    :language: python
+    :linenos:
+
+We can draw the graph of ``t1`` with the following command
+
+.. literalinclude:: graph_demo_1.demo.sh
+    :language: shell
+    :linenos:
+
+The dumped graph ``only_t1.dat.svg`` should be like this
+
+.. image:: only_t1.dat.svg
+    :align: center
+
+
+Draw One Graph For Multiple Trees
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Actually we can put several trees into one graph, just like \
+the following command
+
+.. literalinclude:: graph_demo_2.demo.sh
+    :language: shell
+    :linenos:
+
+The dumped graph ``t1_t2_t3.dat.svg`` should contains 3 trees, \
+like this
+
+.. image:: t1_t2_t3.dat.svg
+    :align: center
+
+Sometime, the trees will share some common nodes (with the same \
+``Tree`` object id), this relation will also be displayed in \
+graph. In another python script ``node_share_demo.py``
+
+.. literalinclude:: node_share_demo.py
+    :language: python
+    :linenos:
+
+The dumped graph ``shared_nodes.dat.svg`` should be like
+
+.. image:: shared_nodes.dat.svg
+    :align: center
+
+The arrow from ``nt3`` named ``first`` is pointed to ``nt1``, \
+the arrow named ``another`` is pointed to ``nt1.x``, and so on.
+
+
+Graph with Configurations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometime we need to assign the title of the dumped graph, \
+or you may think that the white background look prominent in \
+the grey page background. So you can dump the graph like this
+
+.. literalinclude:: graph_demo_4.demo.sh
+    :language: shell
+    :linenos:
+
+The dumped graph ``shared_nodes_with_cfg.dat.svg`` should be \
+like this
+
+.. image:: shared_nodes_with_cfg.dat.svg
+    :align: center
+
+We can see the title and the background color is changed \
+because of the ``-T`` and ``-c`` command. The transparent \
+background looks better than the ``shared_nodes.dat.svg`` \
+in the grey page background.
+
+
+Graph of Different Formats
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you do not need svg format, you can dump it to png format \
+like this
+
+.. literalinclude:: graph_demo_5.demo.sh
+    :language: python
+    :linenos:
+
+The dumped graph ``shared_nodes.dat.png`` should be like this, \
+its format is ``png``.
+
+.. image:: shared_nodes.dat.png
+    :align: center
+
+Besides, if the graphviz code (``gv`` format) is just all \
+what you need, you can dump the ``gv`` source code with the \
+following command line.
+
+.. literalinclude:: graph_demo_6.demo.sh
+    :language: python
+    :linenos:
+
+The dumped source code ``shared_nodes.dat.gv`` should be like \
+this
+
+.. literalinclude:: shared_nodes.dat.gv
+    :language: graphviz
+    :linenos:
+
+Or if you need to put it to the ``stdout``, you can do like this
+
+.. literalinclude:: graph_demo_7.demo.sh
+    :language: python
+    :linenos:
+
+The output in ``stdout`` should almost the same like the \
+source code file ``shared_nodes.dat.gv``.
+
+.. note::
+    When ``-O`` option is used, the ``-o`` will be ignored.
+
+Reuse the Value Nodes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In some cases, the values in the trees' nodes is the same object \
+(using the same memory id). So it's better to put them together \
+in the dumped graph.
+
+For example, in the python code file ``share_demo.py``
+
+.. literalinclude:: share_demo.py
+    :language: python
+    :linenos:
+
+You can run the following command
+
+.. literalinclude:: graph_demo_8.demo.sh
+    :language: shell
+    :linenos:
+
+The dumped graph ``no_shared_values.dat.svg`` should be like \
+this, all the value nodes are separated in the default \
+options.
+
+.. image:: no_shared_values.dat.svg
+    :align: center
+
+We can solve this problem by adding ``-D`` option in the command, \
+which means duplicate all the value nodes.
+
+.. literalinclude:: graph_demo_9.demo.sh
+    :language: shell
+    :linenos:
+
+In the dumped graph ``shared_all_values.dat.svg``, all the value \
+nodes with the same id are put together.
+
+.. image:: shared_all_values.dat.svg
+    :align: center
+
+.. note::
+    When ``-D`` is used, all the values in leaf node with the \
+    same id will share exactly one leaf node in the dumped \
+    graph.
+
+    But actually in python, most of the basic data \
+    types are immutable, which means all the ``1`` in python \
+    code are actually the same object, for their ids are \
+    the same. So in the image ``shared_all_values.dat.svg``, \
+    even the leaf node ``1`` are shared.
+
+    This phenomenon may reduce the intuitiveness of the \
+    image in many cases. Please notice this when you are \
+    deciding to use ``-D`` option.
+
+Because of the problem mentioned in the note above, in most of \
+the cases, it's a better idea to use ``-d`` option to assign \
+which types should be duplicated and which should not. Like the \
+example below.
+
+.. literalinclude:: graph_demo_10.demo.sh
+    :language: shell
+    :linenos:
+
+The dumped graph ``shared_values.dat.svg`` should be like this, \
+the ``1`` is not duplicated any more, but ``list`` \
+and ``np.ndarray`` will be duplicated.
+
+.. image:: shared_values.dat.svg
+    :align: center
+
+
diff --git a/docs/source/tutorials/cli_usage/large_tree_demo.py b/docs/source/tutorials/cli_usage/large_tree_demo.py
new file mode 100644
index 0000000000..d6fd936bd0
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/large_tree_demo.py
@@ -0,0 +1,10 @@
+from treevalue import FastTreeValue
+
+t1 = FastTreeValue({
+    'a': 1,
+    'b': [2] * 1000,  # huge array
+    'x': {
+        'c': b'aklsdfj' * 2000,  # huge bytes
+        'd': 4
+    }
+})
diff --git a/docs/source/tutorials/cli_usage/node_share_demo.py b/docs/source/tutorials/cli_usage/node_share_demo.py
new file mode 100644
index 0000000000..7b5836bc14
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/node_share_demo.py
@@ -0,0 +1,10 @@
+from treevalue import FastTreeValue
+
+nt1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+nt2 = FastTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
+nt3 = FastTreeValue({
+    'first': nt1,
+    'second': nt2,
+    'another': nt1.x,
+    'sum': nt1 + nt2,
+})
diff --git a/docs/source/tutorials/cli_usage/share_demo.py b/docs/source/tutorials/cli_usage/share_demo.py
new file mode 100644
index 0000000000..f6d71bfbc7
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/share_demo.py
@@ -0,0 +1,18 @@
+import numpy as np
+
+from treevalue import FastTreeValue
+
+st1 = FastTreeValue({
+    'a': 1,
+    'b': [1, 2],
+    'x': {
+        'c': 3,
+        'd': np.zeros((3, 2)),
+    }
+})
+st2 = FastTreeValue({
+    'np': st1.x.d,
+    'ar': st1.b,
+    'a': st1.a,
+    'arx': [1, 2],
+})
diff --git a/docs/source/tutorials/cli_usage/tree_demo.py b/docs/source/tutorials/cli_usage/tree_demo.py
new file mode 100644
index 0000000000..10e919c97d
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/tree_demo.py
@@ -0,0 +1,5 @@
+from treevalue import FastTreeValue
+
+t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+t2 = FastTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
+t3 = t1 + t2
diff --git a/docs/source/tutorials/cli_usage/version_demo.demo.sh b/docs/source/tutorials/cli_usage/version_demo.demo.sh
new file mode 100644
index 0000000000..d588b9941e
--- /dev/null
+++ b/docs/source/tutorials/cli_usage/version_demo.demo.sh
@@ -0,0 +1,1 @@
+treevalue -v
\ No newline at end of file
diff --git a/docs/source/tutorials/installation/cli_demo.demo.sh b/docs/source/tutorials/installation/cli_demo.demo.sh
new file mode 100644
index 0000000000..93c0af9676
--- /dev/null
+++ b/docs/source/tutorials/installation/cli_demo.demo.sh
@@ -0,0 +1,3 @@
+treevalue -v &&
+  echo '' &&
+  treevalue -h
diff --git a/docs/source/tutorials/installation/index.rst b/docs/source/tutorials/installation/index.rst
index 2fe6271644..1548114394 100644
--- a/docs/source/tutorials/installation/index.rst
+++ b/docs/source/tutorials/installation/index.rst
@@ -15,16 +15,18 @@ You can also install with the newest version through GitHub:
 
     pip install -U git+https://github.com/opendilab/treevalue.git@main
 
-After installation, open your python console and type
+After installation, open your shell console and use \
+the cli like the script below.
 
-.. literalinclude:: install_check.demo.py
-    :language: python
+.. literalinclude:: cli_demo.demo.sh
+    :language: shell
     :linenos:
 
-If the output is like below and no error occurs, you have successfully installed Treevalue.
-
-.. literalinclude:: install_check.demo.py.txt
+.. literalinclude:: cli_demo.demo.sh.txt
     :language: text
     :linenos:
 
+In newest version of treevalue, cli is supported to do some \
+data processing. Here is the version and help display.
+
 Treevalue is still under development, you can also check out the documents in stable version through `https://opendilab.github.io/treevalue/ <https://opendilab.github.io/treevalue/>`_.
diff --git a/docs/source/tutorials/installation/install_check.demo.py b/docs/source/tutorials/installation/install_check.demo.py
deleted file mode 100644
index 38fcaff296..0000000000
--- a/docs/source/tutorials/installation/install_check.demo.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from treevalue.config.meta import __VERSION__, __TITLE__, __AUTHOR__
-
-if __name__ == '__main__':
-    print(__TITLE__, __VERSION__)
-    print("Powered by", __AUTHOR__)
diff --git a/docs/source/tutorials/quick_start/complex_demo.py b/docs/source/tutorials/quick_start/complex_demo.py
new file mode 100644
index 0000000000..7f84f4d52c
--- /dev/null
+++ b/docs/source/tutorials/quick_start/complex_demo.py
@@ -0,0 +1,10 @@
+import os
+
+from dm import get_module
+
+from treevalue import TreeValue
+
+_module = get_module(os.path.abspath('create_a_complex_tree.demo.py'))
+for key, value in _module.__dict__.items():
+    if isinstance(value, TreeValue):
+        locals()[key] = value
diff --git a/docs/source/tutorials/quick_start/create_a_complex_tree.demo.py b/docs/source/tutorials/quick_start/create_a_complex_tree.demo.py
new file mode 100644
index 0000000000..69799b5bd6
--- /dev/null
+++ b/docs/source/tutorials/quick_start/create_a_complex_tree.demo.py
@@ -0,0 +1,15 @@
+from treevalue import FastTreeValue
+
+t1 = FastTreeValue({'a': 's1', 'b': 2, 'x': {'c': 3, 'd': [1, 2]}})
+t2 = FastTreeValue({'a': 'str11', 'b': 2, 'x': {'c': 33, 'd': t1.x.d}})
+t3 = FastTreeValue({'t1': t1, 't2': t2, 'sum': t1 + t2})
+
+if __name__ == '__main__':
+    print('t1:')
+    print(t1)
+
+    print('t2:')
+    print(t2)
+
+    print('t3:')
+    print(t3)
diff --git a/docs/source/tutorials/quick_start/create_a_tree.demo.py b/docs/source/tutorials/quick_start/create_a_tree.demo.py
index a414d392be..16ac94f6f0 100644
--- a/docs/source/tutorials/quick_start/create_a_tree.demo.py
+++ b/docs/source/tutorials/quick_start/create_a_tree.demo.py
@@ -1,5 +1,6 @@
 from treevalue import FastTreeValue
 
+t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+
 if __name__ == '__main__':
-    t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
     print(t)
diff --git a/docs/source/tutorials/quick_start/create_a_tree.gv b/docs/source/tutorials/quick_start/create_a_tree.gv
deleted file mode 100644
index dcf013bb6b..0000000000
--- a/docs/source/tutorials/quick_start/create_a_tree.gv
+++ /dev/null
@@ -1,15 +0,0 @@
-digraph D {
-    label = "Created new tree 't'"
-    graph [bgcolor = "#ffffff00"];
-    root [label = "t"];
-    n1 [label = "1"];
-    n2 [label = "2"];
-    n3 [label = "t.x"];
-    n4 [label = "3"];
-    n5 [label = "4"];
-    root -> n1 [label = "a"];
-    root -> n2 [label = "b"];
-    root -> n3 [label = "x"];
-    n3 -> n4 [label = "c"];
-    n3 -> n5 [label = "d"];
-}
\ No newline at end of file
diff --git a/docs/source/tutorials/quick_start/display_complex_tree.demo.sh b/docs/source/tutorials/quick_start/display_complex_tree.demo.sh
new file mode 100644
index 0000000000..c7185f3565
--- /dev/null
+++ b/docs/source/tutorials/quick_start/display_complex_tree.demo.sh
@@ -0,0 +1,1 @@
+treevalue graph -t 'complex_demo.*' -c 'bgcolor=#ffffff00' -d list -o complex_demo.dat.svg
diff --git a/docs/source/tutorials/quick_start/display_tree.demo.sh b/docs/source/tutorials/quick_start/display_tree.demo.sh
new file mode 100644
index 0000000000..e62db5d25b
--- /dev/null
+++ b/docs/source/tutorials/quick_start/display_tree.demo.sh
@@ -0,0 +1,1 @@
+treevalue graph -t 'simple_demo.*' -c 'bgcolor=#ffffff00' -d list -o simple_demo.dat.svg
diff --git a/docs/source/tutorials/quick_start/index.rst b/docs/source/tutorials/quick_start/index.rst
index 18f948a349..675311a924 100644
--- a/docs/source/tutorials/quick_start/index.rst
+++ b/docs/source/tutorials/quick_start/index.rst
@@ -1,24 +1,55 @@
 Quick Start
 ==================
 
-Create a tree
------------------
+Create a Simplest Tree
+-------------------------------------
 
-You can easily create a tree value object based on ``FastTreeValue``.
+You can create a simplest tree like this
 
 .. literalinclude:: create_a_tree.demo.py
     :language: python
     :linenos:
 
-The result should be
+A tree value object with the following structure will be \
+created
+
+.. image:: simple_demo.dat.svg
+    :align: center
+
+The output of the code above should be
 
 .. literalinclude:: create_a_tree.demo.py.txt
     :language: text
     :linenos:
 
-A simple tree value structure is created successfully with the structure below.
 
-.. image:: create_a_tree.gv.svg
+Create a Slightly Complex Tree
+-------------------------------------
+
+You can easily create a tree value object which is \
+slightly complex, based on ``FastTreeValue``.
+
+.. literalinclude:: create_a_complex_tree.demo.py
+    :language: python
+    :linenos:
+
+The result should be
+
+.. literalinclude:: create_a_complex_tree.demo.py.txt
+    :language: text
+    :linenos:
+
+Three simple treevalue structures are created. \
+Then save the code above to ``demo.py``, and then input \
+this shell command in your terminal.
+
+.. literalinclude:: display_complex_tree.demo.sh
+    :language: shell
+    :linenos:
+
+A graph named ``demo.dat.svg`` will be generated, like this.
+
+.. image:: complex_demo.dat.svg
     :align: center
 
 Now you are successfully started.
diff --git a/docs/source/tutorials/quick_start/simple_demo.py b/docs/source/tutorials/quick_start/simple_demo.py
new file mode 100644
index 0000000000..0a990e89d8
--- /dev/null
+++ b/docs/source/tutorials/quick_start/simple_demo.py
@@ -0,0 +1,10 @@
+import os
+
+from dm import get_module
+
+from treevalue import TreeValue
+
+_module = get_module(os.path.abspath('create_a_tree.demo.py'))
+for key, value in _module.__dict__.items():
+    if isinstance(value, TreeValue):
+        locals()[key] = value
diff --git a/requirements-doc.txt b/requirements-doc.txt
index 1756ea28b2..04c64a0122 100644
--- a/requirements-doc.txt
+++ b/requirements-doc.txt
@@ -1,4 +1,4 @@
-sphinx>=2.2.1
+sphinx~=3.2.0
 sphinx_rtd_theme~=0.4.3
 enum_tools
 sphinx-toolbox
diff --git a/requirements.txt b/requirements.txt
index a07fe2a3fe..864bbeb4b3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,5 @@
 enum_tools
-treelib>=1.5,<2
\ No newline at end of file
+treelib>=1.5,<2
+graphviz~=0.17
+dill~=0.3.4
+click>=7.1.0
diff --git a/setup.py b/setup.py
index 31c6be1e49..0c0987f499 100644
--- a/setup.py
+++ b/setup.py
@@ -36,6 +36,7 @@ def _load_req(file: str):
     packages=find_packages(
         include=(_package_name, "%s.*" % _package_name)
     ),
+    description=meta['__DESCRIPTION__'],
     long_description=readme,
     long_description_content_type='text/markdown',
     author=meta['__AUTHOR__'],
@@ -55,11 +56,15 @@ def _load_req(file: str):
         'License :: OSI Approved :: Apache Software License',
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: Implementation :: PyPy'
     ],
+    entry_points={
+        'console_scripts': [
+            'treevalue=treevalue.entry.cli:treevalue_cli'
+        ]
+    },
 )
diff --git a/treevalue/config/meta.py b/treevalue/config/meta.py
index cf64255321..1df6bcd754 100644
--- a/treevalue/config/meta.py
+++ b/treevalue/config/meta.py
@@ -1,25 +1,19 @@
 """
 Overview:
     Meta information for treevalue package.
+"""
 
-.. data:: __TITLE__
-
-    Title of this project (should be `treevalue`).
-
-.. data:: __VERSION__
-
-    Version of this project.
-
-.. data:: __AUTHOR__
-
-    Author of this project.
+#: Title of this project (should be `treevalue`).
+__TITLE__ = "treevalue"
 
-.. data:: __AUTHOR_EMAIL__
+#: Version of this project.
+__VERSION__ = "0.1.0"
 
-    Email of the author.
-"""
+#: Short description of the project, will be included in ``setup.py``.
+__DESCRIPTION__ = 'A flexible, generalized tree-based data structure.'
 
-__TITLE__ = "treevalue"
-__VERSION__ = "0.0.1"
+#: Author of this project.
 __AUTHOR__ = "HansBug, DI-engine's Contributors"
+
+#: Email of the authors'.
 __AUTHOR_EMAIL__ = "hansbug@buaa.edu.cn"
diff --git a/treevalue/entry/__init__.py b/treevalue/entry/__init__.py
new file mode 100644
index 0000000000..b1f3550a98
--- /dev/null
+++ b/treevalue/entry/__init__.py
@@ -0,0 +1,1 @@
+from .script import *
diff --git a/treevalue/entry/cli/__init__.py b/treevalue/entry/cli/__init__.py
new file mode 100644
index 0000000000..77c1a0e239
--- /dev/null
+++ b/treevalue/entry/cli/__init__.py
@@ -0,0 +1,1 @@
+from .dispatch import treevalue_cli
diff --git a/treevalue/entry/cli/base.py b/treevalue/entry/cli/base.py
new file mode 100644
index 0000000000..f8ccf58573
--- /dev/null
+++ b/treevalue/entry/cli/base.py
@@ -0,0 +1,50 @@
+import click
+from click.core import Context, Option
+
+from ...config.meta import __TITLE__, __VERSION__, __AUTHOR__, __AUTHOR_EMAIL__
+
+_raw_authors = [item.strip() for item in __AUTHOR__.split(',') if item.strip()]
+_raw_emails = [item.strip() for item in __AUTHOR_EMAIL__.split(',')]
+if len(_raw_emails) < len(_raw_authors):
+    _raw_emails += [None] * (len(_raw_authors) - len(_raw_emails))
+elif len(_raw_emails) > len(_raw_authors):
+    _raw_emails[len(_raw_authors) - 1] = tuple(_raw_emails[len(_raw_authors) - 1:])
+    del _raw_emails[len(_raw_authors):]
+
+_author_tuples = [
+    (author, tuple([item for item in (email if isinstance(email, tuple) else ((email,) if email else ())) if item]))
+    for author, email in zip(_raw_authors, _raw_emails)
+]
+_authors = [
+    author if not emails else '{author} ({emails})'.format(author=author, emails=', '.join(emails))
+    for author, emails in _author_tuples
+]
+
+
+# noinspection PyUnusedLocal
+def print_version(ctx: Context, param: Option, value: bool) -> None:
+    """
+    Print version information of cli
+    :param ctx: click context
+    :param param: current parameter's metadata
+    :param value: value of current parameter
+    """
+    if not value or ctx.resilient_parsing:
+        return
+    click.echo('{title}, version {version}.'.format(title=__TITLE__.capitalize(), version=__VERSION__))
+    if _authors:
+        click.echo('Developed by {authors}.'.format(authors=', '.join(_authors)))
+    ctx.exit()
+
+
+CONTEXT_SETTINGS = dict(
+    help_option_names=['-h', '--help']
+)
+
+
+@click.group(context_settings=CONTEXT_SETTINGS)
+@click.option('-v', '--version', is_flag=True,
+              callback=print_version, expose_value=False, is_eager=True,
+              help="Show package's version information.")
+def _base_treevalue_cli():
+    pass
diff --git a/treevalue/entry/cli/dispatch.py b/treevalue/entry/cli/dispatch.py
new file mode 100644
index 0000000000..cf3a913166
--- /dev/null
+++ b/treevalue/entry/cli/dispatch.py
@@ -0,0 +1,10 @@
+from .base import _base_treevalue_cli
+from .export import _export_cli
+from .graph import _graph_cli
+from .utils import _cli_builder
+
+treevalue_cli = _cli_builder(
+    _base_treevalue_cli,
+    _graph_cli,  # treevalue graph
+    _export_cli,  # treevalue export
+)
diff --git a/treevalue/entry/cli/export.py b/treevalue/entry/cli/export.py
new file mode 100644
index 0000000000..d423572895
--- /dev/null
+++ b/treevalue/entry/cli/export.py
@@ -0,0 +1,80 @@
+import os
+import warnings
+from collections import OrderedDict
+from itertools import chain
+from typing import List, Iterator, Tuple, Optional, Any, Union
+
+import click
+
+from .base import CONTEXT_SETTINGS
+from .io import _import_trees_from_package
+from .utils import err_validator, multiple_validator, validator, _click_pending
+from ...tree import TreeValue
+from ...tree import dump as dump_treevalue
+from ...utils import quick_import_object
+
+
+@err_validator((ImportError,))
+@multiple_validator
+@validator
+def validate_trees(value: str):
+    _items = [item.strip() for item in value.split(':', maxsplit=2)]
+    return _import_trees_from_package(*_items, default_template='$name.btv')
+
+
+_DEFAULT_COMPRESS_VALUE = 'zlib'
+
+
+@err_validator((ImportError,))
+@validator
+def validate_compress(value: Optional[str] = None):
+    _tuple = tuple([quick_import_object(item)[0]
+                    for item in (value or _DEFAULT_COMPRESS_VALUE).split(':', maxsplit=2)])
+    if len(_tuple) == 2:
+        return _tuple, value
+    else:
+        return _tuple[0], value
+
+
+def _export_cli(cli: click.Group):
+    @cli.command('export', help='Export trees to binary files, compression is available.',
+                 context_settings=CONTEXT_SETTINGS)
+    @click.option('-t', '--tree', 'trees', type=click.UNPROCESSED, callback=validate_trees,
+                  multiple=True, help='Trees to be imported from packages, such as \'-t my_tree.t1\'.')
+    @click.option('-o', '--output', 'outputs', type=click.types.Path(dir_okay=False),
+                  multiple=True, help='Output file path, multiple output is supported.')
+    @click.option('-d', '--directory', 'directory',
+                  type=click.types.Path(exists=True, file_okay=False, writable=True, resolve_path=True),
+                  default=None, help='Directory to save the exported trees.')
+    @click.option('-c', '--compress', 'compress', type=click.UNPROCESSED, callback=validate_compress,
+                  default=None, help='Compress algorithm, can be a single lib name or a tuple, default use \'zlib\'.')
+    @click.option('-r', '--raw', 'no_compress', is_flag=True, default=False,
+                  help='Disable all the compression, just export the raw data.', show_default=True)
+    def _export(trees: List[Iterator[Tuple[TreeValue, str]]],
+                outputs: List[str], directory: Optional[str],
+                compress: Tuple[Union[Tuple[Any, Any], Any], Optional[str]], no_compress: bool):
+        trees = [(v, k) for k, v in OrderedDict([(k, v) for v, k in chain(*trees)]).items()]
+        directory = os.path.abspath(directory or '.')
+
+        compress, _compress_value = compress
+        if _compress_value and no_compress:
+            warnings.warn(RuntimeWarning('Compression is disabled due to -r option, '
+                                         'compression assigned in -c option will be ignored.'))
+        compress = None if no_compress else compress
+
+        if 0 < len(outputs) < len(trees):
+            warnings.warn(RuntimeWarning(f'{len(trees)} tree(s) detected but only {len(outputs)} outputs found, '
+                                         f'the tress without output will use their default names.'))
+        if len(outputs) > len(trees):
+            warnings.warn(RuntimeWarning(f'{len(trees)} tree(s) detected but {len(outputs)} outputs found, '
+                                         f'the excess outputs will be ignored.'))
+
+        outputs = list(outputs[:len(trees)])
+        outputs += [None] * (len(trees) - len(outputs))
+        for (tree, name), fname in zip(trees, outputs):
+            _actual_path = os.path.join(directory, fname or name)
+            with _click_pending(f'Exporting tree {repr(name)} to binary file {repr(_actual_path)} ... '):
+                with open(_actual_path, 'wb') as file:
+                    dump_treevalue(tree, file=file, compress=compress)
+
+    return cli
diff --git a/treevalue/entry/cli/graph.py b/treevalue/entry/cli/graph.py
new file mode 100644
index 0000000000..b69f7838c6
--- /dev/null
+++ b/treevalue/entry/cli/graph.py
@@ -0,0 +1,143 @@
+import codecs
+import os
+import shutil
+import tempfile
+import warnings
+from collections import OrderedDict
+from functools import partial
+from itertools import chain
+from typing import Tuple, List, Optional, Iterator, Union
+
+import click
+from graphviz import Digraph, Graph
+
+from .base import CONTEXT_SETTINGS
+from .io import _import_trees_from_binary, _import_trees_from_package
+from .utils import multiple_validator, _click_pending, err_validator, validator
+from ...tree import TreeValue, graphics
+from ...utils import quick_import_object
+
+
+@err_validator((ImportError,))
+@multiple_validator
+@validator
+def validate_trees(value: str) -> Iterator[Tuple[TreeValue, str]]:
+    _items = [item.strip() for item in value.split(':', maxsplit=2)]
+    return chain(_import_trees_from_binary(*_items), _import_trees_from_package(*_items))
+
+
+@err_validator((ImportError,))
+@validator
+def validate_graph(value: str):
+    if value is None:
+        return value
+
+    _graph, _, _ = quick_import_object(value, lambda g: isinstance(g, (Graph, Digraph)))
+    return _graph
+
+
+@multiple_validator
+@validator
+def validate_cfg(value: str) -> Tuple[str, str]:
+    _items = value.split('=', maxsplit=2)
+    if len(_items) < 2:
+        raise click.BadParameter(f'Configuration should be KEY=VALUE, but {repr(value)} found.')
+
+    key, value = _items
+    return key, value
+
+
+@err_validator((ImportError,))
+@multiple_validator
+@validator
+def validate_duplicate_types(value: str):
+    _it, _, _ = quick_import_object(value, lambda t: isinstance(t, type))
+    return _it
+
+
+def _save_source_code(g: Digraph, path: str):
+    with codecs.open(path, 'w') as file:
+        file.write(g.source)
+
+
+def _save_image(g: Digraph, path: str, fmt: str):
+    with tempfile.TemporaryDirectory() as tmpdir:
+        with tempfile.NamedTemporaryFile(dir=tmpdir) as tmpfile:
+            svg_file = g.render(tmpfile.name, format=fmt)
+            shutil.copy(svg_file, path)
+
+
+_IMAGE_FMTS = {'svg', 'png'}
+
+
+def _save_graph_fmt_infer(path: str, fmt: Optional[str] = None):
+    if not fmt:
+        _basename, _extname = os.path.splitext(path)
+        if _extname:
+            fmt = _extname[1:]
+    return fmt if fmt else None
+
+
+def _save_graph(g: Digraph, path: str, fmt: str):
+    path = os.path.abspath(path)
+    _saver = partial(_save_image, fmt=fmt) if fmt in _IMAGE_FMTS else _save_source_code
+    return _saver(g, path)
+
+
+def _graph_cli(cli: click.Group):
+    @cli.command('graph', help='Generate graph by trees and graphviz elements.',
+                 context_settings=CONTEXT_SETTINGS)
+    @click.option('-t', '--tree', 'trees', type=click.UNPROCESSED, callback=validate_trees,
+                  multiple=True, help='Trees to be imported, such as \'-t my_tree.t1\'.')
+    @click.option('-g', '--graph', type=click.UNPROCESSED, callback=validate_graph,
+                  help='Graph to be exported, such as \'-g my_script.graph1\', '
+                       '-t options will be ignored.')
+    @click.option('-c', '--config', 'configs', type=click.UNPROCESSED, callback=validate_cfg,
+                  multiple=True, help='External configuration when generating graph. '
+                                      'Like \'-c bgcolor=#ffffff00\', will be displayed as '
+                                      'graph [bgcolor=#ffffff00] in source code. ')
+    @click.option('-T', '--title', type=str, default=None,
+                  help='Title of the graph, will be automatically generated when not given.')
+    @click.option('-o', '--output', 'outputs', type=click.types.Path(dir_okay=False),
+                  multiple=True, help='Output file path, multiple output is supported.')
+    @click.option('-O', '--stdout', 'print_to_stdout', is_flag=True, default=False,
+                  help='Print graphviz source code to stdout, -o option will be ignored.', show_default=True)
+    @click.option('-F', '--format', 'fmt', type=str, required=False,
+                  help='Format of output file.')
+    @click.option('-d', '--duplicate', 'dups', type=click.UNPROCESSED, callback=validate_duplicate_types,
+                  multiple=True, help='The types need to be duplicated, '
+                                      'such as \'-d numpy.ndarray\', \'-d torch.Tensor\' and \'-d set\'.')
+    @click.option('-D', '--duplicate_all', 'duplicate_all', is_flag=True, default=False,
+                  help='Duplicate all the nodes of values with same memory id.', show_default=True)
+    def _graph(trees: List[Iterator[Tuple[TreeValue, str]]], graph: Union[Graph, Digraph, None],
+               configs: List[Tuple[str, str]], title: Optional[str],
+               outputs: List[str], fmt: Optional[str], print_to_stdout: bool,
+               dups: List[type], duplicate_all: bool):
+        if graph:
+            if trees or configs or title or dups or duplicate_all:
+                warnings.warn(RuntimeWarning('The imported trees and related options will be '
+                                             'ignored due to the enablement of -g option.'))
+            g = graph
+        else:
+            trees = [(v, k) for k, v in OrderedDict([(k, v) for v, k in chain(*trees)]).items()]
+            configs = {key: value for key, value in configs}
+            title = title or f'Graph with {len(trees)} tree(s) - {", ".join(tuple([k for _, k in trees]))}.'
+
+            g = graphics(
+                *trees,
+                dup_value=True if duplicate_all else tuple(dups),
+                title=title, cfg=configs,
+            )
+
+        if print_to_stdout:
+            if outputs:
+                warnings.warn(RuntimeWarning('The output destinations in -o options '
+                                             'will be ignored due to the enablement of -O option.'))
+            click.echo(g.source)
+        else:
+            for output in outputs:
+                _fmt = _save_graph_fmt_infer(output, fmt)
+                with _click_pending(f'Exporting graph to {repr(output)} as format {repr(_fmt)} ... '):
+                    _save_graph(g, output, _fmt)
+
+    return cli
diff --git a/treevalue/entry/cli/io.py b/treevalue/entry/cli/io.py
new file mode 100644
index 0000000000..60acfe4dcc
--- /dev/null
+++ b/treevalue/entry/cli/io.py
@@ -0,0 +1,45 @@
+import glob
+import os
+import pickle
+from string import Template
+from typing import Tuple, Iterator
+
+import dill
+
+from ...tree import TreeValue, load
+from ...utils import dynamic_call, iter_import_objects
+
+
+@dynamic_call
+def _import_trees_from_package(obj_pattern, title=None, *args,
+                               default_template: str = '$name') -> Iterator[Tuple[TreeValue, str]]:
+    _title_template = Template(title or default_template)
+    for _object, _module, _name in iter_import_objects(obj_pattern, lambda o: isinstance(o, TreeValue)):
+        _title = _title_template.safe_substitute(dict(module=_module, name=_name))
+        yield _object, _title
+
+
+@dynamic_call
+def _import_trees_from_binary(filename_pattern, title='', *args,
+                              default_template: str = '$bodyname') -> Iterator[Tuple[TreeValue, str]]:
+    _title_template = Template(title or default_template)
+    for filename in glob.glob(filename_pattern):
+        if not os.path.exists(filename) or not os.path.isfile(filename) or not os.access(filename, os.R_OK):
+            continue
+
+        filename = os.path.abspath(filename)
+        _name_body, _name_ext = os.path.splitext(os.path.basename(filename))
+        _name_ext = _name_ext[1:] if _name_ext.startswith('.') else _name_ext
+        with open(filename, 'rb') as file:
+            try:
+                _tree = load(file)
+            except (pickle.UnpicklingError, dill.UnpicklingError, EOFError, IOError):
+                continue
+            else:
+                yield _tree, _title_template.safe_substitute(dict(
+                    fullname=filename,
+                    dirname=os.path.dirname(filename),
+                    basename=os.path.basename(filename),
+                    extname=_name_ext,
+                    bodyname=_name_body,
+                ))
diff --git a/treevalue/entry/cli/utils.py b/treevalue/entry/cli/utils.py
new file mode 100644
index 0000000000..0d0c6d9cfa
--- /dev/null
+++ b/treevalue/entry/cli/utils.py
@@ -0,0 +1,89 @@
+import sys
+from contextlib import contextmanager
+from functools import wraps
+from typing import Callable, Union, Tuple
+
+import click
+
+from ...utils import dynamic_call, str_traceback
+
+
+def validator(func):
+    func = dynamic_call(func)
+
+    @wraps(func)
+    def _new_func(ctx, param, value):
+        return func(ctx=ctx, param=param, value=value)
+
+    return _new_func
+
+
+def multiple_validator(func):
+    func = validator(func)
+
+    @wraps(func)
+    def _new_func(ctx, param, value):
+        return [func(ctx, param, item) for item in value]
+
+    return _new_func
+
+
+_EXPECTED_TREE_ERRORS = (
+    ValueError, TypeError, ImportError, AttributeError, ModuleNotFoundError,
+    FileNotFoundError, IsADirectoryError, PermissionError, FileExistsError,
+)
+
+_EXCEPTION_WRAPPED = '__exception_wrapped__'
+
+
+def err_validator(types: Union[type, Tuple[type]]):
+    def _decorator(func):
+        func = validator(func)
+
+        @wraps(func)
+        def _new_func(ctx, param, value):
+            try:
+                return func(ctx, param, value)
+            except click.BadParameter as err:
+                raise err
+            except types as err:
+                _messages = [item for item in err.args if isinstance(item, str)]
+                _final_message = _messages[0] if _messages else str(_messages)
+                raise click.BadParameter(_final_message)
+
+        return _new_func
+
+    return _decorator
+
+
+def _cli_builder(base_cli, *wrappers):
+    _cli = None
+    for wrapper in wrappers:
+        _cli = wrapper(_cli or base_cli)
+    return _cli
+
+
+@contextmanager
+def _click_pending(text: str, ok: Union[Callable, str] = 'OK', error: Union[Callable, str] = 'ERROR'):
+    if not hasattr(ok, '__call__'):
+        _okay_text = str(ok)
+        ok = lambda: _okay_text
+    ok = dynamic_call(ok)
+
+    if not hasattr(error, '__call__'):
+        _error_text = str(error)
+        error = lambda: _error_text
+    error = dynamic_call(error)
+
+    click.echo(text, nl=False)
+
+    try:
+        yield
+    except BaseException as err:
+        click.secho(click.style(error(err), fg='red'), nl=False)
+        click.secho(str_traceback(err), file=sys.stderr)
+        raise err
+    else:
+        click.secho(click.style(ok(), fg='green'), nl=False)
+    finally:
+        click.echo('.', nl=True)
diff --git a/treevalue/entry/script/__init__.py b/treevalue/entry/script/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/treevalue/tree/common/base.py b/treevalue/tree/common/base.py
index 9f12a2d9a9..468dc57f38 100644
--- a/treevalue/tree/common/base.py
+++ b/treevalue/tree/common/base.py
@@ -135,6 +135,23 @@ def clone(self, copy_value: Union[None, bool, Callable, Any] = None):
         """
         raise NotImplementedError  # pragma: no cover
 
+    @abstractmethod
+    def copy_from(self, other: 'BaseTree'):
+        """
+        Overview:
+            Copy data from another tree.
+
+        Arguments:
+            - other (:obj:`BaseTree`): Another target tree.
+
+        Returns:
+            - self (:obj:`BaseTree`): Self object
+
+        Example:
+            >>> # todo, complete this demo
+        """
+        raise NotImplementedError  # pragma: no cover
+
     @abstractmethod
     def items(self):
         """
@@ -267,9 +284,4 @@ def __str__(self):
             >>> │   └── 'd' --> 4
             >>> └── 'z' --> [1, 2]
         """
-        return str(build_tree(
-            self,
-            represent=lambda value: repr(value),
-            iterate=lambda value: value.items(),
-            recurse=lambda value: isinstance(value, BaseTree),
-        ))
+        return str(build_tree(self, repr_gen=lambda value: repr(value)))
diff --git a/treevalue/tree/common/tree.py b/treevalue/tree/common/tree.py
index 3bb4217590..dad690bd8f 100644
--- a/treevalue/tree/common/tree.py
+++ b/treevalue/tree/common/tree.py
@@ -1,3 +1,4 @@
+import re
 from copy import deepcopy
 from functools import wraps
 from typing import Dict, Any, Union, List, Callable
@@ -63,6 +64,16 @@ def _unraw(value):
         return value
 
 
+_KEY_PATTERN = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]{0,65534}$')
+
+
+def _check_key(key):
+    if not isinstance(key, str) or not _KEY_PATTERN.fullmatch(key):
+        raise KeyError("Tree's key should be an ascii string matching regex of {regexp}, "
+                       "but {key} found.".format(regexp=repr(_KEY_PATTERN.pattern), key=repr(key)))
+    return key
+
+
 def _to_tree_decorator(init_func):
     @wraps(init_func)
     def _new_init_func(data):
@@ -70,7 +81,7 @@ def _new_init_func(data):
             _new_init_func(_tree_dump(data))
         elif isinstance(data, dict):
             init_func({
-                str(key): Tree(value) if isinstance(value, dict) else _unraw(value)
+                _check_key(key): Tree(value) if isinstance(value, dict) else _unraw(value)
                 for key, value in data.items()
             })
         else:
@@ -138,6 +149,7 @@ def __getitem__(self, key):
         return self.__dict[key]
 
     def __setitem__(self, key, value):
+        key = _check_key(key)
         if isinstance(value, dict):
             value = Tree(value)
         self.__dict[key] = _unraw(value)
@@ -153,6 +165,23 @@ def view(self, path: List[str]):
     def clone(self, copy_value: Union[None, bool, Callable, Any] = None):
         return self.__class__(_tree_dump(self, copy_value))
 
+    def copy_from(self, other: 'BaseTree'):
+        other = other.actual()
+        all_keys = sorted(set(other.keys()) | set(self.keys()))
+        for key in all_keys:
+            if key not in other.keys():
+                del self[key]
+            else:
+                if isinstance(other[key], BaseTree):
+                    if key in self.keys() and isinstance(self[key], BaseTree):
+                        self[key].actual().copy_from(other[key].actual())
+                    else:
+                        self[key] = other[key].clone()
+                else:
+                    self[key] = other[key]
+
+        return self
+
     def items(self):
         return self.__dict.items()
 
@@ -162,5 +191,12 @@ def keys(self):
     def values(self):
         return self.__dict.values()
 
-    def actual(self):
+    def actual(self) -> 'Tree':
         return self
+
+    def __getstate__(self):
+        return {key: value.actual() if isinstance(value, BaseTree) else value
+                for key, value in self.__dict.items()}
+
+    def __setstate__(self, state):
+        self.__dict = state
diff --git a/treevalue/tree/common/view.py b/treevalue/tree/common/view.py
index 7ec215034d..b0bff98d1c 100644
--- a/treevalue/tree/common/view.py
+++ b/treevalue/tree/common/view.py
@@ -54,6 +54,10 @@ def view(self, path: List[str]):
     def clone(self, copy_value: Union[None, bool, Callable, Any] = None):
         return self.__get_actual_tree().clone(copy_value)
 
+    def copy_from(self, other: 'BaseTree'):
+        self.actual().copy_from(other.actual())
+        return self
+
     def items(self):
         return self.__get_actual_tree().items()
 
@@ -63,5 +67,5 @@ def keys(self):
     def values(self):
         return self.__get_actual_tree().values()
 
-    def actual(self):
+    def actual(self) -> Tree:
         return self.__get_actual_tree().actual()
diff --git a/treevalue/tree/func/func.py b/treevalue/tree/func/func.py
index 4fb4ea5f20..a2b4326a30 100644
--- a/treevalue/tree/func/func.py
+++ b/treevalue/tree/func/func.py
@@ -9,7 +9,7 @@
 from .outer import _OuterProcessor
 from .strict import _StrictProcessor
 from ..common import raw
-from ..tree.tree import TreeValue
+from ..tree.tree import TreeValue, get_data_property
 from ..tree.utils import rise as rise_func
 from ..tree.utils import subside as subside_func
 from ...utils import int_enum_loads, SingletonMark
@@ -45,12 +45,16 @@ def __getattr__(self, item):
     return _AnyClass()
 
 
-_ClassType = TypeVar("_ClassType", bound=TreeValue)
+TreeClassType_ = TypeVar("TreeClassType_", bound=TreeValue)
+
+#: Default value of the ``missing`` arguments of ``func_treelize``, ``method_treelize`` \
+#: and ``classmethod_treelize``, which means missing is not allowed \
+#: (raise ``KeyError`` when missing is detected).
 MISSING_NOT_ALLOW = SingletonMark("missing_not_allow")
 
 
 def func_treelize(mode: Union[str, TreeMode] = 'strict',
-                  return_type: Optional[Type[_ClassType]] = TreeValue, inherit: bool = True,
+                  return_type: Optional[Type[TreeClassType_]] = TreeValue, inherit: bool = True,
                   missing: Union[Any, Callable] = MISSING_NOT_ALLOW,
                   subside: Union[Mapping, bool, None] = None,
                   rise: Union[Mapping, bool, None] = None):
@@ -60,7 +64,7 @@ def func_treelize(mode: Union[str, TreeMode] = 'strict',
 
     Arguments:
         - mode (:obj:`Union[str, TreeMode]`): Mode of the wrapping (string or TreeMode both okay), default is `strict`.
-        - return_type (:obj:`Optional[Type[_ClassType]]`): Return type of the wrapped function, default is `TreeValue`.
+        - return_type (:obj:`Optional[Type[TreeClassType_]]`): Return type of the wrapped function, default is `TreeValue`.
         - inherit (:obj:`bool`): Allow inherit in wrapped function, default is `True`.
         - missing (:obj:`Union[Any, Callable]`): Missing value or lambda generator of when missing, \
             default is `MISSING_NOT_ALLOW`, which means raise `KeyError` when missing detected.
@@ -123,7 +127,7 @@ def _get_from_key(key):
             ))
 
     def _decorator(func):
-        def _recursion(*args, **kwargs) -> Optional[_ClassType]:
+        def _recursion(*args, **kwargs) -> Optional[TreeClassType_]:
             if all([not isinstance(item, TreeValue) for item in args]) \
                     and all([not isinstance(value, TreeValue) for value in kwargs.values()]):
                 return func(*args, **kwargs)
@@ -176,7 +180,7 @@ def _tree_check(clazz, allow_none=False):
         ))
 
 
-def _class_config(return_type: Optional[Type[_ClassType]] = None,
+def _class_config(return_type: Optional[Type[TreeClassType_]] = None,
                   allow_none_return_type: bool = True,
                   clazz_must_be_tree_value: bool = True):
     _tree_check(return_type, allow_none=allow_none_return_type)
@@ -195,13 +199,13 @@ def _decorator(clazz: type) -> type:
     return _decorator
 
 
-def tree_class(return_type: Optional[Type[_ClassType]] = None):
+def tree_class(return_type: Optional[Type[TreeClassType_]] = None):
     """
     Overview:
         Wrap tree configs for ``TreeValue`` class.
 
     Arguments:
-        - return_type (:obj:`Optional[Type[_ClassType]]`): Default return type of current class, \
+        - return_type (:obj:`Optional[Type[TreeClassType_]]`): Default return type of current class, \
             default is ``None`` which means use the class itself.
 
     Returns:
@@ -216,6 +220,10 @@ def _get_configs(clazz: type):
     return dict(getattr(clazz, _CONFIGS_TAG, None) or {})
 
 
+#: Default value of the ``return_type`` arguments \
+#: of ``method_treelize`` and ``classmethod_treelize``, \
+#: which means return type will be auto configured to
+#: the current class.
 AUTO_DETECT_RETURN_TYPE = SingletonMark("auto_detect_return_type")
 
 
@@ -224,10 +232,10 @@ def _auto_detect_type(clazz: type):
 
 
 def method_treelize(mode: Union[str, TreeMode] = 'strict',
-                    return_type: Optional[Type[_ClassType]] = AUTO_DETECT_RETURN_TYPE, inherit: bool = True,
+                    return_type: Optional[Type[TreeClassType_]] = AUTO_DETECT_RETURN_TYPE, inherit: bool = True,
                     missing: Union[Any, Callable] = MISSING_NOT_ALLOW,
                     subside: Union[Mapping, bool, None] = None,
-                    rise: Union[Mapping, bool, None] = None):
+                    rise: Union[Mapping, bool, None] = None, self_copy: bool = False):
     """
     Overview:
         Wrap a common instance method to tree-supported method.
@@ -239,7 +247,7 @@ def method_treelize(mode: Union[str, TreeMode] = 'strict',
 
     Arguments:
         - mode (:obj:`Union[str, TreeMode]`): Mode of the wrapping (string or TreeMode both okay), default is `strict`.
-        - return_type (:obj:`Optional[Type[_ClassType]]`): Return type of the wrapped function, \
+        - return_type (:obj:`Optional[Type[TreeClassType_]]`): Return type of the wrapped function, \
             default is `AUTO_DETECT_RETURN_VALUE`, which means automatically use the decorated method's class.
         - inherit (:obj:`bool`): Allow inherit in wrapped function, default is `True`.
         - missing (:obj:`Union[Any, Callable]`): Missing value or lambda generator of when missing, \
@@ -251,6 +259,9 @@ def method_treelize(mode: Union[str, TreeMode] = 'strict',
             and rise configuration, default is `None` which means do not use rise. \
             When rise is `True`, it will use all the default arguments in `rise` function. \
             (Not recommend to use auto mode when your return structure is not so strict.)
+        - self_copy (:obj:`bool`): Self copy mode, if enabled, the result data will be copied to \
+            ``self`` argument and ``self`` will be returned as result. Default is ``False``, \
+            which means do not do self copy.
 
     Returns:
         - decorator (:obj:`Callable`): Wrapper for tree-supported method.
@@ -271,20 +282,26 @@ def _decorator(method):
         @wraps(method)
         def _new_method(self, *args, **kwargs):
             rt = _auto_detect_type(self.__class__) if return_type is AUTO_DETECT_RETURN_TYPE else return_type
-            return func_treelize(mode, rt, inherit, missing, subside, rise)(method)(self, *args, **kwargs)
+            _result = func_treelize(mode, rt, inherit, missing, subside, rise)(method)(self, *args, **kwargs)
+
+            if self_copy:
+                get_data_property(self).copy_from(get_data_property(_result))
+                return self
+            else:
+                return _result
 
         return _new_method
 
     return _decorator
 
 
-def utils_class(return_type: Type[_ClassType]):
+def utils_class(return_type: Type[TreeClassType_]):
     """
     Overview:
         Wrap tree configs for utils class.
 
     Arguments:
-        - return_type (:obj:`Optional[Type[_ClassType]]`): Default return type of current class.
+        - return_type (:obj:`Optional[Type[TreeClassType_]]`): Default return type of current class.
 
     Returns:
         - decorator (:obj:`Callable`): A class decorator.
@@ -310,7 +327,7 @@ def utils_class(return_type: Type[_ClassType]):
 
 
 def classmethod_treelize(mode: Union[str, TreeMode] = 'strict',
-                         return_type: Optional[Type[_ClassType]] = AUTO_DETECT_RETURN_TYPE, inherit: bool = True,
+                         return_type: Optional[Type[TreeClassType_]] = AUTO_DETECT_RETURN_TYPE, inherit: bool = True,
                          missing: Union[Any, Callable] = MISSING_NOT_ALLOW,
                          subside: Union[Mapping, bool, None] = None,
                          rise: Union[Mapping, bool, None] = None):
@@ -324,7 +341,7 @@ def classmethod_treelize(mode: Union[str, TreeMode] = 'strict',
 
     Arguments:
         - mode (:obj:`Union[str, TreeMode]`): Mode of the wrapping (string or TreeMode both okay), default is `strict`.
-        - return_type (:obj:`Optional[Type[_ClassType]]`): Return type of the wrapped function, \
+        - return_type (:obj:`Optional[Type[TreeClassType_]]`): Return type of the wrapped function, \
             default is `AUTO_DETECT_RETURN_VALUE`, which means automatically use the decorated method's class.
         - inherit (:obj:`bool`): Allow inherit in wrapped function, default is `True`.
         - missing (:obj:`Union[Any, Callable]`): Missing value or lambda generator of when missing, \
diff --git a/treevalue/tree/general/general.py b/treevalue/tree/general/general.py
index 60b07a79cf..b49095c671 100644
--- a/treevalue/tree/general/general.py
+++ b/treevalue/tree/general/general.py
@@ -1,22 +1,29 @@
 from functools import lru_cache
-from typing import List, Mapping, Optional, Any, Type, TypeVar, Union, Callable
+from functools import wraps, partial
+from typing import List, Mapping, Optional, Any, Type, TypeVar, Union, Callable, Tuple
 
-from ..func import method_treelize
-from ..tree import TreeValue, jsonify, view, clone, typetrans, mapping, mask, filter_, reduce_, union, subside, rise, \
-    NO_RISE_TEMPLATE
+from graphviz import Digraph
+
+from ..func import method_treelize, MISSING_NOT_ALLOW, TreeMode, func_treelize
+from ..tree import TreeValue, jsonify, view, clone, typetrans, mapping, mask, filter_, reduce_, union, \
+    NO_RISE_TEMPLATE, graphics
+from ..tree import rise as rise_func
+from ..tree import subside as subside_func
+from ..tree.tree import get_data_property
+from ...utils import dynamic_call, raising
 
 _BASE_GENERATION_CONFIG = {}
 
 
 def general_tree_value(base: Optional[Mapping[str, Any]] = None,
-                       methods: Optional[Mapping[str, Optional[Mapping[str, Any]]]] = None):
+                       methods: Optional[Mapping[str, Any]] = None):
     """
     Overview:
         Get general tree value class.
 
     Arguments:
         - base (:obj:`Optional[Mapping[str, Any]]`): Base configuration of `func_treelize`.
-        - methods (:obj:`Optional[Mapping[str, Optional[Mapping[str, Any]]]]`): Method configurations of `func_treelize`.
+        - methods (:obj:`Optional[Mapping[str, Any]]`): Method configurations of `func_treelize`.
 
     Returns:
         - clazz (:obj:`Type[TreeValue]`): General tree value class.
@@ -24,15 +31,34 @@ def general_tree_value(base: Optional[Mapping[str, Any]] = None,
     base = base or {}
     methods = methods or {}
 
-    @lru_cache()
-    def _decorator_config(name):
-        _config = _BASE_GENERATION_CONFIG.copy()
-        _config.update(base)
-        _config.update(methods.get(name, None) or {})
-        return _config
+    def _dynamic_suffix_dec(func, f):
+        return wraps(func)(dynamic_call(f))
 
-    def _decorate(func):
-        return method_treelize(**_decorator_config(func.__name__))(func)
+    @lru_cache()
+    def _get_decorator(name, treelize: bool, ext_cfg: Optional[tuple] = None):
+        _item = methods.get(name, None)
+
+        if treelize and isinstance(_item or {}, dict):
+            _config = _BASE_GENERATION_CONFIG.copy()
+            _config.update(base)
+            _config.update(_item or {})
+            _config.update(dict(ext_cfg or ()))
+            return lambda func: method_treelize(**_config)(func)
+        elif isinstance(_item, BaseException) or (isinstance(_item, type) and issubclass(_item, BaseException)):
+            return lambda func: _dynamic_suffix_dec(func, raising(_item))
+        elif hasattr(_item, '__call__'):
+            return lambda func: _dynamic_suffix_dec(func, _item)
+        elif name in methods.keys():
+            return lambda func: _dynamic_suffix_dec(func, lambda: methods[name])
+        else:
+            return lambda func: func
+
+    def _decorate(func, treelize: bool, ext_cfg: Optional[dict] = None):
+        return _get_decorator(func.__name__, treelize, tuple(sorted((ext_cfg or {}).items())))(func)
+
+    _decorate_treelize = partial(_decorate, treelize=True)
+    _decorate_method = partial(_decorate, treelize=False)
+    _decorate_and_replace = partial(_decorate, treelize=True, ext_cfg=dict(self_copy=True))
 
     _TreeValue = TypeVar("_TreeValue", bound=TreeValue)
 
@@ -69,6 +95,7 @@ def _attr_extern(self, key):
             """
             return getattr(self, key)
 
+        @_decorate_method
         def json(self):
             """
             Overview:
@@ -83,6 +110,7 @@ def json(self):
             """
             return jsonify(self)
 
+        @_decorate_method
         def view(self, path: List[str]):
             """
             Overview:
@@ -100,6 +128,7 @@ def view(self, path: List[str]):
             """
             return view(self, path)
 
+        @_decorate_method
         def clone(self, copy_value: Union[None, bool, Callable, Any] = None):
             """
             Overview:
@@ -117,6 +146,7 @@ def clone(self, copy_value: Union[None, bool, Callable, Any] = None):
             """
             return clone(self, copy_value)
 
+        @_decorate_method
         def type(self, clazz: Type[_TreeValue]) -> _TreeValue:
             """
             Overview:
@@ -136,6 +166,7 @@ def type(self, clazz: Type[_TreeValue]) -> _TreeValue:
             """
             return typetrans(self, clazz)
 
+        @_decorate_method
         def map(self, mapper):
             """
             Overview:
@@ -155,6 +186,7 @@ def map(self, mapper):
             """
             return mapping(self, mapper)
 
+        @_decorate_method
         def mask(self, mask_: TreeValue, remove_empty: bool = True):
             """
             Overview:
@@ -174,6 +206,7 @@ def mask(self, mask_: TreeValue, remove_empty: bool = True):
             """
             return mask(self, mask_, remove_empty)
 
+        @_decorate_method
         def filter(self, func, remove_empty: bool = True):
             """
             Overview:
@@ -195,6 +228,7 @@ def filter(self, func, remove_empty: bool = True):
             """
             return filter_(self, func, remove_empty)
 
+        @_decorate_method
         def reduce(self, func):
             """
             Overview
@@ -215,6 +249,7 @@ def reduce(self, func):
             """
             return reduce_(self, func)
 
+        @_decorate_method
         def rise(self, dict_: bool = True, list_: bool = True, tuple_: bool = True,
                  template=NO_RISE_TEMPLATE):
             """
@@ -251,9 +286,89 @@ def rise(self, dict_: bool = True, list_: bool = True, tuple_: bool = True,
                 >>> # FastTreeValue 1 will be FastTreeValue({'x': [1, 2], 'y': [5, 6]})
                 >>> # FastTreeValue 2 will be FastTreeValue({'x': [2, 3], 'y': [7, 8]})
             """
-            return rise(self, dict_, list_, tuple_)
+            return rise_func(self, dict_, list_, tuple_, template)
+
+        @_decorate_method
+        def graph(self, root: Optional[str] = None, title: Optional[str] = None,
+                  cfg: Optional[dict] = None,
+                  dup_value: Union[bool, Callable, type, Tuple[Type, ...]] = False,
+                  repr_gen: Optional[Callable] = None,
+                  node_cfg_gen: Optional[Callable] = None,
+                  edge_cfg_gen: Optional[Callable] = None) -> Digraph:
+            """
+            Overview:
+                Draw graph of this tree value.
+
+            Args:
+                - root (:obj:`Optional[str]`): Root name of the graph, default is ``None``, \
+                    this name will be automatically generated.
+                - title (:obj:`Optional[str]`): Title of the graph, default is ``None``, \
+                    this title will be automatically generated from ``root`` argument.
+                - cfg (:obj:`Optional[dict]`): Configuration of the graph.
+                - dup_value (:obj:`Union[bool, Callable, type, Tuple[Type, ...]]`): Value duplicator, \
+                    set `True` to make value with same id use the same node in graph, \
+                    you can also define your own node id algorithm by this argument. \
+                    Default is `False` which means do not use value duplicator.
+                - repr_gen (:obj:`Optional[Callable]`): Representation format generator, \
+                    default is `None` which means using `repr` function.
+                - node_cfg_gen (:obj:`Optional[Callable]`): Node configuration generator, \
+                    default is `None` which means no configuration.
+                - edge_cfg_gen (:obj:`Optional[Callable]`): Edge configuration generator, \
+                    default is `None` which means no configuration.
+
+            Returns:
+                - graph (:obj:`Digraph`): Generated graph of tree values.
+            """
+            root = root or ('<%s #%x>' % (type(self).__name__, id(get_data_property(self).actual())))
+            title = title or ('Graph of tree %s.' % (root,))
+            return graphics(
+                (self, root), title=title, cfg=cfg, dup_value=dup_value,
+                repr_gen=repr_gen, node_cfg_gen=node_cfg_gen, edge_cfg_gen=edge_cfg_gen,
+            )
+
+        @classmethod
+        @_decorate_method
+        def func(cls, mode: Union[str, TreeMode] = 'strict', inherit: bool = True,
+                 missing: Union[Any, Callable] = MISSING_NOT_ALLOW,
+                 subside: Union[Mapping, bool, None] = None,
+                 rise: Union[Mapping, bool, None] = None):
+            """
+            Overview:
+                Wrap a common function to tree-supported function based on this type.
+
+            Arguments:
+                - mode (:obj:`Union[str, TreeMode]`): Mode of the wrapping \
+                    (string or TreeMode both okay), default is `strict`.
+                - inherit (:obj:`bool`): Allow inherit in wrapped function, default is `True`.
+                - missing (:obj:`Union[Any, Callable]`): Missing value or lambda generator of when missing, \
+                    default is `MISSING_NOT_ALLOW`, which means raise `KeyError` when missing detected.
+                - subside (:obj:`Union[Mapping, bool, None]`): Subside enabled to function's arguments or not, \
+                    and subside configuration, default is `None` which means do not use subside. \
+                    When subside is `True`, it will use all the default arguments in `subside` function.
+                - rise (:obj:`Union[Mapping, bool, None]`): Rise enabled to function's return value or not, \
+                    and rise configuration, default is `None` which means do not use rise. \
+                    When rise is `True`, it will use all the default arguments in `rise` function. \
+                    (Not recommend to use auto mode when your return structure is not so strict.)
+
+            Returns:
+                - decorator (:obj:`Callable`): Wrapper for tree-supported function.
+
+            Example:
+                >>> from treevalue import FastTreeValue
+                >>>
+                >>> @FastTreeValue.func()
+                >>> def ssum(a, b):
+                >>>     return a + b  # the a and b will be integers, not TreeValue
+                >>>
+                >>> t1 = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = TreeValue({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+                >>> ssum(1, 2)    # 3
+                >>> ssum(t1, t2)  # FastTreeValue({'a': 12, 'b': 24, 'x': {'c': 36, 'd': 9}})
+            """
+            return func_treelize(mode, cls, inherit, missing, subside, rise)
 
         @classmethod
+        @_decorate_method
         def union(cls, *trees, return_type=None, **kwargs):
             """
             Overview:
@@ -278,6 +393,7 @@ def union(cls, *trees, return_type=None, **kwargs):
             return union(*trees, return_type=return_type or cls, **kwargs)
 
         @classmethod
+        @_decorate_method
         def subside(cls, value, dict_: bool = True, list_: bool = True, tuple_: bool = True,
                     return_type: Optional[Type[_TreeValue]] = None, **kwargs):
             """
@@ -319,10 +435,45 @@ def subside(cls, value, dict_: bool = True, list_: bool = True, tuple_: bool = T
                 >>> #    'b': raw({'a': 2, 'k': '233', 'x': {'c': 4, 'd': [6, 8]}}),
                 >>> #}), all structures above the tree values are subsided to the bottom of the tree.
             """
-            return subside(value, dict_, list_, tuple_,
-                           return_type=return_type or cls, **kwargs)
+            return subside_func(value, dict_, list_, tuple_,
+                                return_type=return_type or cls, **kwargs)
+
+        @classmethod
+        @_decorate_method
+        def graphics(cls, *trees, title: Optional[str] = None, cfg: Optional[dict] = None,
+                     dup_value: Union[bool, Callable, type, Tuple[Type, ...]] = False,
+                     repr_gen: Optional[Callable] = None,
+                     node_cfg_gen: Optional[Callable] = None,
+                     edge_cfg_gen: Optional[Callable] = None) -> Digraph:
+            """
+            Overview:
+                Draw graph by tree values.
+                Multiple tree values is supported.
+
+            Args:
+                - trees: Given tree values, tuples of `Tuple[TreeValue, str]` or tree values are both accepted.
+                - title (:obj:`Optional[str]`): Title of the graph.
+                - cfg (:obj:`Optional[dict]`): Configuration of the graph.
+                - dup_value (:obj:`Union[bool, Callable, type, Tuple[Type, ...]]`): Value duplicator, \
+                    set `True` to make value with same id use the same node in graph, \
+                    you can also define your own node id algorithm by this argument. \
+                    Default is `False` which means do not use value duplicator.
+                - repr_gen (:obj:`Optional[Callable]`): Representation format generator, \
+                    default is `None` which means using `repr` function.
+                - node_cfg_gen (:obj:`Optional[Callable]`): Node configuration generator, \
+                    default is `None` which means no configuration.
+                - edge_cfg_gen (:obj:`Optional[Callable]`): Edge configuration generator, \
+                    default is `None` which means no configuration.
+
+            Returns:
+                - graph (:obj:`Digraph`): Generated graph of tree values.
+            """
+            return graphics(
+                *trees, title=title, cfg=cfg, dup_value=dup_value,
+                repr_gen=repr_gen, node_cfg_gen=node_cfg_gen, edge_cfg_gen=edge_cfg_gen,
+            )
 
-        @_decorate
+        @_decorate_treelize
         def __add__(self, other):
             """
             Overview:
@@ -335,7 +486,7 @@ def __add__(self, other):
             """
             return self + other
 
-        @_decorate
+        @_decorate_treelize
         def __radd__(self, other):
             """
             Overview:
@@ -347,7 +498,22 @@ def __radd__(self, other):
             """
             return other + self
 
-        @_decorate
+        @_decorate_and_replace
+        def __iadd__(self, other):
+            """
+            Overview:
+                Self version of `__add__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 11, 'b': 22, 'x': {'c': 30, 'd': 40}})
+                >>> t1 += t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 12, 'b': 24, 'x': {'c': 33, 'd': 44}})
+            """
+            return self + other
+
+        @_decorate_treelize
         def __sub__(self, other):
             """
             Overview:
@@ -360,7 +526,7 @@ def __sub__(self, other):
             """
             return self - other
 
-        @_decorate
+        @_decorate_treelize
         def __rsub__(self, other):
             """
             Overview:
@@ -372,7 +538,22 @@ def __rsub__(self, other):
             """
             return other - self
 
-        @_decorate
+        @_decorate_and_replace
+        def __isub__(self, other):
+            """
+            Overview:
+                Self version of `__sub__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 11, 'b': 22, 'x': {'c': 30, 'd': 40}})
+                >>> t1 -= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': -10, 'b': -20, 'x': {'c': -27, 'd': -36}})
+            """
+            return self - other
+
+        @_decorate_treelize
         def __mul__(self, other):
             """
             Overview:
@@ -385,7 +566,7 @@ def __mul__(self, other):
             """
             return self * other
 
-        @_decorate
+        @_decorate_treelize
         def __rmul__(self, other):
             """
             Overview:
@@ -397,7 +578,22 @@ def __rmul__(self, other):
             """
             return other * self
 
-        @_decorate
+        @_decorate_and_replace
+        def __imul__(self, other):
+            """
+            Overview:
+                Self version of `__mul__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 11, 'b': 22, 'x': {'c': 30, 'd': 40}})
+                >>> t1 *= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 11, 'b': 44, 'x': {'c': 90, 'd': 160}})
+            """
+            return self * other
+
+        @_decorate_treelize
         def __matmul__(self, other):
             """
             Overview:
@@ -405,7 +601,7 @@ def __matmul__(self, other):
             """
             return self @ other
 
-        @_decorate
+        @_decorate_treelize
         def __rmatmul__(self, other):
             """
             Overview:
@@ -413,7 +609,16 @@ def __rmatmul__(self, other):
             """
             return other @ self
 
-        @_decorate
+        @_decorate_and_replace
+        def __imatmul__(self, other):
+            """
+            Overview:
+                Self version of `__matmul__`.
+                Original id of self will be kept.
+            """
+            return self @ other
+
+        @_decorate_treelize
         def __truediv__(self, other):
             """
             Overview:
@@ -426,7 +631,7 @@ def __truediv__(self, other):
             """
             return self / other
 
-        @_decorate
+        @_decorate_treelize
         def __rtruediv__(self, other):
             """
             Overview:
@@ -438,7 +643,22 @@ def __rtruediv__(self, other):
             """
             return other / self
 
-        @_decorate
+        @_decorate_and_replace
+        def __itruediv__(self, other):
+            """
+            Overview:
+                Self version of `__truediv__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 10, 'b': 25, 'x': {'c': 30, 'd': 40}})
+                >>> t1 /= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 0.1, 'b': 0.08, 'x': {'c': 0.1, 'd': 0.1}})
+            """
+            return self / other
+
+        @_decorate_treelize
         def __floordiv__(self, other):
             """
             Overview:
@@ -451,7 +671,7 @@ def __floordiv__(self, other):
             """
             return self // other
 
-        @_decorate
+        @_decorate_treelize
         def __rfloordiv__(self, other):
             """
             Overview:
@@ -463,7 +683,22 @@ def __rfloordiv__(self, other):
             """
             return other // self
 
-        @_decorate
+        @_decorate_and_replace
+        def __ifloordiv__(self, other):
+            """
+            Overview:
+                Self version of `__floordiv__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 10, 'b': 25, 'x': {'c': 30, 'd': 40}})
+                >>> t2 //= t1
+                >>> t2 # t2's id will not change, FastTreeValue({'a': 10, 'b': 12, 'x': {'c': 10, 'd': 10}})
+            """
+            return self // other
+
+        @_decorate_treelize
         def __mod__(self, other):
             """
             Overview:
@@ -476,7 +711,7 @@ def __mod__(self, other):
             """
             return self % other
 
-        @_decorate
+        @_decorate_treelize
         def __rmod__(self, other):
             """
             Overview:
@@ -488,7 +723,22 @@ def __rmod__(self, other):
             """
             return other % self
 
-        @_decorate
+        @_decorate_and_replace
+        def __imod__(self, other):
+            """
+            Overview:
+                Self version of `__mod__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 10, 'b': 25, 'x': {'c': 30, 'd': 40}})
+                >>> t2 %= t1
+                >>> t2 # t2's id will not change, FastTreeValue({'a': 0, 'b': 1, 'x': {'c': 0, 'd': 0}})
+            """
+            return self % other
+
+        @_decorate_treelize
         def __pow__(self, power):
             """
             Overview:
@@ -501,7 +751,7 @@ def __pow__(self, power):
             """
             return self ** power
 
-        @_decorate
+        @_decorate_treelize
         def __rpow__(self, other):
             """
             Overview:
@@ -513,7 +763,22 @@ def __rpow__(self, other):
             """
             return other ** self
 
-        @_decorate
+        @_decorate_and_replace
+        def __ipow__(self, other):
+            """
+            Overview:
+                Self version of `__pow__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 2, 'b': 3, 'x': {'c': 4, 'd': 5}})
+                >>> t1 **= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 1, 'b': 8, 'x': {'c': 81, 'd': 1024}})
+            """
+            return self ** other
+
+        @_decorate_treelize
         def __and__(self, other):
             """
             Overview:
@@ -526,7 +791,7 @@ def __and__(self, other):
             """
             return self & other
 
-        @_decorate
+        @_decorate_treelize
         def __rand__(self, other):
             """
             Overview:
@@ -538,7 +803,22 @@ def __rand__(self, other):
             """
             return other & self
 
-        @_decorate
+        @_decorate_and_replace
+        def __iand__(self, other):
+            """
+            Overview:
+                Self version of `__and__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 2, 'b': 3, 'x': {'c': 4, 'd': 5}})
+                >>> t1 &= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 0, 'b': 2, 'x': {'c': 0, 'd': 4}})
+            """
+            return self & other
+
+        @_decorate_treelize
         def __or__(self, other):
             """
             Overview:
@@ -551,7 +831,7 @@ def __or__(self, other):
             """
             return self | other
 
-        @_decorate
+        @_decorate_treelize
         def __ror__(self, other):
             """
             Overview:
@@ -563,7 +843,22 @@ def __ror__(self, other):
             """
             return other | self
 
-        @_decorate
+        @_decorate_and_replace
+        def __ior__(self, other):
+            """
+            Overview:
+                Self version of `__or__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 2, 'b': 3, 'x': {'c': 4, 'd': 5}})
+                >>> t1 |= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 3, 'b': 3, 'x': {'c': 7, 'd': 5}})
+            """
+            return self | other
+
+        @_decorate_treelize
         def __xor__(self, other):
             """
             Overview:
@@ -576,7 +871,7 @@ def __xor__(self, other):
             """
             return self ^ other
 
-        @_decorate
+        @_decorate_treelize
         def __rxor__(self, other):
             """
             Overview:
@@ -588,7 +883,22 @@ def __rxor__(self, other):
             """
             return other ^ self
 
-        @_decorate
+        @_decorate_and_replace
+        def __ixor__(self, other):
+            """
+            Overview:
+                Self version of `__xor__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 2, 'b': 3, 'x': {'c': 4, 'd': 5}})
+                >>> t1 ^= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 3, 'b': 1, 'x': {'c': 7, 'd': 1}})
+            """
+            return self ^ other
+
+        @_decorate_treelize
         def __lshift__(self, other):
             """
             Overview:
@@ -601,7 +911,7 @@ def __lshift__(self, other):
             """
             return self << other
 
-        @_decorate
+        @_decorate_treelize
         def __rlshift__(self, other):
             """
             Overview:
@@ -613,7 +923,22 @@ def __rlshift__(self, other):
             """
             return other << self
 
-        @_decorate
+        @_decorate_and_replace
+        def __ilshift__(self, other):
+            """
+            Overview:
+                Self version of `__xor__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 2, 'b': 3, 'x': {'c': 4, 'd': 5}})
+                >>> t1 <<= t2
+                >>> t1 # t1's id will not change, FastTreeValue({'a': 4, 'b': 16, 'x': {'c': 48, 'd': 128}})
+            """
+            return self << other
+
+        @_decorate_treelize
         def __rshift__(self, other):
             """
             Overview:
@@ -626,7 +951,7 @@ def __rshift__(self, other):
             """
             return self >> other
 
-        @_decorate
+        @_decorate_treelize
         def __rrshift__(self, other):
             """
             Overview:
@@ -638,7 +963,22 @@ def __rrshift__(self, other):
             """
             return other >> self
 
-        @_decorate
+        @_decorate_and_replace
+        def __irshift__(self, other):
+            """
+            Overview:
+                Self version of `__xor__`.
+                Original id of self will be kept.
+
+            Example:
+                >>> t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+                >>> t2 = FastTreeValue({'a': 20, 'b': 30, 'x': {'c': 40, 'd': 50}})
+                >>> t2 >>= t1
+                >>> t2 # t2's id will not change, FastTreeValue({'a': 10, 'b': 7, 'x': {'c': 5, 'd': 3}})
+            """
+            return self >> other
+
+        @_decorate_treelize
         def __pos__(self):
             """
             Overview:
@@ -650,7 +990,7 @@ def __pos__(self):
             """
             return +self
 
-        @_decorate
+        @_decorate_treelize
         def __neg__(self):
             """
             Overview:
@@ -662,7 +1002,7 @@ def __neg__(self):
             """
             return -self
 
-        @_decorate
+        @_decorate_treelize
         def __invert__(self):
             """
             Overview:
@@ -674,7 +1014,7 @@ def __invert__(self):
             """
             return ~self
 
-        @_decorate
+        @_decorate_treelize
         def __getitem__(self, item):
             """
             Overview:
@@ -688,7 +1028,7 @@ def __getitem__(self, item):
             """
             return self[item]
 
-        @_decorate
+        @_decorate_treelize
         def __setitem__(self, key, value):
             """
             Overview:
@@ -702,7 +1042,7 @@ def __setitem__(self, key, value):
             """
             self[key] = value
 
-        @_decorate
+        @_decorate_treelize
         def __delitem__(self, key):
             """
             Overview:
@@ -714,7 +1054,7 @@ def __delitem__(self, key):
             """
             del self[key]
 
-        @_decorate
+        @_decorate_treelize
         def __call__(self, *args, **kwargs):
             """
             Overview:
diff --git a/treevalue/tree/tree/__init__.py b/treevalue/tree/tree/__init__.py
index 7cc45266d5..00c1f76443 100644
--- a/treevalue/tree/tree/__init__.py
+++ b/treevalue/tree/tree/__init__.py
@@ -1,3 +1,5 @@
+from .graph import graphics
+from .io import loads, load, dumps, dump
 from .tree import TreeValue
 from .utils import jsonify, view, clone, typetrans, mapping, filter_, mask, reduce_, union, subside, rise, \
     NO_RISE_TEMPLATE
diff --git a/treevalue/tree/tree/graph.py b/treevalue/tree/tree/graph.py
new file mode 100644
index 0000000000..667cc52252
--- /dev/null
+++ b/treevalue/tree/tree/graph.py
@@ -0,0 +1,205 @@
+from functools import lru_cache
+from typing import Type, Callable, Union, Optional, Tuple
+
+from graphviz import Digraph, nohtml
+
+from .tree import TreeValue, get_data_property
+from ...utils import post_process, build_graph, dynamic_call, \
+    freduce, Color
+from ...utils.tree import SUFFIXED_TAG
+
+
+def _min_distance(l_, n):
+    list_ = sorted(l_)
+    return min([abs(a - b) for a, b in zip(list_, list_[1:] + [list_[0] + n])])
+
+
+_FIRST_DIS_CNT = 2
+
+
+def _dis_ratios(n, s, t):
+    l_ = [(i * t + s) % n for i in range(n)]
+    _dis = [_min_distance(l_[:i], n) for i in range(_FIRST_DIS_CNT, n)]
+    _exp = [n // i for i in range(_FIRST_DIS_CNT, n)]
+    _ratios = [e / d for d, e in zip(_dis, _exp)]
+    return max(_ratios), tuple(_ratios)
+
+
+@lru_cache()
+def _gcd(x, y):
+    if y == 0:
+        return x
+    else:
+        return _gcd(y, x % y)
+
+
+@lru_cache()
+def _best_t_for_n_s(n, s):
+    _final_mean, _final_t = None, None
+    for _current_t in range(1, (n + 1) // 2 + 1):
+        if _gcd(_current_t, n) == 1:
+            _current_mean = _dis_ratios(n, s, _current_t)
+            if _final_mean is None or _current_mean < _final_mean:
+                _final_mean, _final_t = _current_mean, _current_t
+
+    return n, _final_t, _final_mean
+
+
+@lru_cache()
+def _hue_id_mapping(n, s):
+    _, t, _ = _best_t_for_n_s(n, s)
+    return [(t * i + s) % n for i in range(n)]
+
+
+@lru_cache()
+def _base_hue(n, s, i):
+    return (_hue_id_mapping(n, s)[i] + 0.5) / n
+
+
+@lru_cache()
+def _all_color(n, s, i):
+    h = _base_hue(n, s, i)
+
+    # line color
+    _line = Color.from_hsv(h, 0.8, 0.6, alpha=0.8)
+
+    # key font color
+    _key = Color.from_hsv(h, 1.0, 0.45, alpha=1.0)
+
+    # border color
+    _border_1 = Color.from_hsv(h, 0.65, 0.7, alpha=0.6)
+    _border_2 = Color.from_hsv(h, 0.65, 0.7, alpha=0.0)
+
+    # shape color
+    _shape = Color.from_hsv(h, 0.55, 1.0, alpha=0.6)
+
+    # data font color
+    _data = Color.from_hsv(h, 1.0, 0.2, alpha=1.0)
+
+    return _line, _key, _border_1, _border_2, _shape, _data
+
+
+@freduce(init=lambda: (lambda: {}))
+def _dict_call_merge(d1, d2):
+    d1 = dynamic_call(d1)
+    d2 = dynamic_call(d2)
+
+    def _new_func(*args, **kwargs):
+        _r1 = d1(*args, **kwargs)
+        _r2 = d2(*args, **kwargs)
+
+        _return = dict(_r1)
+        _return.update(_r2)
+        return _return
+
+    return _new_func
+
+
+@dynamic_call
+def _node_id(current):
+    return 'node_%x' % (id(get_data_property(current).actual()))
+
+
+@dynamic_call
+def _default_value_id(_, parent, current_path, parent_path):
+    return 'value__%s__%s' % (_node_id(parent, parent_path), current_path[-1])
+
+
+@post_process(lambda f: dynamic_call(f) if f is not None else None)
+def _dup_value_func(dup_value):
+    if dup_value:
+        if isinstance(dup_value, (type, tuple)):
+            _dup_value = lambda v: id(v) if isinstance(v, dup_value) else None
+        elif hasattr(dup_value, '__call__'):
+            _dup_value = dup_value
+        else:
+            _dup_value = lambda v: id(v)
+        _id_getter = dynamic_call(_dup_value)
+
+        def _new_func(current, parent, current_path, parent_path):
+            _id = _id_getter(current, parent, current_path, parent_path)
+            if not _id:
+                return _default_value_id(current, parent, current_path, parent_path)
+            elif isinstance(_id, int):
+                return 'value_%x' % (_id,)
+            else:
+                return 'value_%s' % (_id,)
+
+        return _new_func
+    else:
+        return None
+
+
+_GENERIC_N = 36
+_GENERIC_S = _GENERIC_N // 3
+
+
+def graphics(*trees, title: Optional[str] = None, cfg: Optional[dict] = None,
+             dup_value: Union[bool, Callable, type, Tuple[Type, ...]] = False,
+             repr_gen: Optional[Callable] = None,
+             node_cfg_gen: Optional[Callable] = None,
+             edge_cfg_gen: Optional[Callable] = None) -> Digraph:
+    """
+    Overview:
+        Draw graph by tree values.
+        Multiple tree values is supported.
+
+    Args:
+        - trees: Given tree values, tuples of `Tuple[TreeValue, str]` or tree values are both accepted.
+        - title (:obj:`Optional[str]`): Title of the graph.
+        - cfg (:obj:`Optional[dict]`): Configuration of the graph.
+        - dup_value (:obj:`Union[bool, Callable, type, Tuple[Type, ...]]`): Value duplicator, \
+            set `True` to make value with same id use the same node in graph, \
+            you can also define your own node id algorithm by this argument. \
+            Default is `False` which means do not use value duplicator.
+        - repr_gen (:obj:`Optional[Callable]`): Representation format generator, \
+            default is `None` which means using `repr` function.
+        - node_cfg_gen (:obj:`Optional[Callable]`): Node configuration generator, \
+            default is `None` which means no configuration.
+        - edge_cfg_gen (:obj:`Optional[Callable]`): Edge configuration generator, \
+            default is `None` which means no configuration.
+
+    Returns:
+        - graph (:obj:`Digraph`): Generated graph of tree values.
+    """
+
+    def _node_tag(current, parent, current_path, parent_path, is_node):
+        if is_node:
+            return _node_id(current, current_path)
+        else:
+            return dynamic_call(
+                _dup_value_func(dup_value) or _default_value_id
+            )(current, parent, current_path, parent_path)
+
+    setattr(_node_tag, SUFFIXED_TAG, True)
+
+    _line_color = lambda i: _all_color(_GENERIC_N, _GENERIC_S, i)[0]
+    _key_font_color = lambda i: _all_color(_GENERIC_N, _GENERIC_S, i)[1]
+    _border_color = lambda i, is_node: _all_color(_GENERIC_N, _GENERIC_S, i)[2 if is_node else 3]
+    _shape_color = lambda i: _all_color(_GENERIC_N, _GENERIC_S, i)[4]
+    _data_font_color = lambda i: _all_color(_GENERIC_N, _GENERIC_S, i)[5]
+
+    return build_graph(
+        *trees,
+        node_id_gen=_node_tag,
+        graph_title=title or "<untitled>",
+        graph_cfg=cfg or {},
+        repr_gen=repr_gen or (lambda x: nohtml(repr(x))),
+        iter_gen=lambda n: iter(n) if isinstance(n, TreeValue) else None,
+        node_cfg_gen=_dict_call_merge(lambda n, p, np, pp, is_node, is_root, root: {
+            'fillcolor': _shape_color(root[2]),
+            'color': _border_color(root[2], is_node),
+            'fontcolor': _data_font_color(root[2]),
+            'style': 'filled',
+            'shape': 'diamond' if is_root else ('ellipse' if is_node else 'box'),
+            'penwidth': 3 if is_root else 1.5,
+            'fontname': "Times-Roman bold" if is_node else "Times-Roman",
+        }, (node_cfg_gen or (lambda: {}))),
+        edge_cfg_gen=_dict_call_merge(lambda n, p, np, pp, is_node, root: {
+            'arrowhead': 'vee' if is_node else 'dot',
+            'arrowsize': 1.0 if is_node else 0.5,
+            'color': _line_color(root[2]),
+            'fontcolor': _key_font_color(root[2]),
+            'fontname': "Times-Roman bold",
+        }, (edge_cfg_gen or (lambda: {}))),
+    )
diff --git a/treevalue/tree/tree/io.py b/treevalue/tree/tree/io.py
new file mode 100644
index 0000000000..a6defc18f9
--- /dev/null
+++ b/treevalue/tree/tree/io.py
@@ -0,0 +1,117 @@
+import io
+import pickle
+import warnings
+import zlib
+from typing import Optional, Type, TypeVar, Union, Callable
+
+import dill
+
+from .tree import TreeValue, get_data_property
+
+_TreeType = TypeVar('_TreeType', bound=TreeValue)
+
+_NO_DECOMPRESSION = 0
+_NEED_DECOMPRESSION = 1
+
+
+def _extract_compress_and_decompress(compress):
+    if isinstance(compress, tuple):
+        return compress[0], compress[1]
+    elif hasattr(compress, '__call__'):
+        return compress, _NEED_DECOMPRESSION
+    elif compress is None:
+        return (lambda x: x), _NO_DECOMPRESSION
+    else:
+        return getattr(compress, 'compress'), getattr(compress, 'decompress')
+
+
+def dump(t: _TreeType, file, compress=None):
+    """
+    Overview:
+        Dump tree value to file.
+
+    Arguments:
+        - t (:obj:`_TreeType`): Tree value object.
+        - file: Target dump file.
+        - compress: Compress object, may be compression function, \
+            tuple of functions or module (``compress`` and ``decompress`` required).
+    """
+    compress_func, decompress_func = _extract_compress_and_decompress(compress)
+    dump_data = (compress_func(dill.dumps(get_data_property(t).actual())),
+                 zlib.compress(dill.dumps(decompress_func)))
+    pickle.dump(dump_data, file)
+
+
+def dumps(t: _TreeType, compress=None) -> bytes:
+    """
+    Overview:
+        Dump tree value to file.
+
+    Arguments:
+        - t (:obj:`_TreeType`): Tree value object.
+        - compress: Compress object, may be compression function, \
+            tuple of functions or module (``compress`` and ``decompress`` required).
+
+    Returns:
+        - data (:obj:`bytes`): Dumped binary data.
+    """
+    with io.BytesIO() as file:
+        dump(t, file, compress)
+        return file.getvalue()
+
+
+def load(file, type_: Type[_TreeType] = TreeValue, decompress: Optional[Callable] = None) -> _TreeType:
+    """
+    Overview:
+        Load tree value object from file.
+
+    Arguments:
+        - file: Original file.
+        - type\\_ (:obj:`Type[_TreeType]`): Type of tree value, default is ``TreeValue``.
+        - decompress (:obj:`Optional[Callable]`): Decompress function, default is ``None`` which means \
+            do not do any decompression.
+
+    Returns:
+        - tree (:obj:`_TreeType`): Tree value object.
+    """
+    try:
+        _compressed_data, _decompress_func_data = pickle.load(file)
+    except ValueError:
+        raise pickle.UnpicklingError('Invalid TreeValue binary data.')
+
+    decompress_func = dill.loads(zlib.decompress(_decompress_func_data))
+    if decompress_func == _NEED_DECOMPRESSION:
+        if decompress:
+            decompress_func = decompress
+        else:
+            raise RuntimeError('Decompression function not provided but it is required.')
+    elif decompress_func == _NO_DECOMPRESSION:
+        if decompress:
+            warnings.warn("Decompress function is not needed because the data is not compressed.")
+        decompress_func = lambda x: x
+    else:
+        if decompress:
+            warnings.warn("Decompress function is not needed "
+                          "because decompression function has already been provided.")
+        decompress_func = decompress_func
+
+    return type_(dill.loads(decompress_func(_compressed_data)))
+
+
+def loads(data: Union[bytes, bytearray], type_: Optional[Type[_TreeType]] = TreeValue,
+          decompress: Optional[Callable] = None) -> _TreeType:
+    """
+    Overview:
+        Load tree value object from file.
+
+    Arguments:
+        - data (:obj:`Union[bytes, bytearray]`): Binary data.
+        - type\\_ (:obj:`Type[_TreeType]`): Type of tree value, default is ``TreeValue``.
+        - decompress (:obj:`Optional[Callable]`): Decompress function, default is ``None`` which means \
+            do not do any decompression.
+
+    Returns:
+        - tree (:obj:`_TreeType`): Tree value object.
+    """
+    with io.BytesIO(bytes(data)) as file:
+        return load(file, type_, decompress)
diff --git a/treevalue/tree/tree/tree.py b/treevalue/tree/tree/tree.py
index 690f0c745b..29eef34d64 100644
--- a/treevalue/tree/tree/tree.py
+++ b/treevalue/tree/tree/tree.py
@@ -237,9 +237,8 @@ def __str__(self):
         """
         return str(build_tree(
             self,
-            represent=lambda x: repr(x),
-            iterate=lambda x: iter(x),
-            recurse=lambda x: isinstance(x, TreeValue),
+            repr_gen=lambda x: repr(x),
+            iter_gen=lambda x: iter(x) if isinstance(x, TreeValue) else None,
         ))
 
     def __hash__(self):
@@ -292,3 +291,36 @@ def _attr_extern(self, key):
                 just raise an exception here.
         """
         raise KeyError("Attribute {key} not found.".format(key=repr(key)))
+
+    def __setstate__(self, tree: Tree):
+        """
+        Overview:
+            Deserialize operation, can support `pickle.loads`.
+
+        Arguments:
+            - tree (:obj:`Tree`): Deserialize tree.
+
+        Examples:
+            >>> import pickle
+            >>> from treevalue import TreeValue
+            >>>
+            >>> t = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3}})
+            >>> bin_ = pickle.dumps(t)  # dump it to binary
+            >>> pickle.loads(bin_)      #  TreeValue({'a': 1, 'b': 2, 'x': {'c': 3}})
+        """
+        setattr(self, _DATA_PROPERTY, tree)
+
+    def __getstate__(self):
+        """
+        Overview:
+            Serialize operation, can support `pickle.dumps`.
+
+        Examples:
+            >>> import pickle
+            >>> from treevalue import TreeValue
+            >>>
+            >>> t = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3}})
+            >>> bin_ = pickle.dumps(t)  # dump it to binary
+            >>> pickle.loads(bin_)      #  TreeValue({'a': 1, 'b': 2, 'x': {'c': 3}})
+        """
+        return getattr(self, _DATA_PROPERTY).actual()
diff --git a/treevalue/tree/tree/utils.py b/treevalue/tree/tree/utils.py
index 5d53e05c8f..35c16e5cc9 100644
--- a/treevalue/tree/tree/utils.py
+++ b/treevalue/tree/tree/utils.py
@@ -302,6 +302,9 @@ def _get_default_type(args):
     return func_treelize(return_type=return_type, **kwargs)(builder)(*arguments)
 
 
+#: Means no template is given to the rise function, \
+#: and the decorated function will automatically \
+#: try to match the format patterns as template.
 NO_RISE_TEMPLATE = SingletonMark("no_rise_template")
 
 
@@ -393,14 +396,25 @@ def _new_func(*args_):
                     partial(lambda k, g, x: g(x[k]), key, getter)
                     for key in keyset for getter in getter_lists[key]], _new_func
 
-        elif (list_ and issubclass(base_class, list)) \
-                or (tuple_ and issubclass(base_class, tuple)):
+        elif (list_ and issubclass(base_class, list)) or \
+                (tuple_ and issubclass(base_class, tuple)):
+            if issubclass(base_class, list):
+                if tpl is not NO_RISE_TEMPLATE:
+                    if len(tpl) > 1:
+                        raise ValueError("Length of list template should be no more than 1, "
+                                         f"but {len(tpl)} found.")
+                    _inner_tpl = tpl[0] if len(tpl) > 0 else None
+                else:
+                    _inner_tpl = NO_RISE_TEMPLATE
+                _inner_tpl_getter = lambda index: _inner_tpl
+            else:
+                _inner_tpl_getter = lambda index: (NO_RISE_TEMPLATE if tpl is NO_RISE_TEMPLATE else tpl[index])
+
             lengths = [len(item) for item in args]
             if len(set(lengths)) == 1:
                 length = list(lengths)[0]
                 results = [_get_common_structure_getter(
-                    *[item[i] for item in args],
-                    tpl=NO_RISE_TEMPLATE if tpl is NO_RISE_TEMPLATE else tpl[i]
+                    *[item[i] for item in args], tpl=_inner_tpl_getter(i)
                 ) for i in range(length)]
                 getter_lists = [getters for _, getters, _ in results]
 
@@ -431,7 +445,7 @@ def _new_func(*args_):
     meta_value_count, meta_value_getters, value_builder = _get_common_structure_getter(*value_list, tpl=template)
     assert meta_value_count == len(meta_value_getters)
 
-    return value_builder(*[tree_builder(*[getter_(item_) for item_ in value_list]) for getter_ in meta_value_getters])
+    return value_builder(*map(lambda getter_: tree_builder(*map(getter_, value_list)), meta_value_getters))
 
 
 def reduce_(tree: _TreeValue, func):
diff --git a/treevalue/utils/__init__.py b/treevalue/utils/__init__.py
index c7e6450854..b42d04af1c 100644
--- a/treevalue/utils/__init__.py
+++ b/treevalue/utils/__init__.py
@@ -1,6 +1,10 @@
-from .clazz import init_magic, class_wraps, common_bases, common_direct_base
+from .clazz import init_magic, class_wraps, common_bases, common_direct_base, get_class_full_name
+from .color import Color
 from .enum import int_enum_loads
+from .exception import str_traceback
 from .final import FinalMeta
-from .func import args_iter, dynamic_call
+from .func import args_iter, dynamic_call, static_call, post_process, pre_process, freduce, raising, warning_
+from .imports import import_object, quick_import_object, iter_import_objects
+from .random import seed_random, random_hex, random_hex_with_timestamp
 from .singleton import SingletonMeta, ValueBasedSingletonMeta, SingletonMark
-from .tree import build_tree
+from .tree import build_tree, build_graph
diff --git a/treevalue/utils/clazz.py b/treevalue/utils/clazz.py
index 0c703147d6..dfbbde1596 100644
--- a/treevalue/utils/clazz.py
+++ b/treevalue/utils/clazz.py
@@ -181,3 +181,21 @@ def common_direct_base(*classes: Type, base: Union[Collection[Type], Type, None]
             template = "No common base found with {classes}."
 
         raise TypeError(template.format(classes=repr(classes), bases=repr(base)))
+
+
+def get_class_full_name(clazz: type):
+    """
+    Overview:
+        Get full name of a class.
+
+    Arguments:
+        - clazz (:obj:`type`): Given class.
+
+    Returns:
+        - name (:obj:`str`): Full name of the given class.
+    """
+    module = clazz.__module__
+    if not module or module == 'builtins':
+        return clazz.__name__
+    else:
+        return module + '.' + clazz.__name__
diff --git a/treevalue/utils/color.py b/treevalue/utils/color.py
new file mode 100644
index 0000000000..3f0e65d55c
--- /dev/null
+++ b/treevalue/utils/color.py
@@ -0,0 +1,488 @@
+import colorsys
+import math
+import re
+from typing import Optional, Union, Tuple
+
+from .func import post_process, raising, freduce, dynamic_call, warning_
+
+
+def _round_mapper(min_: float, max_: float):
+    min_, max_ = min(min_, max_), max(min_, max_)
+    round_ = max_ - min_
+
+    def _func(v):
+        if v < min_:
+            v += math.ceil((min_ - v) / round_) * round_
+        if v > max_:
+            v -= math.ceil((v - max_) / round_) * round_
+
+        return v
+
+    return _func
+
+
+def _range_mapper(min_: Optional[float], max_: Optional[float], warning=None):
+    if min_ is not None and max_ is not None:
+        min_, max_ = min(min_, max_), max(min_, max_)
+    warning = dynamic_call(warning_(warning if warning is not None else lambda: None))
+
+    def _func(v):
+        if max_ is not None and v > max_:
+            warning(v, min_, max_)
+            return max_
+        elif min_ is not None and v < min_:
+            warning(v, min_, max_)
+            return min_
+        else:
+            return v
+
+    return _func
+
+
+class GetSetProxy:
+    def __init__(self, getter, setter=None):
+        self.__getter = getter
+        self.__setter = setter or raising(lambda x: NotImplementedError)
+
+    def set(self, value):
+        return self.__setter(value)
+
+    def get(self):
+        return self.__getter()
+
+
+_r_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Red value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+_g_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Green value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+_b_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Blue value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+_a_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Alpha value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+
+
+class RGBColorProxy:
+    def __init__(self, this: 'Color', r: GetSetProxy, g: GetSetProxy, b: GetSetProxy):
+        self.__this = this
+        self.__rp = r
+        self.__gp = g
+        self.__bp = b
+
+    @property
+    def red(self) -> float:
+        return self.__rp.get()
+
+    @red.setter
+    def red(self, new: float):
+        self.__rp.set(new)
+
+    @property
+    def green(self) -> float:
+        return self.__gp.get()
+
+    @green.setter
+    def green(self, new: float):
+        self.__gp.set(new)
+
+    @property
+    def blue(self) -> float:
+        return self.__bp.get()
+
+    @blue.setter
+    def blue(self, new: float):
+        self.__bp.set(new)
+
+    def __iter__(self):
+        yield self.red
+        yield self.green
+        yield self.blue
+
+    def __repr__(self):
+        return '<{cls} red: {red}, green: {green}, blue: {blue}>'.format(
+            cls=self.__class__.__name__,
+            red='%.3f' % (self.red,),
+            green='%.3f' % (self.green,),
+            blue='%.3f' % (self.blue,),
+        )
+
+
+_hsv_h_mapper = _round_mapper(0.0, 1.0)
+_hsv_s_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Saturation value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+_hsv_v_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Brightness(value) value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+
+
+class HSVColorProxy:
+    def __init__(self, this: 'Color', h: GetSetProxy, s: GetSetProxy, v: GetSetProxy):
+        this.__this = this
+        self.__hp = h
+        self.__sp = s
+        self.__vp = v
+
+    @property
+    def hue(self) -> float:
+        return self.__hp.get()
+
+    @hue.setter
+    def hue(self, new: float):
+        self.__hp.set(new)
+
+    @property
+    def saturation(self) -> float:
+        return self.__sp.get()
+
+    @saturation.setter
+    def saturation(self, new: float):
+        self.__sp.set(new)
+
+    @property
+    def value(self) -> float:
+        return self.__vp.get()
+
+    @value.setter
+    def value(self, new: float):
+        self.__vp.set(new)
+
+    def __iter__(self):
+        yield self.hue
+        yield self.saturation
+        yield self.value
+
+    def __repr__(self):
+        return '<{cls} hue: {hue}, saturation: {saturation}, value: {value}>'.format(
+            cls=self.__class__.__name__,
+            hue='%.3f' % (self.hue,),
+            saturation='%.3f' % (self.saturation,),
+            value='%.3f' % (self.value,),
+        )
+
+
+_hls_h_mapper = _round_mapper(0.0, 1.0)
+_hls_l_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Lightness value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+_hls_s_mapper = _range_mapper(0.0, 1.0, lambda v, min_, max_: Warning(
+    'Saturation value should be no less than %.3d and no more than %.3d, but %.3d found.' % (min_, max_, v)))
+
+
+class HLSColorProxy:
+    def __init__(self, this: 'Color', h: GetSetProxy, l: GetSetProxy, s: GetSetProxy):
+        this.__this = this
+        self.__hp = h
+        self.__lp = l
+        self.__sp = s
+
+    @property
+    def hue(self) -> float:
+        return self.__hp.get()
+
+    @hue.setter
+    def hue(self, new: float):
+        self.__hp.set(new)
+
+    @property
+    def lightness(self) -> float:
+        return self.__lp.get()
+
+    @lightness.setter
+    def lightness(self, new: float):
+        self.__lp.set(new)
+
+    @property
+    def saturation(self) -> float:
+        return self.__sp.get()
+
+    @saturation.setter
+    def saturation(self, new: float):
+        self.__sp.set(new)
+
+    def __iter__(self):
+        yield self.hue
+        yield self.lightness
+        yield self.saturation
+
+    def __repr__(self):
+        return '<{cls} hue: {hue}, lightness: {lightness}, saturation: {saturation}>'.format(
+            cls=self.__class__.__name__,
+            hue='%.3f' % (self.hue,),
+            lightness='%.3f' % (self.lightness,),
+            saturation='%.3f' % (self.saturation,),
+        )
+
+
+_ratio_to_255 = lambda x: int(round(x * 255))
+_ratio_to_hex = post_process(lambda x: '%02x' % (x,))(_ratio_to_255)
+_hex_to_255 = lambda x: int(x, base=16) if x is not None else None
+_hex_to_ratio = post_process(lambda x: x / 255.0 if x is not None else None)(_hex_to_255)
+
+_RGB_COLOR_PATTERN = re.compile(r'^#?([a-zA-Z\d]{2})([a-zA-Z\d]{2})([a-zA-Z\d]{2})([a-zA-Z\d]{2}|)$')
+
+
+@freduce(init=None)
+def _ratio_or(a, b):
+    return b if a is None else a
+
+
+class Color:
+    """
+    Overview:
+        Color utility object.
+    """
+
+    def __init__(self, c: Union[str, Tuple[float, float, float]], alpha: Optional[float] = None):
+        """
+        Overview:
+            Constructor of ``Color``.
+
+        Arguments:
+            - c (:obj:`Union[str, Tuple[float, float, float]]`): Color value, can be hex string value \
+                or tuple rgb value.
+            - alpha: (:obj:`Optional[float]`): Alpha value of color, \
+                default is `None` which means no alpha value.
+        """
+        if isinstance(c, tuple):
+            self.__r, self.__g, self.__b = _r_mapper(c[0]), _g_mapper(c[1]), _b_mapper(c[2])
+            self.__alpha = _a_mapper(alpha) if alpha is not None else None
+        elif isinstance(c, str):
+            _finding = _RGB_COLOR_PATTERN.findall(c)
+            if _finding:
+                _first = _finding[0]
+                rs, gs, bs, as_ = _first
+                as_ = None if not as_ else as_
+
+                r, g, b, a = map(_hex_to_ratio, (rs, gs, bs, as_))
+                if alpha is not None:
+                    a = a * alpha if a is not None else alpha
+
+                self.__init__((r, g, b), a)
+            else:
+                raise ValueError("Invalid string color, matching of pattern {pattern} "
+                                 "expected but {actual} found.".format(pattern=repr(_RGB_COLOR_PATTERN.pattern),
+                                                                       actual=repr(c), ))
+        else:
+            raise TypeError('Unknown color value - {c}.'.format(c=repr(c)))
+
+    @property
+    def alpha(self) -> Optional[float]:
+        """
+        Overview:
+            Get value of alpha.
+
+        Returns:
+            - alpha (:obj:`Optional[float]`): Alpha value.
+        """
+        return self.__alpha
+
+    @alpha.setter
+    def alpha(self, new: Optional[float]):
+        """
+        Overview:
+            Set value of alpha.
+
+        Arguments:
+            - new (:obj:`Optional[float]`): New value of alpha.
+        """
+        if new is not None:
+            new = _a_mapper(new)
+        self.__alpha = new
+
+    def __get_rgb(self):
+        return self.__r, self.__g, self.__b
+
+    def __set_rgb(self, r=None, g=None, b=None):
+        self.__r, self.__g, self.__b = map(lambda args: _ratio_or(*args), zip((r, g, b), self.__get_rgb()))
+
+    @property
+    def rgb(self) -> RGBColorProxy:
+        """
+        Overview:
+            Get rgb color system based color proxy.
+
+        Returns:
+            - proxy (:obj:`RGBColorProxy`): Rgb color proxy.
+        """
+        return RGBColorProxy(
+            self,
+            GetSetProxy(
+                lambda: self.__r,
+                lambda x: self.__set_rgb(r=_r_mapper(x)),
+            ),
+            GetSetProxy(
+                lambda: self.__g,
+                lambda x: self.__set_rgb(g=_g_mapper(x)),
+            ),
+            GetSetProxy(
+                lambda: self.__b,
+                lambda x: self.__set_rgb(b=_b_mapper(x)),
+            ),
+        )
+
+    def __get_hsv(self):
+        return colorsys.rgb_to_hsv(self.__r, self.__g, self.__b)
+
+    def __set_hsv(self, h=None, s=None, v=None):
+        h, s, v = map(lambda args: _ratio_or(*args), zip((h, s, v), self.__get_hsv()))
+        self.__r, self.__g, self.__b = colorsys.hsv_to_rgb(h, s, v)
+
+    @property
+    def hsv(self) -> HSVColorProxy:
+        """
+        Overview:
+            Get hsv color system based color proxy.
+
+        Returns:
+            - proxy (:obj:`HSVColorProxy`): Hsv color proxy.
+        """
+        return HSVColorProxy(
+            self,
+            GetSetProxy(
+                lambda: self.__get_hsv()[0],
+                lambda x: self.__set_hsv(h=_hsv_h_mapper(x)),
+            ),
+            GetSetProxy(
+                lambda: self.__get_hsv()[1],
+                lambda x: self.__set_hsv(s=_hsv_s_mapper(x)),
+            ),
+            GetSetProxy(
+                lambda: self.__get_hsv()[2],
+                lambda x: self.__set_hsv(v=_hsv_v_mapper(x)),
+            ),
+        )
+
+    def __get_hls(self):
+        return colorsys.rgb_to_hls(self.__r, self.__g, self.__b)
+
+    def __set_hls(self, h=None, l_=None, s=None):
+        h, l, s = map(lambda args: _ratio_or(*args), zip((h, l_, s), self.__get_hls()))
+        self.__r, self.__g, self.__b = colorsys.hls_to_rgb(h, l, s)
+
+    @property
+    def hls(self) -> HLSColorProxy:
+        """
+        Overview:
+            Get hls color system based color proxy.
+
+        Returns:
+            - proxy (:obj:`HLSColorProxy`): Hls color proxy.
+        """
+        return HLSColorProxy(
+            self,
+            GetSetProxy(
+                lambda: self.__get_hls()[0],
+                lambda x: self.__set_hls(h=_hls_h_mapper(x)),
+            ),
+            GetSetProxy(
+                lambda: self.__get_hls()[1],
+                lambda x: self.__set_hls(l_=_hls_l_mapper(x)),
+            ),
+            GetSetProxy(
+                lambda: self.__get_hls()[2],
+                lambda x: self.__set_hls(s=_hls_s_mapper(x)),
+            ),
+        )
+
+    def __get_hex(self, include_alpha: bool):
+        rs, gs, bs = _ratio_to_hex(self.__r), _ratio_to_hex(self.__g), _ratio_to_hex(self.__b)
+        as_ = _ratio_to_hex(self.__alpha) if self.__alpha is not None and include_alpha else ''
+
+        return '#' + rs + gs + bs + as_
+
+    def __repr__(self):
+        if self.__alpha is not None:
+            return '<{cls} {hex}, alpha: {alpha}>'.format(
+                cls=self.__class__.__name__,
+                hex=self.__get_hex(False),
+                alpha='%.3f' % (self.__alpha,),
+            )
+        else:
+            return '<{cls} {hex}>'.format(
+                cls=self.__class__.__name__,
+                hex=self.__get_hex(False),
+            )
+
+    def __str__(self):
+        return self.__get_hex(True)
+
+    def __getstate__(self) -> Tuple[float, float, float, Optional[float]]:
+        """
+        Overview:
+            Dump color as pickle object.
+
+        Returns:
+            - info (:obj:`Tuple[float, float, float, Optional[float]]`): Dumped data object.
+        """
+        return self.__r, self.__g, self.__b, self.__alpha
+
+    def __setstate__(self, v: Tuple[float, float, float, Optional[float]]):
+        """
+        Overview:
+            Load color from pickle object.
+        Args:
+            - v (:obj:`Tuple[float, float, float, Optional[float]]`): Dumped data object.
+        """
+        self.__r, self.__g, self.__b, self.__alpha = v
+
+    def __hash__(self):
+        """
+        Overview:
+            Get hash value of current object.
+
+        Returns:
+            - hash (:obj:`int`): Hash value of current color.
+        """
+        return hash(self.__getstate__())
+
+    def __eq__(self, other):
+        """
+        Overview:
+            Get equality between colors.
+
+        Arguments:
+            - other: Another object.
+
+        Returns:
+            - equal (:obj:`bool`): Equal or not.
+        """
+        if other is self:
+            return True
+        elif type(other) == type(self):
+            return other.__getstate__() == self.__getstate__()
+        else:
+            return False
+
+    @classmethod
+    def from_hsv(cls, h, s, v, alpha=None) -> 'Color':
+        """
+        Overview:
+            Load color from hsv system.
+
+        Arguments:
+            - h (:obj:`float`): Hue value, should be a float value in :math:`\\left[0, 1\\right)`.
+            - s (:obj:`float`): Saturation value, should be a float value in :math:`\\left[0, 1\\right]`.
+            - v (:obj:`float`): Brightness (value) value, should be a float value \
+                in :math:`\\left[0, 1\\right]`.
+            - alpha (:obj:`Optional[float]`): Alpha value, should be a float value \
+                in :math:`\\left[0, 1\\right]`, default is None which means no alpha value is used.
+
+        Returns:
+            - color (:obj:`Color`): Color object.
+        """
+        return cls(colorsys.hsv_to_rgb(h, s, v), alpha)
+
+    @classmethod
+    def from_hls(cls, h: float, l: float, s: float, alpha: Optional[float] = None) -> 'Color':
+        """
+        Overview:
+            Load color from hls system.
+
+        Arguments:
+            - h (:obj:`float`): Hue value, should be a float value in :math:`\\left[0, 1\\right)`.
+            - l (:obj:`float`): Lightness value, should be a float value in :math:`\\left[0, 1\\right]`.
+            - s (:obj:`float`): Saturation value, should be a float value in :math:`\\left[0, 1\\right]`.
+            - alpha (:obj:`Optional[float]`): Alpha value, should be a float value \
+                in :math:`\\left[0, 1\\right]`, default is None which means no alpha value is used.
+
+        Returns:
+            - color (:obj:`Color`): Color object.
+        """
+        return cls(colorsys.hls_to_rgb(h, l, s), alpha)
diff --git a/treevalue/utils/exception.py b/treevalue/utils/exception.py
new file mode 100644
index 0000000000..f7c1d9f4b0
--- /dev/null
+++ b/treevalue/utils/exception.py
@@ -0,0 +1,31 @@
+import io
+import traceback
+
+
+def str_traceback(err: BaseException) -> str:
+    """
+    Overview:
+        Get full backtrace for exception object.
+
+    Arguments:
+        - err (:obj:`BaseException`): Exception object.
+
+    Returns:
+        - backtrace (:obj:`str`): Full string backtrace.
+
+    Example:
+        >>> try:
+        >>>     raise RuntimeError('runtime error')
+        >>> except Exception as err:
+        >>>     s = str_traceback(err)
+
+        The output should be like
+
+        >>> Traceback (most recent call last):
+        >>>   File "<stdin>", line 2, in <module>
+        >>>     raise RuntimeError('runtime error')
+        >>> RuntimeError: runtime error
+    """
+    with io.StringIO() as fs:
+        traceback.print_exception(type(err), err, err.__traceback__, file=fs)
+        return fs.getvalue()
diff --git a/treevalue/utils/func.py b/treevalue/utils/func.py
index ee498922c1..6e5ec15dd5 100644
--- a/treevalue/utils/func.py
+++ b/treevalue/utils/func.py
@@ -1,7 +1,10 @@
+import warnings
 from functools import wraps
 from inspect import signature, Parameter
 from itertools import chain
-from typing import Callable
+from typing import Callable, TypeVar, Union, Type
+
+from .singleton import SingletonMark
 
 
 def args_iter(*args, **kwargs):
@@ -34,6 +37,9 @@ def args_iter(*args, **kwargs):
         yield _index, _item
 
 
+_STATICAL = '__statical__'
+
+
 def dynamic_call(func: Callable):
     """
     Overview:
@@ -52,6 +58,9 @@ def dynamic_call(func: Callable):
         >>> dynamic_call(lambda x, y: (x, y))(y=2, x=1)  # (1, 2), key word supported
         >>> dynamic_call(lambda x, y, **kwargs: (kwargs, x, y))(1, k=2, y=3)  # ({'k': 2}, 1, 3)
     """
+    if getattr(func, _STATICAL, None):
+        return func
+
     enable_args, args_count = False, 0
     enable_kwargs, kwargs_set = False, set()
 
@@ -75,4 +84,245 @@ def _get_kwargs(**kwargs):
     def _new_func(*args, **kwargs):
         return func(*_get_args(*args), **_get_kwargs(**kwargs))
 
+    setattr(_new_func, _STATICAL, True)
     return _new_func
+
+
+def static_call(func: Callable, static_ok: bool = True):
+    """
+    Overview:
+        Static call, anti-calculation of dynamic call.
+
+    Arguments:
+        - func (:obj:`Callable`): Given dynamic function.
+        - static_ok (:obj:`bool`): Allow given function to be static, default is `True`.
+
+    Returns:
+        - static (:obj:`Callable`): Static function.
+    """
+    if not static_ok and not getattr(func, _STATICAL, None):
+        raise TypeError("Given callable is already static.")
+
+    return getattr(func, '__wrapped__', func)
+
+
+def pre_process(processor: Callable):
+    """
+    Overview:
+        Pre processor for function.
+
+    Arguments:
+        - processor (:obj:`Callable`): Pre processor.
+
+    Returns:
+        - decorator (:obj:`Callable`): Function decorator
+
+    Example:
+        >>> @pre_process(lambda x, y: (-x, (x + 2) * y))
+        >>> def plus(a, b):
+        >>>     return a + b
+        >>>
+        >>> plus(1, 2)  # 5, 5 = -1 + (1 + 2) * 2
+    """
+    _processor = dynamic_call(processor)
+
+    def _decorator(func):
+        @wraps(func)
+        def _new_func(*args, **kwargs):
+            pargs = _processor(*args, **kwargs)
+
+            if isinstance(pargs, tuple) and len(pargs) == 2 \
+                    and isinstance(pargs[0], (list, tuple)) \
+                    and isinstance(pargs[1], (dict,)):
+                args_, kwargs_ = tuple(pargs[0]), dict(pargs[1])
+            elif isinstance(pargs, (tuple, list)):
+                args_, kwargs_ = tuple(pargs), {}
+            elif isinstance(pargs, (dict,)):
+                args_, kwargs_ = (), dict(pargs)
+            else:
+                args_, kwargs_ = (pargs,), {}
+
+            return func(*args_, **kwargs_)
+
+        return _new_func
+
+    return _decorator
+
+
+def post_process(processor: Callable):
+    """
+    Overview:
+        Post processor for function.
+
+    Arguments:
+        - processor (:obj:`Callable`): Post processor.
+
+    Returns:
+        - decorator (:obj:`Callable`): Function decorator
+
+    Example:
+        >>> @post_process(lambda x: -x)
+        >>> def plus(a, b):
+        >>>     return a + b
+        >>>
+        >>> plus(1, 2)  # -3
+    """
+    processor = dynamic_call(processor)
+
+    def _decorator(func):
+        @wraps(func)
+        def _new_func(*args, **kwargs):
+            _result = func(*args, **kwargs)
+            return processor(_result)
+
+        return _new_func
+
+    return _decorator
+
+
+def _is_throwable(err):
+    return isinstance(err, BaseException) or (isinstance(err, type) and issubclass(err, BaseException))
+
+
+def _post_for_raising(ret):
+    if _is_throwable(ret):
+        raise ret
+    else:
+        return ret
+
+
+def raising(func: Union[Callable, BaseException, Type[BaseException]]):
+    """
+    Overview:
+        Decorate function with exception object return value to a raisisng function.
+
+    Arguments:
+        - func (:obj:`Union[Callable, BaseException, Type[BaseException]]`): Not decorated function or class
+
+    Returns:
+        - decorated (:obj:`Callable`): Decorated new function
+
+    Examples:
+        >>> raising(RuntimeError)()  # RuntimeError
+        >>> raising(lambda x: ValueError('value error - %s' % (repr(x), )))(1)  # ValueError, value error - 1
+    """
+    if _is_throwable(func):
+        return raising(dynamic_call(lambda: func))
+    else:
+        return post_process(_post_for_raising)(func)
+
+
+def _is_warning(w):
+    return isinstance(w, (Warning, str)) or (isinstance(w, type) and issubclass(w, Warning))
+
+
+def _warn(w):
+    return w() if _is_warning(w) and isinstance(w, type) and issubclass(w, Warning) else w
+
+
+def _post_for_warning(ret):
+    _matched = False
+    if _is_warning(ret):
+        _matched, _w, args_, kwargs_ = True, ret, (), {}
+    elif isinstance(ret, tuple) and len(ret) >= 1 and _is_warning(ret[0]):
+        _w, ret = ret[0], ret[1:]
+        if len(ret) == 1:
+            if isinstance(ret[0], tuple):
+                _matched, args_, kwargs_ = True, ret[0], {}
+            elif isinstance(ret[0], dict):
+                _matched, args_, kwargs_ = True, (), ret[0]
+        elif len(ret) == 2:
+            if isinstance(ret[0], tuple) and isinstance(ret[1], dict):
+                _matched, args_, kwargs_ = True, ret[0], ret[1]
+
+    if not _matched:
+        return ret
+    else:
+        # noinspection PyUnboundLocalVariable
+        warnings.warn(_warn(_w), *args_, **kwargs_)
+
+
+def warning_(func: Union[Callable, Warning, Type[Warning], str]):
+    """
+    Overview:
+        Decorate function with exception object return value to a ``warning_`` function.
+
+    Arguments:
+        - func (:obj:`Union[Callable, Warning, Type[Warning], str]`): Not decorated function or class
+
+    Returns:
+        - decorated (:obj:`Callable`): Decorated new function
+
+    Examples:
+        >>> warning_(RuntimeWarning)()  # RuntimeWarning
+        >>> raising(lambda x: Warning('value warning - %s' % (repr(x), )))(1)  # Warning, value warning - 1
+    """
+    if _is_warning(func):
+        return warning_(dynamic_call(lambda: func))
+    else:
+        return post_process(_post_for_warning)(func)
+
+
+NO_INITIAL = SingletonMark("no_initial")
+
+_ElementType = TypeVar("_ElementType")
+
+
+def freduce(init=NO_INITIAL, pass_kwargs: bool = True):
+    """
+    Overview:
+        Make binary function be reducible.
+
+    Arguments:
+        - init (:obj:`Any`): Initial data generator or \
+            initial data, default is `NO_INITIAL` which means no initial data. \
+            Missing of positional arguments is forbidden.
+        - pass_kwargs (:obj:`bool`): Pass kwargs into initial function and wrapped function or not, \
+            default is `True` which means pass the arguments in.
+
+    Returns:
+        - decorator (:obj:`Callable`): Decorator for the original function.
+
+    Example:
+        >>> @freduce(init=0)
+        >>> def plus(a, b):
+        >>>     return a + b
+        >>>
+        >>> plus()            # 0
+        >>> plus(1)           # 1
+        >>> plus(1, 2)        # 3
+        >>> plus(1, 2, 3, 4)  # 10
+    """
+    if init is NO_INITIAL:
+        init_func = None
+    else:
+        init_func = dynamic_call(init if hasattr(init, '__call__') else (lambda: init))
+
+    def _decorator(func):
+        func = dynamic_call(func)
+
+        @wraps(func)
+        def _new_func(*args, **kwargs) -> _ElementType:
+            if not pass_kwargs and kwargs:
+                warnings.warn(SyntaxWarning(
+                    "Key-word arguments detected but will not be passed due to the pass_kwargs setting - {kwargs}.".format(
+                        kwargs=repr(kwargs))))
+            kwargs = kwargs if pass_kwargs else {}
+
+            if init_func is None:
+                if not args:
+                    raise SyntaxError(
+                        "No less than 1 argument expected in function {func} but 0 found.".format(func=repr(func)))
+                current = args[0]
+                args = args[1:]
+            else:
+                current = init_func(**kwargs)
+
+            for arg in args:
+                current = func(current, arg, **kwargs)
+
+            return current
+
+        return _new_func
+
+    return _decorator
diff --git a/treevalue/utils/imports.py b/treevalue/utils/imports.py
new file mode 100644
index 0000000000..c3775cd9e8
--- /dev/null
+++ b/treevalue/utils/imports.py
@@ -0,0 +1,114 @@
+import builtins
+import fnmatch
+import importlib
+from itertools import islice
+from queue import Queue
+from typing import Optional, Callable, Any, Tuple, Iterator
+
+from .func import dynamic_call
+
+
+def _import_module(module_name=None):
+    if module_name:
+        return importlib.import_module(module_name)
+    else:
+        return builtins
+
+
+def import_object(obj_name: str, module_name: Optional[str] = None):
+    """
+    Overview:
+        Dynamically import an object from module.
+
+    Arguments:
+        - obj_name (:obj:`str`): Name of the object.
+        - module_name (:obj:`Optional[str]`): Name of the module, \
+            default is ``None`` which means the ``builtins`` module.
+
+    Returns:
+        - obj: Imported object.
+
+    Example::
+        >>> import_object('zip')               # <class 'zip'>
+        >>> import_object('ndarray', 'numpy')  # <class 'numpy.ndarray'>
+    """
+    return getattr(_import_module(module_name), obj_name)
+
+
+def quick_import_object(full_name: str, predicate: Optional[Callable] = None) -> Tuple[Any, str, str]:
+    """
+    Overview:
+        Quickly dynamically import an object with a single name.
+
+    Arguments:
+        - full_name (:obj:`str`): Full name of the object, attribute is supported as well.
+        - predicate (:obj:`Callable`): Predicate function, default is ``None`` means no limitation.
+
+    Returns:
+        - obj (:obj:`Tuple[Any, str, str]`): Imported object.
+
+    Example::
+        >>> quick_import_object('zip')                     # <class 'zip'>, '', 'zip'
+        >>> quick_import_object('numpy.ndarray')           # <class 'numpy.ndarray'>, 'numpy', 'ndarray'
+        >>> quick_import_object('numpy.ndarray.__name__')  # 'ndarray', 'numpy', 'ndarray.__name__'
+    """
+    _iter = islice(iter_import_objects(full_name, predicate), 1)
+
+    try:
+        # noinspection PyTupleAssignmentBalance
+        _obj, _module, _name = next(_iter)
+        return _obj, _module, _name
+    except (StopIteration, StopAsyncIteration):
+        raise ImportError(f'Cannot import object {repr(full_name)}.')
+
+
+def iter_import_objects(full_pattern: str, predicate: Optional[Callable] = None) \
+        -> Iterator[Tuple[Any, str, str]]:
+    """
+    Overview:
+        Quickly dynamically import all objects with full name pattern.
+
+    Arguments:
+        - full_pattern (:obj:`str`): Full pattern of the object, attribute is supported as well.
+        - predicate (:obj:`Callable`): Predicate function, default is ``None`` means no limitation.
+
+    Returns:
+        - iter (:obj:`Iterator[Tuple[Any, str, str]]`): Iterator for all the imported objects.
+    """
+    predicate = dynamic_call(predicate or (lambda: True))
+
+    segments = full_pattern.split('.')
+    length = len(segments)
+    _errs = []
+    for i in reversed(range(length + 1)):
+        module_name = '.'.join(segments[:i])
+        attrs = tuple(segments[i:])
+        attrs_count = len(attrs)
+
+        try:
+            module = importlib.import_module(module_name or 'builtins')
+        except (ModuleNotFoundError, ImportError):
+            continue
+
+        queue = Queue()
+        queue.put((module, 0, ()))
+        exist = False
+
+        while not queue.empty():
+            root, pos, ats = queue.get()
+
+            if pos >= attrs_count:
+                obj_name = '.'.join(ats)
+                if predicate(root, module_name, obj_name):
+                    yield root, module_name, obj_name
+            elif hasattr(root, attrs[pos]):
+                queue.put((getattr(root, attrs[pos]), pos + 1, ats + (attrs[pos],)))
+                exist = True
+            elif hasattr(root, '__dict__'):
+                for key, value in sorted(root.__dict__.items()):
+                    if fnmatch.fnmatch(key, attrs[pos]):
+                        queue.put((value, pos + 1, ats + (key,)))
+                        exist = True
+
+        if exist:
+            break
diff --git a/treevalue/utils/random.py b/treevalue/utils/random.py
new file mode 100644
index 0000000000..7f24486d7e
--- /dev/null
+++ b/treevalue/utils/random.py
@@ -0,0 +1,55 @@
+import random
+from contextlib import contextmanager
+from datetime import datetime
+from random import Random
+
+
+@contextmanager
+def seed_random(seed):
+    """
+    Overview:
+        Get seeded random object in a `with` block.
+
+    Arguments:
+        - seed (:obj:`int`): Random seed, should be a `int`.
+    """
+    rnd = Random()
+    rnd.seed(seed)
+    try:
+        yield rnd
+    finally:
+        rnd.seed()
+
+
+def random_hex(length: int = 32) -> str:
+    """
+    Overview:
+        Generate random hex string.
+
+    Arguments:
+        - length (:obj:`int`): Length of hex string, default is `32`.
+
+    Returns:
+        - string (:obj:`str`): Generated string.
+
+    Examples:
+        >>> random_hex()  # 'ca7f14b25aa4498efdacb54e9ff72784'
+    """
+    return ''.join([hex(random.randint(0, 15))[2:] for _ in range(length)])
+
+
+def random_hex_with_timestamp(length: int = 12) -> str:
+    """
+    Overview:
+        Generate random hex string, with prefix of timestamp.
+
+    Arguments:
+        - length (:obj:`int`): Length of hex string, default is `12`.
+
+    Returns:
+        - string (:obj:`str`): Generated string.
+
+    Examples:
+        >>> random_hex_with_timestamp()  # '20210729_202059576266_69603d64afad'
+    """
+    return datetime.now().strftime("%Y%m%d_%H%M%S%f") + "_" + random_hex(length)
diff --git a/treevalue/utils/tree.py b/treevalue/utils/tree.py
index 1e43484602..85f48e5c1a 100644
--- a/treevalue/utils/tree.py
+++ b/treevalue/utils/tree.py
@@ -1,21 +1,28 @@
+import re
+from functools import wraps
 from queue import Queue
+from typing import Optional, Mapping, Any, Callable
 
+from graphviz import Digraph
 from treelib import Tree as LibTree
 
+from .func import dynamic_call, post_process
+from .random import random_hex_with_timestamp
+
 _ROOT_ID = '_root'
 _NODE_ID_TEMP = '_node_{id}'
 
 
-def build_tree(root, represent=None, iterate=None, recurse=None) -> LibTree:
+def build_tree(root_node, repr_gen=None, iter_gen=None) -> LibTree:
     """
     Overview:
         Build a treelib object by an object.
 
     Arguments:
-        - root (:obj:`Any`): Root object.
-        - represent (:obj:`Optional[Callable]`): Represent function, default is primitive `repr`.
-        - iterate (:obj:`Optional[Callable]`): Iterate function, default is `lambda x: x.items()`.
-        - recurse (:obj:`Optional[Callable]`): Recurse check function, default is `lambda x: hasattr(x, 'items')`.
+        - root_node (:obj:`Any`): Root object.
+        - repr_gen (:obj:`Optional[Callable]`): Represent function, default is primitive `repr`.
+        - iter_gen (:obj:`Optional[Callable]`): Iterate function, \
+            default is `lambda x: x.items() if hasattr(x, 'items') else None`.
 
     Returns:
         - tree (:obj:`treelib.Tree`): Built tree.
@@ -23,8 +30,7 @@ def build_tree(root, represent=None, iterate=None, recurse=None) -> LibTree:
     Example:
          >>> t = build_tree(
          >>>     {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}, 'z': [1, 2], 'v': {'1': '2'}},
-         >>>     represent=lambda x: '<node>' if isinstance(x, dict) else repr(x),
-         >>>     recurse=lambda x: isinstance(x, dict),
+         >>>     repr_gen=lambda x: '<node>' if isinstance(x, dict) else repr(x),
          >>> )
          >>> print(t)
 
@@ -40,27 +46,180 @@ def build_tree(root, represent=None, iterate=None, recurse=None) -> LibTree:
          >>> │   └── 'd' --> 4
          >>> └── 'z' --> [1, 2]
     """
-    represent = represent or repr
-    iterate = iterate or (lambda x: x.items())
-    recurse = recurse or (lambda x: hasattr(x, 'items'))
+    repr_gen = repr_gen or repr
+    iter_gen = iter_gen or (lambda x: x.items() if hasattr(x, 'items') else None)
 
     _tree = LibTree()
-    _tree.create_node(represent(root), _ROOT_ID)
+    _tree.create_node(repr_gen(root_node), _ROOT_ID)
     _index, _queue = 0, Queue()
-    _queue.put((_ROOT_ID, root))
+    _queue.put((_ROOT_ID, root_node))
 
     while not _queue.empty():
         _parent_id, _parent_tree = _queue.get()
 
-        for key, value in iterate(_parent_tree):
+        for key, value in iter_gen(_parent_tree):
             _index += 1
             _current_id = _NODE_ID_TEMP.format(id=_index)
             _tree.create_node(
-                "{key} --> {value}".format(key=repr(key), value=represent(value)),
+                "{key} --> {value}".format(key=repr(key), value=repr_gen(value)),
                 _current_id,
                 _parent_id
             )
-            if recurse(value):
+            if iter_gen(value):
                 _queue.put((_current_id, value))
 
     return _tree
+
+
+_NAME_PATTERN = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
+
+
+def _title_flatten(title):
+    title = re.sub(r'[^a-zA-Z0-9_]+', '_', str(title))
+    title = re.sub(r'_+', '_', title)
+    title = title.strip('_').lower()
+    return title
+
+
+def _no_none_value(dict_) -> dict:
+    return type(dict_)({key: value for key, value in dict_.items() if value is not None})
+
+
+def _cfg_func_wrap(func):
+    @wraps(func)
+    @post_process(lambda d: type(d)({str(key): str(value) for key, value in d.items()}))
+    @post_process(_no_none_value)
+    def _new_func(*args, **kwargs):
+        return func(*args, **kwargs)
+
+    return _new_func
+
+
+SUFFIXED_TAG = '__suffixed__'
+
+
+def suffixed_node_id(func):
+    if getattr(func, SUFFIXED_TAG, None):
+        return func
+
+    func = dynamic_call(func)
+
+    @wraps(func)
+    @dynamic_call
+    def _new_func(current, parent, current_path, parent_path, is_node):
+        if is_node:
+            return func(current, current_path)
+        else:
+            return '%s__%s' % (func(parent, parent_path), current_path[-1])
+
+    setattr(_new_func, SUFFIXED_TAG, True)
+    return _new_func
+
+
+@suffixed_node_id
+@dynamic_call
+def _default_node_id(current):
+    return 'node_%x' % (id(current),)
+
+
+def _root_process(root, index):
+    if isinstance(root, tuple):
+        if len(root) < 1:
+            return None
+        elif len(root) == 1:
+            return _root_process(root[0], index)
+        else:
+            return root[0], str(root[1]), index
+    else:
+        return root, '<root_%d>' % (index,), index
+
+
+def build_graph(*roots, node_id_gen: Optional[Callable] = None,
+                graph_title: Optional[str] = None, graph_name: Optional[str] = None,
+                graph_cfg: Optional[Mapping[str, Any]] = None,
+                repr_gen: Optional[Callable] = None, iter_gen: Optional[Callable] = None,
+                node_cfg_gen: Optional[Callable] = None, edge_cfg_gen: Optional[Callable] = None) -> Digraph:
+    """
+    Overview:
+        Build a graphviz graph based on given tree structure.
+
+    Arguments:
+        - roots: Root nodes of the graph.
+        - node_id_gen (:obj:`Optional[Callable]`): Node id generation function, \
+            default is `None` which means based on object id.
+        - graph_title (:obj:`Optional[str]`): Title of the graph, \
+            default is `None` which means generate automatically based on timestamp.
+        - graph_name (:obj:`Optional[str]`): Name of the graph, \
+            default is `None` which means auto generated based on graph title.
+        - graph_cfg (:obj:`Optional[Mapping[str, Any]]`): Configuration of graph, \
+            default is `None` which means no configuration.
+        - repr_gen (:obj:`Optional[Callable]`): Representation format generator, \
+            default is `None` which means using `repr` function.
+        - iter_gen (:obj:`Optional[Callable]`): Iterator generator, \
+            default is `None` which means load from `items` method.
+        - node_cfg_gen (:obj:`Optional[Callable]`): Node configuration generator, \
+            default is `None` which means no configuration.
+        - edge_cfg_gen (:obj:`Optional[Callable]`): Edge configuration generator, \
+            default is `None` which means no configuration.
+
+    Returns:
+        - dot (:obj:`Digraph`): Graphviz directed graph object.
+    """
+    roots = [_root_process(root, index) for index, root in enumerate(roots)]
+    roots = [item for item in roots if item is not None]
+
+    node_id_gen = dynamic_call(suffixed_node_id(node_id_gen or _default_node_id))
+    graph_title = graph_title or ('untitled_' + random_hex_with_timestamp())
+    graph_name = graph_name or _title_flatten(graph_title)
+    graph_cfg = _no_none_value(graph_cfg or {})
+
+    repr_gen = dynamic_call(repr_gen or repr)
+    iter_gen = dynamic_call(iter_gen or (lambda x: x.items() if hasattr(x, 'items') else None))
+    node_cfg_gen = _cfg_func_wrap(dynamic_call(node_cfg_gen or (lambda: {})))
+    edge_cfg_gen = _cfg_func_wrap(dynamic_call(edge_cfg_gen or (lambda: {})))
+
+    graph = Digraph(name=graph_name, comment=graph_title)
+    graph.graph_attr.update(graph_cfg or {})
+    graph.graph_attr.update({'label': graph_title})
+
+    _queue = Queue()
+    _queued_node_ids = set()
+    _queued_edges = set()
+    for root_info in roots:
+        root, root_title, root_index = root_info
+        root_node_id = node_id_gen(root, None, [], [], True)
+        if root_node_id not in _queued_node_ids:
+            graph.node(
+                name=root_node_id, label=root_title,
+                **node_cfg_gen(root, None, [], [], True, True, root_info)
+            )
+            _queue.put((root_node_id, root, (root, root_title, root_index), []))
+            _queued_node_ids.add(root_node_id)
+
+    while not _queue.empty():
+        _parent_id, _parent_node, _root_info, _parent_path = _queue.get()
+        _root_node, _root_title, _root_index = _root_info
+
+        for key, _current_node in iter_gen(_parent_node, _parent_path):
+            _current_path = [*_parent_path, key]
+            _is_node = not not iter_gen(_current_node, _current_path)
+            _current_id = node_id_gen(_current_node, _parent_node, _current_path, _parent_path, _is_node)
+            if iter_gen(_current_node, _current_path):
+                _current_label = '.'.join([_root_title, *_current_path])
+            else:
+                _current_label = repr_gen(_current_node, _current_path)
+
+            if _current_id not in _queued_node_ids:
+                graph.node(_current_id, label=_current_label,
+                           **node_cfg_gen(_current_node, _parent_node, _current_path, _parent_path,
+                                          _is_node, False, _root_info))
+                if iter_gen(_current_node, _current_path):
+                    _queue.put((_current_id, _current_node, _root_info, _current_path))
+                _queued_node_ids.add(_current_id)
+            if (_parent_id, _current_id, key) not in _queued_edges:
+                graph.edge(_parent_id, _current_id, label=key,
+                           **edge_cfg_gen(_current_node, _parent_node, _current_path, _parent_path,
+                                          _is_node, _root_info))
+                _queued_edges.add((_parent_id, _current_id, key))
+
+    return graph


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2e4f6825a2..7e8ac372ef 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,7 +11,7 @@ jobs:
     strategy:
       matrix:
         os:
-          - 'ubuntu-latest'
+          - 'ubuntu-18.04'
         python-version:
           - '3.6'
           - '3.7'
@@ -27,8 +27,9 @@ jobs:
         if: ${{ runner.os == 'Linux' }}
         run: |
           sudo apt-get update
-          sudo apt-get install -y tree cloc wget curl make
+          sudo apt-get install -y tree cloc wget curl make graphviz
           sudo apt-get install -y libxml2-dev libxslt-dev python-dev  # need by pypy3
+          dot -V
       - name: Set up python ${{ matrix.python-version }}
         uses: actions/setup-python@v2
         with:
diff --git a/test/__init__.py b/test/__init__.py
index 4227a5dfdd..76423fddea 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -1,3 +1,4 @@
 from .config import *
+from .entry import *
 from .tree import *
 from .utils import *
diff --git a/test/entry/__init__.py b/test/entry/__init__.py
new file mode 100644
index 0000000000..c515f6299f
--- /dev/null
+++ b/test/entry/__init__.py
@@ -0,0 +1,2 @@
+from .cli import *
+from .script import *
diff --git a/test/entry/cli/__init__.py b/test/entry/cli/__init__.py
new file mode 100644
index 0000000000..b465c4088c
--- /dev/null
+++ b/test/entry/cli/__init__.py
@@ -0,0 +1,3 @@
+from .test_export import TestEntryCliExport
+from .test_graph import TestEntryCliGraph
+from .test_version import TestEntryCliVersion
diff --git a/test/entry/cli/test_export.py b/test/entry/cli/test_export.py
new file mode 100644
index 0000000000..b09ccd8140
--- /dev/null
+++ b/test/entry/cli/test_export.py
@@ -0,0 +1,243 @@
+import bz2
+import gzip
+import os
+import zlib
+
+import pytest
+from click.testing import CliRunner
+
+from treevalue import FastTreeValue, load
+from treevalue.entry.cli import treevalue_cli
+
+t1 = FastTreeValue({'a': 1, 'b': [2] * 1000, 'x': {'c': 3, 'd': 4}})
+t2 = FastTreeValue({'a': 1, 'b': {2, 4}, 'x': {'c': [1, 3] * 500, 'd': 4}})
+t3 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': t1.b, 'd': t2.x.c}})
+
+
+def _my_compress(d):
+    return bz2.compress(gzip.compress(zlib.compress(d)))
+
+
+def _my_decompress(d):
+    return zlib.decompress(gzip.decompress(bz2.decompress(d)))
+
+
+# noinspection DuplicatedCode
+@pytest.mark.unittest
+class TestEntryCliExport:
+    def test_simple_export(self):
+        runner = CliRunner(mix_stderr=False)
+        with runner.isolated_filesystem():
+            result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.t1'])
+
+            assert result.exit_code == 0
+            assert os.path.exists('t1.btv')
+            assert 150 < os.path.getsize('t1.btv') < 220
+
+            with open('t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t1
+
+    def test_multiple_export(self):
+        runner = CliRunner(mix_stderr=False)
+        with runner.isolated_filesystem():
+            result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*'])
+
+            assert result.exit_code == 0
+            assert os.path.exists('t1.btv')
+            assert 150 < os.path.getsize('t1.btv') < 220
+            assert os.path.exists('t2.btv')
+            assert 200 < os.path.getsize('t2.btv') < 280
+            assert os.path.exists('t3.btv')
+            assert 210 < os.path.getsize('t3.btv') < 250
+
+            with open('t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t1
+            with open('t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+        with runner.isolated_filesystem():
+            result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.t[23]'])
+
+            assert result.exit_code == 0
+            assert not os.path.exists('t1.btv')
+            assert os.path.exists('t2.btv')
+            assert 200 < os.path.getsize('t2.btv') < 280
+            assert os.path.exists('t3.btv')
+            assert 210 < os.path.getsize('t3.btv') < 250
+
+            with open('t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+    def test_multiple_export_without_compression(self):
+        runner = CliRunner(mix_stderr=False)
+        with runner.isolated_filesystem():
+            with pytest.warns(None):
+                result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*', '-r'])
+
+            assert result.exit_code == 0
+            assert os.path.exists('t1.btv')
+            assert 2120 < os.path.getsize('t1.btv') < 2160
+            assert os.path.exists('t2.btv')
+            assert 2120 < os.path.getsize('t2.btv') < 2220
+            assert os.path.exists('t3.btv')
+            assert 4120 < os.path.getsize('t3.btv') < 4160
+
+            with open('t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t1
+            with open('t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+        with runner.isolated_filesystem():
+            with pytest.warns(RuntimeWarning):
+                result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*',
+                                                       '-r', '-c', 'zlib'])
+
+            assert result.exit_code == 0
+            assert os.path.exists('t1.btv')
+            assert 2120 < os.path.getsize('t1.btv') < 2160
+            assert os.path.exists('t2.btv')
+            assert 2120 < os.path.getsize('t2.btv') < 2220
+            assert os.path.exists('t3.btv')
+            assert 4120 < os.path.getsize('t3.btv') < 4160
+
+            with open('t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t1
+            with open('t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+    def test_multiple_export_with_compress_define(self):
+        runner = CliRunner(mix_stderr=False)
+        with runner.isolated_filesystem():
+            result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*',
+                                                   '-c', 'gzip'])
+
+            assert result.exit_code == 0
+            assert os.path.exists('t1.btv')
+            assert 150 < os.path.getsize('t1.btv') < 220
+            assert os.path.exists('t2.btv')
+            assert 160 < os.path.getsize('t2.btv') < 240
+            assert os.path.exists('t3.btv')
+            assert 160 < os.path.getsize('t3.btv') < 200
+
+            with open('t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t1
+            with open('t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+        with runner.isolated_filesystem():
+            result = runner.invoke(treevalue_cli, [
+                'export', '-t', 'test.entry.cli.test_export.*',
+                '-c', 'test.entry.cli.test_export._my_compress:test.entry.cli.test_export._my_decompress'
+            ])
+
+            assert result.exit_code == 0
+            assert os.path.exists('t1.btv')
+            assert 260 < os.path.getsize('t1.btv') < 300
+            assert os.path.exists('t2.btv')
+            assert 280 < os.path.getsize('t2.btv') < 370
+            assert os.path.exists('t3.btv')
+            assert 280 < os.path.getsize('t3.btv') < 320
+
+            with open('t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t1
+            with open('t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+    def test_with_od(self):
+        runner = CliRunner(mix_stderr=False)
+        with runner.isolated_filesystem():
+            os.mkdir('subpath')
+            with pytest.warns(RuntimeWarning):
+                result = runner.invoke(treevalue_cli, [
+                    'export', '-t', 'test.entry.cli.test_export.t[23]',
+                    '-o', 'subpath/t1.btv',
+                    '-o', 'subpath/t2.btv',
+                    '-o', 'subpath/t3.btv',
+                ])
+
+            assert result.exit_code == 0
+            assert os.path.exists('subpath/t1.btv')
+            assert 200 < os.path.getsize('subpath/t1.btv') < 280
+            assert os.path.exists('subpath/t2.btv')
+            assert 210 < os.path.getsize('subpath/t2.btv') < 250
+            assert not os.path.exists('subpath/t3.btv')
+
+            with open('subpath/t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('subpath/t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+        with runner.isolated_filesystem():
+            os.mkdir('subpath')
+            with pytest.warns(RuntimeWarning):
+                result = runner.invoke(treevalue_cli, [
+                    'export', '-t', 'test.entry.cli.test_export.t[23]',
+                    '-o', 't1.btv',
+                    '-o', 't2.btv',
+                    '-o', 't3.btv',
+                    '-d', 'subpath',
+                ])
+
+            assert result.exit_code == 0
+            assert os.path.exists('subpath/t1.btv')
+            assert 200 < os.path.getsize('subpath/t1.btv') < 280
+            assert os.path.exists('subpath/t2.btv')
+            assert 210 < os.path.getsize('subpath/t2.btv') < 250
+            assert not os.path.exists('subpath/t3.btv')
+
+            with open('subpath/t1.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('subpath/t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+        with runner.isolated_filesystem():
+            os.mkdir('subpath')
+            with pytest.warns(None):
+                result = runner.invoke(treevalue_cli, [
+                    'export', '-t', 'test.entry.cli.test_export.t[23]',
+                    '-d', 'subpath',
+                ])
+
+            assert result.exit_code == 0
+            assert not os.path.exists('subpath/t1.btv')
+            assert os.path.exists('subpath/t2.btv')
+            assert 200 < os.path.getsize('subpath/t2.btv') < 280
+            assert os.path.exists('subpath/t3.btv')
+            assert 210 < os.path.getsize('subpath/t3.btv') < 250
+
+            with open('subpath/t2.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('subpath/t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
+
+        with runner.isolated_filesystem():
+            with pytest.warns(None):
+                result = runner.invoke(treevalue_cli, [
+                    'export', '-t', 'test.entry.cli.test_export.t[23]',
+                    '-o', 'ppp.btv'
+                ])
+
+            assert result.exit_code == 0
+            assert not os.path.exists('t1.btv')
+            assert not os.path.exists('t2.btv')
+            assert os.path.exists('ppp.btv')
+            assert 200 < os.path.getsize('ppp.btv') < 280
+            assert os.path.exists('t3.btv')
+            assert 210 < os.path.getsize('t3.btv') < 250
+
+            with open('ppp.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t2
+            with open('t3.btv', 'rb') as file:
+                assert load(file, type_=FastTreeValue) == t3
diff --git a/test/entry/cli/test_graph.py b/test/entry/cli/test_graph.py
new file mode 100644
index 0000000000..89fc8bfeba
--- /dev/null
+++ b/test/entry/cli/test_graph.py
@@ -0,0 +1,224 @@
+import os
+import pathlib
+import pickle
+import warnings
+import zlib
+
+import pytest
+from click.testing import CliRunner
+
+from treevalue import FastTreeValue, dump, graphics
+from treevalue.entry.cli import treevalue_cli
+
+t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+t2 = FastTreeValue({'a': 1, 'b': {2, 4}, 'x': {'c': [1, 3], 'd': 4}})
+t3 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': t2.b, 'd': t2.x.c}})
+
+g = graphics(
+    (t1, 't1'), (t2, 't2'), (t3, 't3'),
+    title='This is title for g.',
+    cfg=dict(bgcolor='#ffffff00'),
+    dup_value=(list,),
+)
+
+
+@pytest.mark.unittest
+class TestEntryCliGraph:
+    def test_simple_code_graph(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.t1',
+                      '-o', 'test_graph.svg', '-o', 'test_graph.gv'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 5830 <= os.path.getsize('test_graph.svg') <= 5930
+            assert os.path.exists('test_graph.gv')
+            assert 1800 <= os.path.getsize('test_graph.gv') <= 1900
+
+    def test_simple_code_graph_to_stdout(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            with pytest.warns(RuntimeWarning):
+                result = runner.invoke(
+                    treevalue_cli,
+                    args=['graph', '-t', 'test.entry.cli.test_graph.t1',
+                          '-o', 'test_graph.svg', '-o', 'test_graph.gv', '-O'],
+                )
+
+            assert result.exit_code == 0
+            assert not os.path.exists('test_graph.svg')
+            assert not os.path.exists('test_graph.gv')
+            assert 1800 <= len(result.output) <= 1900
+
+    def test_simple_code_multiple_graph(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.t[12]', '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 11000 <= os.path.getsize('test_graph.svg') <= 12000
+
+        with runner.isolated_filesystem():
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.*', '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 16150 <= os.path.getsize('test_graph.svg') <= 16300
+
+    def test_simple_binary_graph(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            with open('g1.bg', 'wb') as file:
+                dump(t1, file, compress=zlib)
+
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'g1.bg', '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 5830 <= os.path.getsize('test_graph.svg') <= 5930
+
+        with runner.isolated_filesystem():
+            with open('g1.bg', 'wb') as file:
+                dump(t1, file, compress=zlib)
+            with open('g2.bg', 'wb') as file:
+                dump(t2, file, compress=zlib)
+            with open('g3.bg', 'wb') as file:
+                dump(t3, file, compress=zlib)
+
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', '*.bg', '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 16150 <= os.path.getsize('test_graph.svg') <= 16300
+
+        with runner.isolated_filesystem():
+            with open('test.entry.cli.test_graph.t1', 'wb') as file:
+                dump(t2, file, compress=zlib)
+
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.t1', '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 11000 <= os.path.getsize('test_graph.svg') <= 12000
+
+        with runner.isolated_filesystem():
+            with open('test.entry.cli.test_graph.t1', 'wb') as file:
+                pickle.dump([1, 2, 3], file)
+
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.t1', '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 5830 <= os.path.getsize('test_graph.svg') <= 5930
+
+    def test_duplicates(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.t[23]', '-D',
+                      '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 10000 <= os.path.getsize('test_graph.svg') <= 10430
+
+        _p = os.path.abspath(os.curdir)
+        with runner.isolated_filesystem():
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.t[23]',
+                      '-d', 'list', '-d', 'set',
+                      '-o', 'test_graph.svg'],
+            )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            import shutil
+            shutil.copy('test_graph.svg', os.path.join(_p, 'test_graph.svg'))
+            assert 10550 <= os.path.getsize('test_graph.svg') <= 10650
+
+    def test_graph(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            with pytest.warns(RuntimeWarning):
+                result = runner.invoke(
+                    treevalue_cli,
+                    args=['graph', '-g', 'test.entry.cli.test_graph.g',
+                          '-t', 'first title', '-o', 'test_graph.svg'],
+                )
+
+            assert result.exit_code == 0
+            assert os.path.exists('test_graph.svg')
+            assert 15670 <= os.path.getsize('test_graph.svg') <= 15770
+
+            content = pathlib.Path('test_graph.svg').read_text()
+            assert 'first title' not in content
+            assert 'This is title for g.' in content
+
+    def test_cfg(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.*',
+                      '-c', 'bgcolor=#ffffff00', '-O'],
+            )
+
+            assert result.exit_code == 0
+            assert 5320 <= len(result.output) <= 5420
+            assert '#ffffff00' in result.output
+
+        with runner.isolated_filesystem():
+            result = runner.invoke(
+                treevalue_cli,
+                args=['graph', '-t', 'test.entry.cli.test_graph.*',
+                      '-c', 'bgcolor#ffffff00', '-O'],
+            )
+
+            assert result.exit_code != 0
+            assert "Configuration should be KEY=VALUE, but 'bgcolor#ffffff00' found." in result.output
+
+    def test_file_with_invalid_permission(self):
+        runner = CliRunner()
+        with runner.isolated_filesystem():
+            with open('g1.bg', 'wb') as file:
+                dump(t1, file, compress=zlib)
+
+            try:
+                os.chmod('g1.bg', int('000', base=8))
+            except PermissionError:
+                warnings.warn(RuntimeWarning('Permission denied when changing the permission, skip this test.'))
+            else:
+                result = runner.invoke(
+                    treevalue_cli,
+                    args=['graph', '-t', 'g1.bg', '-o', 'test_graph.svg'],
+                )
+
+                assert result.exit_code == 0
+                assert os.path.exists('test_graph.svg')
+                assert 700 <= os.path.getsize('test_graph.svg') <= 800
diff --git a/test/entry/cli/test_version.py b/test/entry/cli/test_version.py
new file mode 100644
index 0000000000..8a5b3771b3
--- /dev/null
+++ b/test/entry/cli/test_version.py
@@ -0,0 +1,16 @@
+import pytest
+from click.testing import CliRunner
+
+from treevalue.config.meta import __TITLE__, __VERSION__
+from treevalue.entry.cli import treevalue_cli
+
+
+@pytest.mark.unittest
+class TestEntryCliVersion:
+    def test_version(self):
+        runner = CliRunner()
+        result = runner.invoke(treevalue_cli, args=['-v'])
+
+        assert result.exit_code == 0
+        assert __TITLE__.lower() in result.stdout.lower()
+        assert __VERSION__.lower() in result.stdout.lower()
diff --git a/test/entry/script/__init__.py b/test/entry/script/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/tests/__init__.py b/test/tests/__init__.py
new file mode 100644
index 0000000000..32de88dd96
--- /dev/null
+++ b/test/tests/__init__.py
@@ -0,0 +1,1 @@
+from .utils import float_eq
diff --git a/test/tests/utils.py b/test/tests/utils.py
new file mode 100644
index 0000000000..1c80dfd94e
--- /dev/null
+++ b/test/tests/utils.py
@@ -0,0 +1,29 @@
+from functools import wraps
+from typing import Callable
+
+
+def eq_extend(func: Callable[..., bool]):
+    @wraps(func)
+    def _new_func(a, b, *args, **kwargs):
+        if isinstance(a, dict) and isinstance(b, dict):
+            aks, bks = set(a.keys()), set(b.keys())
+            if aks != bks:
+                return False
+            else:
+                return all([_new_func(a[key], b[key], *args, **kwargs) for key in aks])
+        elif (isinstance(a, tuple) and isinstance(b, tuple)) \
+                or (isinstance(a, list) and isinstance(b, list)):
+            length_a, length_b = len(a), len(b)
+            if length_a != length_b:
+                return False
+            else:
+                return all([_new_func(ai, bi, *args, **kwargs) for ai, bi in zip(a, b)])
+        else:
+            return func(a, b, *args, **kwargs)
+
+    return _new_func
+
+
+@eq_extend
+def float_eq(a, b, eps=1e-5):
+    return abs(a - b) < abs(eps)
diff --git a/test/tree/common/test_tree.py b/test/tree/common/test_tree.py
index 518a2d82d8..754cd9b447 100644
--- a/test/tree/common/test_tree.py
+++ b/test/tree/common/test_tree.py
@@ -37,6 +37,20 @@ def test_tree_init_and_eq(self):
         assert h[t1] == 2
         assert h[Tree(t1.json())] == 2
 
+    def test_copy_from(self):
+        t1 = Tree({'a': 1, 'x': {'b': 1, 'c': 2}})
+        t2 = Tree({'a': 11, 'b': 24, 'x': {'b': 12, 'e': {'dfkj': 892374}}})
+        original_id = id(t1.actual())
+        original_id_x = id(t1['x'].actual())
+
+        t1.copy_from(t2)
+        assert t1 == t2
+        assert t1 is not t2
+        assert id(t1.actual()) == original_id
+        assert id(t1['x'].actual()) == original_id_x
+        assert t1['x']['e'] == t2['x']['e']
+        assert t1['x']['e'] is not t2['x']['e']
+
     def test_tree_items(self):
         t = Tree({'a': 1, 'x': {'b': 1, 'c': 2}})
         assert t['a'] == 1
@@ -123,6 +137,20 @@ def test_raw(self):
         assert t['x']['c'] == {'a': 5, 'b': 6}
         assert t['x']['d'] == {'a': 7, 'b': 8}
 
+    def test_invalid_key(self):
+        with pytest.raises(KeyError):
+            _ = Tree({'a': 123, '\uffff': 321})
+
+        t = Tree({'a': 1, 'x': {'b': 1, 'c': 2}})
+        with pytest.raises(KeyError):
+            t['\uffff'] = 233
+        with pytest.raises(KeyError):
+            t['a' * 0x1ffff] = 233
+        with pytest.raises(KeyError):
+            t[''] = 233
+        with pytest.raises(KeyError):
+            t['0'] = 233
+
     def test_deep_clone(self):
         t = Tree({
             'a': raw({'a': 1, 'b': 2}),
diff --git a/test/tree/common/test_view.py b/test/tree/common/test_view.py
index 93c3af77e2..8a2ecde78e 100644
--- a/test/tree/common/test_view.py
+++ b/test/tree/common/test_view.py
@@ -52,6 +52,19 @@ def test_simple_view(self):
             3, 2, 1, Tree({'t': -1, 'q': -2})
         }
 
+    def test_copy_from(self):
+        t = Tree({'a': 1, 'x': {'b': 233, 'c': 'sdklfgjl', 'f': {'t': 2, 'p': 3}}})
+        tv1 = t.view(['x'])
+        t2 = Tree({'bf': 233, 'c': 'sdklfgjl', 'f': {'t': 2, 'p': 3, 'tp': 3}})
+
+        original_id_x = id(t['x'].actual())
+        original_id_x_f = id(t['x']['f'].actual())
+
+        tv1.copy_from(t2)
+        assert tv1 == t2
+        assert id(tv1.actual()) == original_id_x
+        assert id(tv1['f'].actual()) == original_id_x_f
+
     def test_deep_clone(self):
         tx = Tree({
             'a': {
diff --git a/test/tree/general/base.py b/test/tree/general/base.py
index ed72a55725..fc15d43bc1 100644
--- a/test/tree/general/base.py
+++ b/test/tree/general/base.py
@@ -6,9 +6,10 @@
 import pytest
 
 from treevalue.tree import func_treelize, TreeValue, raw
+from treevalue.tree.tree.tree import get_data_property
 
 
-def get_tree_test(tree_number_class: Type[TreeValue]):
+def get_tree_test(tree_value_clazz: Type[TreeValue]):
     class Container:
         def __init__(self, value):
             self.__value = value
@@ -38,34 +39,67 @@ def __repr__(self):
     @pytest.mark.unittest
     class _TestClass:
         def test_basic_methods(self):
-            t3 = tree_number_class({'a': 14, 'b': 26, 'x': {'c': 38, 'd': 11}})
+            t3 = tree_value_clazz({'a': 14, 'b': 26, 'x': {'c': 38, 'd': 11}})
             assert t3.json() == {'a': 14, 'b': 26, 'x': {'c': 38, 'd': 11}}
 
             t4 = t3.view(['x'])
             t5 = t3.x.clone()
-            assert t4 == tree_number_class({'c': 38, 'd': 11})
-            assert t5 == tree_number_class({'c': 38, 'd': 11})
+            assert t4 == tree_value_clazz({'c': 38, 'd': 11})
+            assert t5 == tree_value_clazz({'c': 38, 'd': 11})
             t3.x.c = 100
-            assert t4 == tree_number_class({'c': 100, 'd': 11})
-            assert t5 == tree_number_class({'c': 38, 'd': 11})
+            assert t4 == tree_value_clazz({'c': 100, 'd': 11})
+            assert t5 == tree_value_clazz({'c': 38, 'd': 11})
 
         def test_numeric_add(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (2 + t1 + t2) == tree_number_class({'a': 14, 'b': 26, 'x': {'c': 38, 'd': 11}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (2 + t1 + t2) == tree_value_clazz({'a': 14, 'b': 26, 'x': {'c': 38, 'd': 11}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t3 = t1 + t2
+            t1 += t2
+            assert t1 == t3
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_sub(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (2 - t1 - t2) == tree_number_class({'a': -10, 'b': -22, 'x': {'c': -34, 'd': -7}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (2 - t1 - t2) == tree_value_clazz({'a': -10, 'b': -22, 'x': {'c': -34, 'd': -7}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t3 = t1 - t2
+            t1 -= t2
+            assert t1 == t3
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_mul(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (2 * t1 * t2) == tree_number_class({'a': 22, 'b': 88, 'x': {'c': 198, 'd': 40}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (2 * t1 * t2) == tree_value_clazz({'a': 22, 'b': 88, 'x': {'c': 198, 'd': 40}})
+
+            i, j = 1, 1.0
+            assert i.__mul__(j) == NotImplemented
+            assert j.__mul__(i) == 1.0
+            assert i * j == j.__mul__(i)
+
+            t3 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5.2}})
+            assert (t1 * t3) == tree_value_clazz({'a': 11, 'b': 44, 'x': {'c': 99, 'd': 20.8}})
+            assert (t3 * t1) == tree_value_clazz({'a': 11, 'b': 44, 'x': {'c': 99, 'd': 20.8}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 * t2
+            t1 *= t2
+            assert t1 == t4
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_matmul(self):
-            t1 = tree_number_class({
+            t1 = tree_value_clazz({
                 'a': np.array([[1, 2], [3, 4]]),
                 'b': np.array([[2, 3], [4, 5]]),
                 'x': {
@@ -73,7 +107,7 @@ def test_numeric_matmul(self):
                     'd': np.array([[4, 5], [6, 7]]),
                 }
             })
-            t2 = tree_number_class({
+            t2 = tree_value_clazz({
                 'a': np.array([[4, 5], [6, 7]]),
                 'b': np.array([[3, 4], [5, 6]]),
                 'x': {
@@ -82,15 +116,15 @@ def test_numeric_matmul(self):
                 }
             })
 
-            npeq = func_treelize(return_type=tree_number_class)(np.array_equal)
-            assert npeq((t1 @ t2), tree_number_class({
+            npeq = func_treelize(return_type=tree_value_clazz)(np.array_equal)
+            assert npeq((t1 @ t2), tree_value_clazz({
                 'a': np.array([[1, 2], [3, 4]]) @ np.array([[4, 5], [6, 7]]),
                 'b': np.array([[2, 3], [4, 5]]) @ np.array([[3, 4], [5, 6]]),
                 'x': {
                     'c': np.array([[3, 4], [5, 6]]) @ np.array([[2, 3], [4, 5]]),
                     'd': np.array([[4, 5], [6, 7]]) @ np.array([[1, 2], [3, 4]]),
                 }
-            })) == tree_number_class({
+            })) == tree_value_clazz({
                 'a': True,
                 'b': True,
                 'x': {
@@ -99,7 +133,7 @@ def test_numeric_matmul(self):
                 }
             })
             assert npeq(
-                (t2.__rmatmul__(np.array([[1, 2], [3, 4]]))), tree_number_class({
+                (t2.__rmatmul__(np.array([[1, 2], [3, 4]]))), tree_value_clazz({
                     'a': np.array([[1, 2], [3, 4]]) @ np.array([[4, 5], [6, 7]]),
                     'b': np.array([[1, 2], [3, 4]]) @ np.array([[3, 4], [5, 6]]),
                     'x': {
@@ -107,7 +141,7 @@ def test_numeric_matmul(self):
                         'd': np.array([[1, 2], [3, 4]]) @ np.array([[1, 2], [3, 4]]),
                     }
                 })
-            ) == tree_number_class({
+            ) == tree_value_clazz({
                 'a': True,
                 'b': True,
                 'x': {
@@ -116,150 +150,230 @@ def test_numeric_matmul(self):
                 }
             })
 
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 @ t2
+            t1 @= t2
+            assert npeq(t1, t4)
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
+
         def test_numeric_floordiv(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t2 / t1) == tree_number_class({'a': 11.0, 'b': 11.0, 'x': {'c': 11.0, 'd': 1.25}})
-            assert (6 / t1) == tree_number_class({'a': 6.0, 'b': 3.0, 'x': {'c': 2.0, 'd': 1.5}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t2 / t1) == tree_value_clazz({'a': 11.0, 'b': 11.0, 'x': {'c': 11.0, 'd': 1.25}})
+            assert (6 / t1) == tree_value_clazz({'a': 6.0, 'b': 3.0, 'x': {'c': 2.0, 'd': 1.5}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 / t2
+            t1 /= t2
+            assert t1 == t4
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_truediv(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t2 // t1) == tree_number_class({'a': 11, 'b': 11, 'x': {'c': 11, 'd': 1}})
-            assert (6 // t1) == tree_number_class({'a': 6, 'b': 3, 'x': {'c': 2, 'd': 1}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t2 // t1) == tree_value_clazz({'a': 11, 'b': 11, 'x': {'c': 11, 'd': 1}})
+            assert (6 // t1) == tree_value_clazz({'a': 6, 'b': 3, 'x': {'c': 2, 'd': 1}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 // t2
+            t1 //= t2
+            assert t1 == t4
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_mod(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t2 % t1) == tree_number_class({'a': 0, 'b': 0, 'x': {'c': 0, 'd': 1}})
-            assert (6 % t1) == tree_number_class({'a': 0, 'b': 0, 'x': {'c': 0, 'd': 2}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t2 % t1) == tree_value_clazz({'a': 0, 'b': 0, 'x': {'c': 0, 'd': 1}})
+            assert (6 % t1) == tree_value_clazz({'a': 0, 'b': 0, 'x': {'c': 0, 'd': 2}})
+
+            original_id = id(get_data_property(t2).actual())
+            original_id_x = id(get_data_property(t2.x).actual())
+            t4 = t2 % t1
+            t2 %= t1
+            assert t2 == t4
+            assert id(get_data_property(t2).actual()) == original_id
+            assert id(get_data_property(t2.x).actual()) == original_id_x
 
         def test_numeric_power(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            assert (t1 ** t1) == tree_number_class({'a': 1, 'b': 4, 'x': {'c': 27, 'd': 256}})
-            assert (2 ** t1) == tree_number_class({'a': 2, 'b': 4, 'x': {'c': 8, 'd': 16}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            assert (t1 ** t1) == tree_value_clazz({'a': 1, 'b': 4, 'x': {'c': 27, 'd': 256}})
+            assert (2 ** t1) == tree_value_clazz({'a': 2, 'b': 4, 'x': {'c': 8, 'd': 16}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 ** t1
+            t1 **= t1
+            assert t1 == t4
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_and(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t1 & t2) == tree_number_class({'a': 1, 'b': 2, 'x': {'c': 1, 'd': 4}})
-            assert (7 & t2) == tree_number_class({'a': 3, 'b': 6, 'x': {'c': 1, 'd': 5}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t1 & t2) == tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 1, 'd': 4}})
+            assert (7 & t2) == tree_value_clazz({'a': 3, 'b': 6, 'x': {'c': 1, 'd': 5}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 & t2
+            t1 &= t2
+            assert t1 == t4
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_or(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t1 | t2) == tree_number_class({'a': 11, 'b': 22, 'x': {'c': 35, 'd': 5}})
-            assert (7 | t2) == tree_number_class({'a': 15, 'b': 23, 'x': {'c': 39, 'd': 7}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t1 | t2) == tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 35, 'd': 5}})
+            assert (7 | t2) == tree_value_clazz({'a': 15, 'b': 23, 'x': {'c': 39, 'd': 7}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 | t2
+            t1 |= t2
+            assert t1 == t4
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_xor(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t1 ^ t2) == tree_number_class({'a': 10, 'b': 20, 'x': {'c': 34, 'd': 1}})
-            assert (7 ^ t2) == tree_number_class({'a': 12, 'b': 17, 'x': {'c': 38, 'd': 2}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t1 ^ t2) == tree_value_clazz({'a': 10, 'b': 20, 'x': {'c': 34, 'd': 1}})
+            assert (7 ^ t2) == tree_value_clazz({'a': 12, 'b': 17, 'x': {'c': 38, 'd': 2}})
+
+            original_id = id(get_data_property(t1).actual())
+            original_id_x = id(get_data_property(t1.x).actual())
+            t4 = t1 ^ t2
+            t1 ^= t2
+            assert t1 == t4
+            assert id(get_data_property(t1).actual()) == original_id
+            assert id(get_data_property(t1.x).actual()) == original_id_x
 
         def test_numeric_lshift(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t2 << t1) == tree_number_class({'a': 22, 'b': 88, 'x': {'c': 264, 'd': 80}})
-            assert (3 << t1) == tree_number_class({'a': 6, 'b': 12, 'x': {'c': 24, 'd': 48}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t2 << t1) == tree_value_clazz({'a': 22, 'b': 88, 'x': {'c': 264, 'd': 80}})
+            assert (3 << t1) == tree_value_clazz({'a': 6, 'b': 12, 'x': {'c': 24, 'd': 48}})
+
+            original_id = id(get_data_property(t2).actual())
+            original_id_x = id(get_data_property(t2.x).actual())
+            t4 = t2 << t1
+            t2 <<= t1
+            assert t2 == t4
+            assert id(get_data_property(t2).actual()) == original_id
+            assert id(get_data_property(t2.x).actual()) == original_id_x
 
         def test_numeric_rshift(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
-            assert (t2 >> t1) == tree_number_class({'a': 5, 'b': 5, 'x': {'c': 4, 'd': 0}})
-            assert (32 >> t1) == tree_number_class({'a': 16, 'b': 8, 'x': {'c': 4, 'd': 2}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 5}})
+            assert (t2 >> t1) == tree_value_clazz({'a': 5, 'b': 5, 'x': {'c': 4, 'd': 0}})
+            assert (32 >> t1) == tree_value_clazz({'a': 16, 'b': 8, 'x': {'c': 4, 'd': 2}})
+
+            original_id = id(get_data_property(t2).actual())
+            original_id_x = id(get_data_property(t2.x).actual())
+            t4 = t2 >> t1
+            t2 >>= t1
+            assert t2 == t4
+            assert id(get_data_property(t2).actual()) == original_id
+            assert id(get_data_property(t2.x).actual()) == original_id_x
 
         def test_numeric_pos(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            assert +t1 == tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            assert +t1 == tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
 
         def test_numeric_neg(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            assert -t1 == tree_number_class({'a': -1, 'b': -2, 'x': {'c': -3, 'd': -4}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            assert -t1 == tree_value_clazz({'a': -1, 'b': -2, 'x': {'c': -3, 'd': -4}})
 
         def test_numeric_invert(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            assert ~t1 == tree_number_class({'a': -2, 'b': -3, 'x': {'c': -4, 'd': -5}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            assert ~t1 == tree_value_clazz({'a': -2, 'b': -3, 'x': {'c': -4, 'd': -5}})
 
         def test_getitem(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': [2, 3, 5, 7], 'b': [11, 13, 17, 19],
-                                    'x': {'c': [23, 29, 31, 37], 'd': [41, 43, 47, 53]}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': [2, 3, 5, 7], 'b': [11, 13, 17, 19],
+                                   'x': {'c': [23, 29, 31, 37], 'd': [41, 43, 47, 53]}})
 
-            assert t2[t1 - 1] == tree_number_class({'a': 2, 'b': 13, 'x': {'c': 31, 'd': 53}})
+            assert t2[t1 - 1] == tree_value_clazz({'a': 2, 'b': 13, 'x': {'c': 31, 'd': 53}})
 
         def test_setitem(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': [2, 3, 5, 7], 'b': [11, 13, 17, 19],
-                                    'x': {'c': [23, 29, 31, 37], 'd': [41, 43, 47, 53]}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': [2, 3, 5, 7], 'b': [11, 13, 17, 19],
+                                   'x': {'c': [23, 29, 31, 37], 'd': [41, 43, 47, 53]}})
 
             t2[t1 - 1] = t1
-            assert t2 == tree_number_class({'a': [1, 3, 5, 7], 'b': [11, 2, 17, 19],
-                                            'x': {'c': [23, 29, 3, 37], 'd': [41, 43, 47, 4]}})
+            assert t2 == tree_value_clazz({'a': [1, 3, 5, 7], 'b': [11, 2, 17, 19],
+                                           'x': {'c': [23, 29, 3, 37], 'd': [41, 43, 47, 4]}})
 
         def test_delitem(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = tree_number_class({'a': [2, 3, 5, 7], 'b': [11, 13, 17, 19],
-                                    'x': {'c': [23, 29, 31, 37], 'd': [41, 43, 47, 53]}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': [2, 3, 5, 7], 'b': [11, 13, 17, 19],
+                                   'x': {'c': [23, 29, 31, 37], 'd': [41, 43, 47, 53]}})
 
             del t2[t1 - 1]
-            assert t2 == tree_number_class({'a': [3, 5, 7], 'b': [11, 17, 19],
-                                            'x': {'c': [23, 29, 37], 'd': [41, 43, 47]}})
+            assert t2 == tree_value_clazz({'a': [3, 5, 7], 'b': [11, 17, 19],
+                                           'x': {'c': [23, 29, 37], 'd': [41, 43, 47]}})
 
         def test_attr(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = func_treelize(return_type=tree_number_class)(Container)(t1)
-            assert t2 == tree_number_class(
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = func_treelize(return_type=tree_value_clazz)(Container)(t1)
+            assert t2 == tree_value_clazz(
                 {'a': Container(1), 'b': Container(2), 'x': {'c': Container(3), 'd': Container(4)}})
             assert t2.value == t1
 
         def test_call(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t2 = func_treelize(return_type=tree_number_class)(Container)(t1)
-            assert t2.add(10) == tree_number_class({'a': 11, 'b': 12, 'x': {'c': 13, 'd': 14}})
-            assert t2.add(x=10) == tree_number_class({'a': 11, 'b': 12, 'x': {'c': 13, 'd': 14}})
-            assert t2.add(t1) == tree_number_class({'a': 2, 'b': 4, 'x': {'c': 6, 'd': 8}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = func_treelize(return_type=tree_value_clazz)(Container)(t1)
+            assert t2.add(10) == tree_value_clazz({'a': 11, 'b': 12, 'x': {'c': 13, 'd': 14}})
+            assert t2.add(x=10) == tree_value_clazz({'a': 11, 'b': 12, 'x': {'c': 13, 'd': 14}})
+            assert t2.add(t1) == tree_value_clazz({'a': 2, 'b': 4, 'x': {'c': 6, 'd': 8}})
 
         def test_map(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            assert t1.map(lambda x: x + 2) == tree_number_class({'a': 3, 'b': 4, 'x': {'c': 5, 'd': 6}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            assert t1.map(lambda x: x + 2) == tree_value_clazz({'a': 3, 'b': 4, 'x': {'c': 5, 'd': 6}})
 
         def test_type(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
             assert t1.type(TreeValue) == TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            assert t1.type(TreeValue) != tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            assert t1.type(TreeValue) != tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
 
         def test_filter(self):
-            t1 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            assert t1.filter(lambda x: x % 2 == 1) == tree_number_class({'a': 1, 'x': {'c': 3}})
-            assert t1.filter(lambda x: x < 3) == tree_number_class({'a': 1, 'b': 2, })
-            assert t1.filter(lambda x: x < 3, False) == tree_number_class({'a': 1, 'b': 2, 'x': {}})
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            assert t1.filter(lambda x: x % 2 == 1) == tree_value_clazz({'a': 1, 'x': {'c': 3}})
+            assert t1.filter(lambda x: x < 3) == tree_value_clazz({'a': 1, 'b': 2, })
+            assert t1.filter(lambda x: x < 3, False) == tree_value_clazz({'a': 1, 'b': 2, 'x': {}})
 
         def test_mask(self):
-            t1 = tree_number_class({'a': 13, 'b': 27, 'x': {'c': 39, 'd': 45}})
-            t2 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t3 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 7, 'd': 4}})
+            t1 = tree_value_clazz({'a': 13, 'b': 27, 'x': {'c': 39, 'd': 45}})
+            t2 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t3 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 7, 'd': 4}})
 
             mask1 = t2.map(lambda x: (lambda v: v % x == 0))(t1)
-            assert t1.mask(mask1) == tree_number_class({'a': 13, 'x': {'c': 39}})
+            assert t1.mask(mask1) == tree_value_clazz({'a': 13, 'x': {'c': 39}})
 
             mask2 = t3.map(lambda x: (lambda v: v % x == 0))(t1)
-            assert t1.mask(mask2) == tree_number_class({'a': 13})
-            assert t1.mask(mask2, False) == tree_number_class({'a': 13, 'x': {}})
+            assert t1.mask(mask2) == tree_value_clazz({'a': 13})
+            assert t1.mask(mask2, False) == tree_value_clazz({'a': 13, 'x': {}})
 
         def test_reduce(self):
-            t1 = tree_number_class({'a': 13, 'b': 27, 'x': {'c': 39, 'd': 45}})
-            t2 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t1 = tree_value_clazz({'a': 13, 'b': 27, 'x': {'c': 39, 'd': 45}})
+            t2 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
 
             assert t1.reduce(lambda **kwargs: sum(kwargs.values())) == 124
             assert t2.reduce(lambda **kwargs: reduce(__mul__, kwargs.values())) == 24
 
         def test_union(self):
-            t1 = tree_number_class({'a': 13, 'b': 27, 'x': {'c': 39, 'd': 45}})
-            t2 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
-            t3 = tree_number_class({'a': 1, 'b': 2, 'x': {'c': 7, 'd': 4}})
+            t1 = tree_value_clazz({'a': 13, 'b': 27, 'x': {'c': 39, 'd': 45}})
+            t2 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t3 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 7, 'd': 4}})
 
-            assert tree_number_class.union(t1, t2, t3) == tree_number_class({
+            assert tree_value_clazz.union(t1, t2, t3) == tree_value_clazz({
                 'a': (13, 1, 1),
                 'b': (27, 2, 2),
                 'x': {
@@ -268,7 +382,7 @@ def test_union(self):
                 }
             })
 
-            assert tree_number_class.union(t1, t2, t3, return_type=TreeValue) == TreeValue({
+            assert tree_value_clazz.union(t1, t2, t3, return_type=TreeValue) == TreeValue({
                 'a': (13, 1, 1),
                 'b': (27, 2, 2),
                 'x': {
@@ -290,11 +404,11 @@ def test_subside(self):
                 'k': '233'
             }
 
-            assert tree_number_class.subside(data) == tree_number_class({
+            assert tree_value_clazz.subside(data) == tree_value_clazz({
                 'a': raw({'a': 1, 'k': '233', 'x': {'c': 3, 'd': [5, 7]}}),
                 'b': raw({'a': 2, 'k': '233', 'x': {'c': 4, 'd': [6, 8]}}),
             })
-            assert tree_number_class.subside(data, return_type=TreeValue) == TreeValue({
+            assert tree_value_clazz.subside(data, return_type=TreeValue) == TreeValue({
                 'a': raw({'a': 1, 'k': '233', 'x': {'c': 3, 'd': [5, 7]}}),
                 'b': raw({'a': 2, 'k': '233', 'x': {'c': 4, 'd': [6, 8]}}),
             })
@@ -312,21 +426,21 @@ def test_rise(self):
                 'k': '233'
             }
 
-            t1 = tree_number_class.subside(data)
+            t1 = tree_value_clazz.subside(data)
             assert t1.rise() == {
-                'a': tree_number_class({'a': 1, 'b': 2}),
+                'a': tree_value_clazz({'a': 1, 'b': 2}),
                 'x': {
-                    'c': tree_number_class({'a': 3, 'b': 4}),
+                    'c': tree_value_clazz({'a': 3, 'b': 4}),
                     'd': [
-                        tree_number_class({'a': 5, 'b': 6}),
-                        tree_number_class({'a': 7, 'b': 8}),
+                        tree_value_clazz({'a': 5, 'b': 6}),
+                        tree_value_clazz({'a': 7, 'b': 8}),
                     ]
                 },
-                'k': tree_number_class({'a': '233', 'b': '233'}),
+                'k': tree_value_clazz({'a': '233', 'b': '233'}),
             }
 
         def test_deep_clone(self):
-            t = tree_number_class({
+            t = tree_value_clazz({
                 'a': raw({'a': 1, 'b': 2}),
                 'b': raw({'a': 3, 'b': 4}),
                 'x': {
@@ -349,4 +463,92 @@ def test_deep_clone(self):
             assert t2.x.c is not t.x.c
             assert t2.x.d is not t.x.d
 
+        def test_graph(self):
+            t = tree_value_clazz({
+                'a': [4, 3, 2, 1],
+                'b': np.array([[5, 6], [7, 8]]),
+                'x': {
+                    'c': np.array([[5, 7], [8, 6]]),
+                    'd': {'a', 'b', 'c'},
+                    'e': np.array([[1, 2], [3, 4]])
+                },
+            })
+            graph = t.graph('t')
+            assert 2210 <= len(graph.source) <= 2290
+
+        def test_graphics(self):
+            t = tree_value_clazz({
+                'a': [4, 3, 2, 1],
+                'b': np.array([[5, 6], [7, 8]]),
+                'x': {
+                    'c': np.array([[5, 7], [8, 6]]),
+                    'd': {'a', 'b', 'c'},
+                    'e': np.array([[1, 2], [3, 4]])
+                },
+            })
+            t1 = tree_value_clazz({
+                'aa': t.a,
+                'bb': np.array([[5, 6], [7, 8]]),
+                'xx': {
+                    'cc': t.x.c,
+                    'dd': t.x.d,
+                    'ee': np.array([[1, 2], [3, 4]])
+                },
+            })
+
+            graph_1 = tree_value_clazz.graphics(
+                (t, 't'), (t1, 't1'),
+                (tree_value_clazz({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+                dup_value=(np.ndarray, list),
+                title="This is a demo of 2 trees with dup value.",
+                cfg={'bgcolor': '#ffffffff'},
+            )
+            assert 4910 <= len(graph_1.source) <= 4960
+
+            graph_2 = tree_value_clazz.graphics(
+                (t, 't'), (t1, 't1'),
+                (tree_value_clazz({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+                dup_value=False,
+                title="This is a demo of 2 trees with dup value.",
+                cfg={'bgcolor': '#ffffffff'},
+            )
+            assert 5420 <= len(graph_2.source) <= 5480
+
+            graph_3 = tree_value_clazz.graphics(
+                (t, 't'), (t1, 't1'),
+                (tree_value_clazz({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+                dup_value=lambda x: id(x),
+                title="This is a demo of 2 trees with dup value.",
+                cfg={'bgcolor': '#ffffffff'},
+            )
+            assert 4700 <= len(graph_3.source) <= 4760
+
+            graph_4 = tree_value_clazz.graphics(
+                (t, 't'), (t1, 't1'),
+                (tree_value_clazz({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+                dup_value=lambda x: type(x).__name__,
+                title="This is a demo of 2 trees with dup value.",
+                cfg={'bgcolor': '#ffffffff'},
+            )
+            assert 3720 <= len(graph_4.source) <= 3780
+
+            graph_6 = tree_value_clazz.graphics(
+                (t, 't'), (t1, 't1'),
+                (tree_value_clazz({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+                dup_value=True,
+                title="This is a demo of 2 trees with dup value.",
+                cfg={'bgcolor': '#ffffffff'},
+            )
+            assert 4700 <= len(graph_6.source) <= 4760
+
+        def test_func(self):
+            t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
+            t2 = tree_value_clazz({'a': 11, 'b': 20, 'x': {'c': 33, 'd': 48}})
+
+            @tree_value_clazz.func()
+            def ssum(x, y):
+                return x + y
+
+            assert ssum(t1, t2) == tree_value_clazz({'a': 12, 'b': 22, 'x': {'c': 36, 'd': 52}})
+
     return _TestClass
diff --git a/test/tree/general/test_general.py b/test/tree/general/test_general.py
index e67aa0c5fa..1bf2954fc4 100644
--- a/test/tree/general/test_general.py
+++ b/test/tree/general/test_general.py
@@ -17,6 +17,22 @@ class NonDefaultTreeNumber(general_tree_value(base=dict(), methods=dict(
     pass
 
 
+class BanAndOverrideTreeNumber(general_tree_value(methods=dict(
+    __add__=NotImplemented,
+    __radd__=NotImplemented,
+    __iadd__=NotImplemented,
+    __mul__=KeyError("lksdjfkl"),
+    __rmul__=KeyError("lsdfjkldks"),
+    __imul__=KeyError("dklfgjsl"),
+    __matmul__=KeyError,
+    __rmatmul__=KeyError,
+    __imatmul__=KeyError,
+    __pos__=(lambda sp: sp // 2),
+    __truediv__=(lambda sp: sp - 2),
+))):
+    pass
+
+
 @pytest.mark.unittest
 class TestTreeGeneralGeneral(get_tree_test(TreeNumber)):
     def test_numeric_append(self):
@@ -43,3 +59,16 @@ def test_non_default_tree_number(self):
         assert (t1 + t2) == NonDefaultTreeNumber({'a': 12, 'b': 24, 'c': 4, 'x': {'c': 36, 'd': 9, 'e': 7}})
         with pytest.raises(KeyError):
             _ = t1 - t2
+
+    def test_ban_add(self):
+        t1 = BanAndOverrideTreeNumber({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4, 'e': 7}})
+        t2 = BanAndOverrideTreeNumber({'a': 11, 'b': 22, 'c': 4, 'x': {'c': 33, 'd': 5}})
+
+        with pytest.raises(TypeError):
+            _ = t1 + t2
+        with pytest.raises(KeyError):
+            _ = t1 * t2
+        with pytest.raises(KeyError):
+            _ = t1 @ t2
+        assert +t1 == BanAndOverrideTreeNumber({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4, 'e': 7}}) // 2
+        assert t1 / 3 == BanAndOverrideTreeNumber({'a': 1 - 2, 'b': 2 - 2, 'x': {'c': 3 - 2, 'd': 4 - 2, 'e': 7 - 2}})
diff --git a/test/tree/general/test_tensor.py b/test/tree/general/test_tensor.py
index 52d4a17d1c..26a462d5b4 100644
--- a/test/tree/general/test_tensor.py
+++ b/test/tree/general/test_tensor.py
@@ -6,51 +6,23 @@
 
 @pytest.mark.unittest
 def test_for_torch_support():
-    @func_treelize(inherit=True)
-    def add(a, b):
-        return a + b
+    sin = func_treelize()(torch.sin)
+    cos = func_treelize()(torch.cos)  # the same sin function
 
-    @func_treelize()
-    def sin(a):
-        return torch.sin(a)
+    cat = func_treelize(subside=True)(torch.cat)
+    stack = func_treelize(subside=True)(torch.stack)
+    split = func_treelize(rise=True)(torch.split)
 
-    cos = func_treelize()(torch.cos)
-
-    @func_treelize(inherit=True)
-    def cat(a, b, dim):
-        return torch.cat([a, b], dim=dim)
-
-    @func_treelize(inherit=True)
-    def stack(a, b, dim):
-        return torch.stack([a, b], dim=dim)
-
-    @func_treelize(inherit=True)
-    def stack_all(items, dim):
-        return torch.stack(items, dim=dim)
-
-    @func_treelize(inherit=True)
-    def split(a, split_size_or_sections, dim):
-        return torch.split(a, split_size_or_sections, dim)
-
-    @func_treelize(inherit=True)
-    def mean(a, *args, **kwargs):
-        return torch.mean(a, *args, **kwargs)
-
-    @func_treelize(inherit=True)
-    def clamp(a, *args, **kwargs):
-        return torch.clamp(a, *args, **kwargs)
-
-    @func_treelize(inherit=True)
-    def to(a, *args, **kwargs):
-        return a.to(*args, **kwargs)
+    mean = func_treelize()(torch.mean)
+    clamp = func_treelize()(torch.clamp)
 
     t1 = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(5)})
     t2 = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(5)})
 
-    t3 = add(t1, t2)
+    t3 = t1 + t2
     assert t3.a.shape == (4, 3)
     assert t3.b.shape == (5,)
-    t4 = add(1, t2)
+    t4 = 1 + t2
     assert t4.a.shape == (4, 3)
     assert t4.b.shape == (5,)
     assert t4.b.eq(t2.b + 1).all()
@@ -66,6 +38,7 @@ def to(a, *args, **kwargs):
     assert t6.a.shape == (3,)
     assert t6.b.shape == ()
 
+    # list not available now
     # t1a = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(6, 9), 'c': {'d': torch.randn(8, 2)}, 'e': [torch.randn(2, 3), torch.randn(2, 4)]})
     # t2a = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(6, 9), 'c': {'d': torch.randn(8, 2)}, 'e': [torch.randn(2, 3), torch.randn(2, 4)]})
     t1a = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(6, 9), 'c': {'d': torch.randn(8, 2)}})
@@ -80,29 +53,29 @@ def to(a, *args, **kwargs):
     t6 = t1a[slice(0, 3)]
     assert t6.a.shape == (3, 3)
     assert t6.a[1].eq(t1a.a[1]).all()
-    # t6 = t1a[..., 1]
-    # assert t6.a.shape == (4, )
-    # t6 = t1a[..., 1:2]
-    # assert t6.a.shape == (4, 1)
+    t6 = t1a[..., 1]
+    assert t6.a.shape == (4,)
+    t6 = t1a[..., 1:2]
+    assert t6.a.shape == (4, 1)
 
-    t7 = cat(t1a, t2a, dim=0)
+    t7 = cat([t1a, t2a], dim=0)
     assert t7.a.shape == (8, 3)
     assert t7.b.shape == (12, 9)
-    t7 = cat(t1a, t2a, dim=1)
+    t7 = cat([t1a, t2a], dim=1)
     assert t7.a.shape == (4, 6)
     assert t7.b.shape == (6, 18)
 
-    t7 = stack(t1a, t2a, dim=0)
+    t7 = stack([t1a, t2a], dim=0)
     assert t7.a.shape == (2, 4, 3)
     assert t7.b.shape == (2, 6, 9)
-    t7 = stack(t1a, t2a, dim=1)
+    t7 = stack([t1a, t2a], dim=1)
     assert t7.a.shape == (4, 2, 3)
     assert t7.b.shape == (6, 2, 9)
 
     t8 = split(t1a, 2, 0)
-    # assert isinstance(t8.a, tuple)
-    # assert t8.a[0].shape == (2, 3)
-    # assert isinstance(t8, tuple)
+    assert isinstance(t8.a, tuple)
+    assert t8.a[0].shape == (2, 3)
+    # assert isinstance(t8, tuple)  # cannot be risen due to the different lengths of tuples
     # assert t8[0].a.shape == (2, 3)
 
     t9 = mean(t1a)
@@ -114,11 +87,8 @@ def to(a, *args, **kwargs):
     assert t10.c.d.eq(t1a.c.d.clamp(0, 0.2)).all()
 
     assert t1a.a.dtype == torch.float32
-    t11 = to(t1a, torch.int64)
+    t11 = t1a.to(torch.int64)
     assert t11.a.dtype == torch.int64
 
-    def stack_spec(items, *args, **kwargs):
-        return stack_all(FastTreeValue.subside(items), *args, **kwargs)
-
-    t12 = stack_spec([t1a, t2a, t1a], dim=0)
+    t12 = stack([t1a, t2a, t1a], dim=0)
     assert t12.a.shape == (3, 4, 3)
diff --git a/test/tree/tree/__init__.py b/test/tree/tree/__init__.py
index ece4dcb372..7b26453898 100644
--- a/test/tree/tree/__init__.py
+++ b/test/tree/tree/__init__.py
@@ -1,1 +1,4 @@
+from .test_graph import TestTreeTreeGraph
+from .test_io import TestTreeTreeIo
 from .test_tree import TestTreeTreeTree
+from .test_utils import TestTreeTreeUtils
diff --git a/test/tree/tree/test_graph.py b/test/tree/tree/test_graph.py
new file mode 100644
index 0000000000..e561e5f7c4
--- /dev/null
+++ b/test/tree/tree/test_graph.py
@@ -0,0 +1,76 @@
+import numpy as np
+import pytest
+
+from treevalue import FastTreeValue, graphics
+
+
+class MyFastTreeValue(FastTreeValue):
+    pass
+
+
+@pytest.mark.unittest
+class TestTreeTreeGraph:
+    def test_graphics(self):
+        t = MyFastTreeValue({
+            'a': [4, 3, 2, 1],
+            'b': np.array([[5, 6], [7, 8]]),
+            'x': {
+                'c': np.array([[5, 7], [8, 6]]),
+                'd': {'a', 'b', 'c'},
+                'e': np.array([[1, 2], [3, 4]])
+            },
+        })
+        t1 = MyFastTreeValue({
+            'aa': t.a,
+            'bb': np.array([[5, 6], [7, 8]]),
+            'xx': {
+                'cc': t.x.c,
+                'dd': t.x.d,
+                'ee': np.array([[1, 2], [3, 4]])
+            },
+        })
+
+        graph_1 = graphics(
+            (t, 't'), (t1, 't1'),
+            (MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+            dup_value=(np.ndarray, list),
+            title="This is a demo of 2 trees with dup value.",
+            cfg={'bgcolor': '#ffffffff'},
+        )
+        assert 4910 <= len(graph_1.source) <= 4960
+
+        graph_2 = graphics(
+            (t, 't'), (t1, 't1'),
+            (MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+            dup_value=False,
+            title="This is a demo of 2 trees with dup value.",
+            cfg={'bgcolor': '#ffffffff'},
+        )
+        assert 5420 <= len(graph_2.source) <= 5480
+
+        graph_3 = graphics(
+            (t, 't'), (t1, 't1'),
+            (MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+            dup_value=lambda x: id(x),
+            title="This is a demo of 2 trees with dup value.",
+            cfg={'bgcolor': '#ffffffff'},
+        )
+        assert 4700 <= len(graph_3.source) <= 4760
+
+        graph_4 = graphics(
+            (t, 't'), (t1, 't1'),
+            (MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+            dup_value=lambda x: type(x).__name__,
+            title="This is a demo of 2 trees with dup value.",
+            cfg={'bgcolor': '#ffffffff'},
+        )
+        assert 3720 <= len(graph_4.source) <= 3780
+
+        graph_6 = graphics(
+            (t, 't'), (t1, 't1'),
+            (MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
+            dup_value=True,
+            title="This is a demo of 2 trees with dup value.",
+            cfg={'bgcolor': '#ffffffff'},
+        )
+        assert 4700 <= len(graph_6.source) <= 4760
diff --git a/test/tree/tree/test_io.py b/test/tree/tree/test_io.py
new file mode 100644
index 0000000000..3bb494a301
--- /dev/null
+++ b/test/tree/tree/test_io.py
@@ -0,0 +1,77 @@
+import zlib
+
+import pytest
+
+from treevalue.tree import loads, dumps, FastTreeValue
+
+
+class MyTreeValue(FastTreeValue):
+    pass
+
+
+# noinspection DuplicatedCode
+@pytest.mark.unittest
+class TestTreeTreeIo:
+    def test_dumps_and_loads(self):
+        t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
+
+        _dumped_data = dumps(t)
+        t1 = loads(_dumped_data, type_=FastTreeValue)
+        assert 2130 < len(_dumped_data) < 2150
+        assert isinstance(t1, FastTreeValue)
+        assert t1 == t
+
+        t2 = loads(_dumped_data, type_=MyTreeValue)
+        assert isinstance(t2, MyTreeValue)
+        assert t2 != t
+        assert FastTreeValue(t2) == t
+
+        with pytest.warns(UserWarning):
+            t1 = loads(_dumped_data, decompress=zlib.decompress, type_=FastTreeValue)
+        assert isinstance(t1, FastTreeValue)
+        assert t1 == t
+
+    def test_dumps_and_loads_with_zip(self):
+        t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
+
+        _dumped_data = dumps(t, compress=zlib)
+        t1 = loads(_dumped_data, type_=FastTreeValue)
+        assert 210 < len(dumps(t, compress=zlib)) < 220
+        assert isinstance(t1, FastTreeValue)
+        assert t1 == t
+
+        t2 = loads(_dumped_data, type_=MyTreeValue)
+        assert isinstance(t2, MyTreeValue)
+        assert t2 != t
+        assert FastTreeValue(t2) == t
+
+        with pytest.warns(UserWarning):
+            t1 = loads(_dumped_data, decompress=zlib.decompress, type_=FastTreeValue)
+        assert isinstance(t1, FastTreeValue)
+        assert t1 == t
+
+    def test_dumps_and_loads_with_zip_tuple(self):
+        t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
+
+        _dumped_data = dumps(t, compress=(zlib.compress, zlib.decompress))
+        t1 = loads(_dumped_data, type_=FastTreeValue)
+        assert 210 < len(_dumped_data) < 220
+        assert isinstance(t1, FastTreeValue)
+        assert t1 == t
+
+        t2 = loads(_dumped_data, type_=MyTreeValue)
+        assert isinstance(t2, MyTreeValue)
+        assert t2 != t
+        assert FastTreeValue(t2) == t
+
+    def test_dumps_and_loads_with_compress_only(self):
+        t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
+
+        _dumped_data = dumps(t, compress=zlib.compress)
+        t1 = loads(_dumped_data, decompress=zlib.decompress, type_=FastTreeValue)
+        assert 130 < len(_dumped_data) < 150
+        assert isinstance(t1, FastTreeValue)
+        assert t1 == t
+
+        with pytest.raises(RuntimeError):
+            loads(_dumped_data, type_=FastTreeValue)
diff --git a/test/tree/tree/test_tree.py b/test/tree/tree/test_tree.py
index 63628f2d48..e6d3b6528a 100644
--- a/test/tree/tree/test_tree.py
+++ b/test/tree/tree/test_tree.py
@@ -1,3 +1,4 @@
+import pickle
 import re
 
 import pytest
@@ -6,6 +7,30 @@
 from treevalue.tree.tree.tree import get_data_property
 
 
+class _Container:
+    def __init__(self, value):
+        self.__value = value
+
+    @property
+    def value(self):
+        return self.__value
+
+    def __repr__(self):
+        return '<{cls} {id} value: {value}>'.format(
+            cls=self.__class__.__name__, id=hex(id(self)), value=repr(self.__value))
+
+    def __eq__(self, other):
+        if other is self:
+            return True
+        elif type(other) == _Container:
+            return other.__value == self.value
+        else:
+            return False
+
+    def __hash__(self):
+        return hash((self.__value,))
+
+
 @pytest.mark.unittest
 class TestTreeTreeTree:
     def test_tree_value_init(self):
@@ -131,3 +156,19 @@ def test_tee_value_hash_equal(self):
         assert d[tv1] == 1
         assert d[TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}})] == 1
         assert d[TreeValue({'x': 2, 'y': 3})] == 2
+
+    def test_serialize_support(self):
+        tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}})
+        bt1 = pickle.dumps(tv1)
+        assert pickle.loads(bt1) == tv1
+
+        tv2 = TreeValue({
+            'a': _Container(1),
+            'b': _Container(2),
+            'x': {
+                'c': _Container(3),
+                'd': _Container(4),
+            }
+        })
+        bt2 = pickle.dumps(tv2)
+        assert pickle.loads(bt2) == tv2
diff --git a/test/tree/tree/test_utils.py b/test/tree/tree/test_utils.py
index a60a14f5ae..debccb8393 100644
--- a/test/tree/tree/test_utils.py
+++ b/test/tree/tree/test_utils.py
@@ -257,26 +257,61 @@ class MyTreeValue(TreeValue):
                 MyTreeValue({'x': 3, 'y': 8}),
             ]
         }
-        assert rise(t3, template={'a': [None, None], 'b': [None, None]}) == {
-            'a': [
+
+        t4 = MyTreeValue({'x': raw({'a': (1, 2), 'b': (2, 3)}), 'y': raw({'a': (5, 6), 'b': (7, 8)})})
+        assert rise(t4) == {
+            'a': (
                 MyTreeValue({'x': 1, 'y': 5}),
                 MyTreeValue({'x': 2, 'y': 6}),
-            ],
-            'b': [
+            ),
+            'b': (
                 MyTreeValue({'x': 2, 'y': 7}),
                 MyTreeValue({'x': 3, 'y': 8}),
-            ]
+            )
         }
-        assert rise(t3, template={'a': None, 'b': None}) == {
-            'a': MyTreeValue({'x': [1, 2], 'y': [5, 6]}),
-            'b': MyTreeValue({'x': [2, 3], 'y': [7, 8]}),
+        assert rise(t4, template={'a': (None, None), 'b': (None, None)}) == {
+            'a': (
+                MyTreeValue({'x': 1, 'y': 5}),
+                MyTreeValue({'x': 2, 'y': 6}),
+            ),
+            'b': (
+                MyTreeValue({'x': 2, 'y': 7}),
+                MyTreeValue({'x': 3, 'y': 8}),
+            )
+        }
+        assert rise(t4, template={'a': None, 'b': None}) == {
+            'a': MyTreeValue({'x': (1, 2), 'y': (5, 6)}),
+            'b': MyTreeValue({'x': (2, 3), 'y': (7, 8)}),
         }
-        assert rise(t3, template=None) == t3
-
         with pytest.raises(ValueError):
-            rise(t2, template={'a': [None, None], 'b': [None, None]})
+            rise(t4, template={'a': [], 'b': None})
+
+        t5 = TreeValue({
+            'x': raw([{'a': 1, 'b': 2}, {'a': 2, 'b': 3}, {'a': 3, 'b': 5}]),
+            'y': raw([{'a': 21, 'b': 32}, {'a': -2, 'b': 23}, {'a': 53, 'b': 25}]),
+        })
+        assert rise(t5) == [
+            {'a': TreeValue({'x': 1, 'y': 21}), 'b': TreeValue({'x': 2, 'y': 32})},
+            {'a': TreeValue({'x': 2, 'y': -2}), 'b': TreeValue({'x': 3, 'y': 23})},
+            {'a': TreeValue({'x': 3, 'y': 53}), 'b': TreeValue({'x': 5, 'y': 25})},
+        ]
+        assert rise(t5, template=[{'a': None, 'b': None}]) == [
+            {'a': TreeValue({'x': 1, 'y': 21}), 'b': TreeValue({'x': 2, 'y': 32})},
+            {'a': TreeValue({'x': 2, 'y': -2}), 'b': TreeValue({'x': 3, 'y': 23})},
+            {'a': TreeValue({'x': 3, 'y': 53}), 'b': TreeValue({'x': 5, 'y': 25})},
+        ]
+        assert rise(t5, template=[]) == [
+            TreeValue({'x': raw({'a': 1, 'b': 2}), 'y': raw({'a': 21, 'b': 32})}),
+            TreeValue({'x': raw({'a': 2, 'b': 3}), 'y': raw({'a': -2, 'b': 23})}),
+            TreeValue({'x': raw({'a': 3, 'b': 5}), 'y': raw({'a': 53, 'b': 25})}),
+        ]
+        assert rise(t5, template=[None]) == [
+            TreeValue({'x': raw({'a': 1, 'b': 2}), 'y': raw({'a': 21, 'b': 32})}),
+            TreeValue({'x': raw({'a': 2, 'b': 3}), 'y': raw({'a': -2, 'b': 23})}),
+            TreeValue({'x': raw({'a': 3, 'b': 5}), 'y': raw({'a': 53, 'b': 25})}),
+        ]
         with pytest.raises(ValueError):
-            rise(t3, template={'a': (None, None), 'b': [None, None]})
+            rise(t5, template=[None, None])
 
     def test_reduce(self):
         class MyTreeValue(TreeValue):
diff --git a/test/utils/__init__.py b/test/utils/__init__.py
index 6c4bf61abb..3bde9810df 100644
--- a/test/utils/__init__.py
+++ b/test/utils/__init__.py
@@ -1,6 +1,10 @@
 from .test_clazz import TestUtilsClazz
+from .test_color import TestUtilsColor
 from .test_enum import TestUtilsEnum
+from .test_exception import TestUtilsException
 from .test_final import TestUtilsFinal
 from .test_func import TestUtilsFunc
+from .test_imports import TestUtilsImports
+from .test_random import TestUtilsRandom
 from .test_singleton import TestUtilsSingleton
 from .test_tree import TestUtilsTree
diff --git a/test/utils/test_clazz.py b/test/utils/test_clazz.py
index ef4ff3c159..dbf4ec3f92 100644
--- a/test/utils/test_clazz.py
+++ b/test/utils/test_clazz.py
@@ -2,7 +2,11 @@
 from easydict import EasyDict
 
 from treevalue.tree import FastTreeValue, TreeValue
-from treevalue.utils import init_magic, common_direct_base, common_bases
+from treevalue.utils import init_magic, common_direct_base, common_bases, get_class_full_name
+
+
+class _TestClass:
+    pass
 
 
 @pytest.mark.unittest
@@ -88,3 +92,7 @@ class T5(T2, T1):
 
         assert common_direct_base(T3, T4) == T1
         assert common_direct_base(T4, T5) == object
+
+    def test_get_class_full_name(self):
+        assert get_class_full_name(str) == 'str'
+        assert get_class_full_name(_TestClass) == 'test.utils.test_clazz._TestClass'
diff --git a/test/utils/test_color.py b/test/utils/test_color.py
new file mode 100644
index 0000000000..59a78f2005
--- /dev/null
+++ b/test/utils/test_color.py
@@ -0,0 +1,162 @@
+import math
+import pickle
+
+import pytest
+
+from treevalue.utils import Color
+from ..tests import float_eq
+
+
+# noinspection DuplicatedCode
+@pytest.mark.unittest
+class TestUtilsColor:
+    def test_basis(self):
+        c1 = Color((0.8, 0.7, 0.5))
+        assert float_eq(c1.rgb.red, 0.8)
+        assert float_eq(c1.rgb.green, 0.7)
+        assert float_eq(c1.rgb.blue, 0.5)
+        assert c1.alpha is None
+        assert str(c1) == '#ccb280'
+        assert repr(c1) == '<Color #ccb280>'
+
+    def test_basic_with_alpha(self):
+        c1 = Color((0.8, 0.7, 0.5), 0.6)
+        assert float_eq(c1.rgb.red, 0.8)
+        assert float_eq(c1.rgb.green, 0.7)
+        assert float_eq(c1.rgb.blue, 0.5)
+        assert float_eq(c1.alpha, 0.6)
+        assert str(c1) == '#ccb28099'
+        assert repr(c1) == '<Color #ccb280, alpha: 0.600>'
+
+    def test_basic_with_hex(self):
+        c1 = Color('#aabbccdd')
+        assert c1.rgb.red == int('aa', 16) / 255
+        assert c1.rgb.green == int('bb', 16) / 255
+        assert c1.rgb.blue == int('cc', 16) / 255
+        assert c1.alpha == int('dd', 16) / 255
+
+        with pytest.raises(ValueError):
+            Color('#aabbccd')
+
+        c2 = Color('#aabbccdd', alpha=0.8)
+        assert c2.rgb.red == int('aa', 16) / 255
+        assert c2.rgb.green == int('bb', 16) / 255
+        assert c2.rgb.blue == int('cc', 16) / 255
+        assert c2.alpha == int('dd', 16) / 255 * 0.8
+
+    def test_basic_error(self):
+        with pytest.raises(TypeError):
+            Color(None)
+
+    def test_dumps_hash_and_eq(self):
+        c1 = Color('#aabbccdd')
+        assert c1 == c1
+        assert c1 == Color('#aabbccdd')
+        assert pickle.loads(pickle.dumps(c1)) == c1
+        assert c1 != 1
+
+        assert hash(Color('#aabbccdd')) == hash(c1)
+
+    def test_set_rgb(self):
+        c1 = Color((0.8, 0.7, 0.5), 0.6)
+        assert float_eq(c1.rgb.red, 0.8)
+        assert float_eq(c1.rgb.green, 0.7)
+        assert float_eq(c1.rgb.blue, 0.5)
+        assert float_eq(c1.alpha, 0.6)
+
+        r, g, b = c1.rgb
+        assert float_eq((r, g, b), (0.8, 0.7, 0.5))
+        assert repr(c1.rgb) == '<RGBColorProxy red: 0.800, green: 0.700, blue: 0.500>'
+
+        c1.rgb.red *= 0.7
+        c1.rgb.green *= 0.6
+        c1.rgb.blue *= 0.8
+        c1.alpha *= 0.9
+        assert float_eq(c1.rgb.red, 0.56)
+        assert float_eq(c1.rgb.green, 0.42)
+        assert float_eq(c1.rgb.blue, 0.4)
+        assert float_eq(c1.alpha, 0.54)
+        assert str(c1) == '#8f6b668a'
+
+        with pytest.warns(Warning):
+            c1.rgb.red *= 10
+        with pytest.warns(Warning):
+            c1.rgb.green *= 10
+        with pytest.warns(Warning):
+            c1.rgb.blue *= 10
+        with pytest.warns(Warning):
+            c1.alpha *= 10
+        assert float_eq(c1.rgb.red, 1.0)
+        assert float_eq(c1.rgb.green, 1.0)
+        assert float_eq(c1.rgb.blue, 1.0)
+        assert float_eq(c1.alpha, 1.0)
+        assert str(c1) == '#ffffffff'
+
+        with pytest.warns(Warning):
+            c1.rgb.red *= -10
+        with pytest.warns(Warning):
+            c1.rgb.green *= -10
+        with pytest.warns(Warning):
+            c1.rgb.blue *= -10
+        with pytest.warns(Warning):
+            c1.alpha *= -10
+
+        assert float_eq(c1.rgb.red, 0.0)
+        assert float_eq(c1.rgb.green, 0.0)
+        assert float_eq(c1.rgb.blue, 0.0)
+        assert float_eq(c1.alpha, 0.0)
+        assert str(c1) == '#00000000'
+
+    def test_set_hsv(self):
+        c1 = Color((0.8, 0.7, 0.5), 0.6)
+
+        h, s, v = c1.hsv
+        assert repr(c1.hsv) == '<HSVColorProxy hue: 0.111, saturation: 0.375, value: 0.800>'
+
+        assert Color.from_hsv(h, s, v, 0.6) == c1
+
+        c1.hsv.hue *= 0.6
+        c1.hsv.saturation *= 0.7
+        c1.hsv.value *= 0.8
+        assert float_eq(tuple(c1.hsv), (h * 0.6, s * 0.7, v * 0.8))
+        h, s, v = c1.hsv
+
+        with pytest.warns(None):
+            c1.hsv.hue *= 1000
+        with pytest.warns(Warning):
+            c1.hsv.saturation *= 10
+        with pytest.warns(Warning):
+            c1.hsv.value *= 10
+        assert float_eq(tuple(c1.hsv), (h * 1000 - math.floor(h * 1000), 1.0, 1.0))
+        h, s, v = c1.hsv
+
+        with pytest.warns(None):
+            c1.hsv.hue *= -1000
+        assert float_eq(tuple(c1.hsv), (h * -1000 - math.floor(h * -1000), s, v))
+
+    def test_set_hls(self):
+        c1 = Color((0.8, 0.7, 0.5), 0.6)
+
+        h, l, s = c1.hls
+        assert repr(c1.hls) == '<HLSColorProxy hue: 0.111, lightness: 0.650, saturation: 0.429>'
+
+        assert Color.from_hls(h, l, s, 0.6) == c1
+
+        c1.hls.hue *= 0.6
+        c1.hls.lightness *= 0.7
+        c1.hls.saturation *= 0.8
+        assert float_eq(tuple(c1.hls), (h * 0.6, l * 0.7, s * 0.8))
+        h, l, s = c1.hls
+
+        with pytest.warns(None):
+            c1.hls.hue *= 1000
+        assert float_eq(tuple(c1.hls), (h * 1000 - math.floor(h * 1000), l, s))
+
+        with pytest.warns(Warning):
+            c1.hls.lightness += 1000
+        assert float_eq(c1.hls.lightness, 1.0)
+        c1.hls.lightness = 0.5
+
+        with pytest.warns(Warning):
+            c1.hls.saturation += 1000
+        assert float_eq(c1.hls.saturation, 1.0)
diff --git a/test/utils/test_exception.py b/test/utils/test_exception.py
new file mode 100644
index 0000000000..8f17717796
--- /dev/null
+++ b/test/utils/test_exception.py
@@ -0,0 +1,48 @@
+import pytest
+
+from treevalue.utils import str_traceback
+
+
+@pytest.mark.unittest
+class TestUtilsException:
+    def test_str_traceback(self):
+        def func1(x):
+            return func2(x * 3 + 1)
+
+        def func2(x):
+            if x % 2 == 0:
+                return func3(x)
+            else:
+                return func4(x)
+
+        def func3(x):
+            raise RuntimeError('this is a runtime error')
+
+        def func4(x):
+            raise ValueError('this is a value error')
+
+        try:
+            func1(1)
+        except Exception as err:
+            str_ = str_traceback(err)
+            assert 'func1' in str_
+            assert 'func2' in str_
+            assert 'func3' in str_
+            assert 'RuntimeError' in str_
+            assert 'this is a runtime error' in str_
+            assert 'func4' not in str_
+            assert 'ValueError' not in str_
+            assert 'this is a value error' not in str_
+
+        try:
+            func1(2)
+        except Exception as err:
+            str_ = str_traceback(err)
+            assert 'func1' in str_
+            assert 'func2' in str_
+            assert 'func3' not in str_
+            assert 'RuntimeError' not in str_
+            assert 'this is a runtime error' not in str_
+            assert 'func4' in str_
+            assert 'ValueError' in str_
+            assert 'this is a value error' in str_
diff --git a/test/utils/test_func.py b/test/utils/test_func.py
index 37930fce85..fdba96f0aa 100644
--- a/test/utils/test_func.py
+++ b/test/utils/test_func.py
@@ -1,6 +1,8 @@
+from functools import wraps
+
 import pytest
 
-from treevalue.utils import args_iter, dynamic_call
+from treevalue.utils import args_iter, dynamic_call, static_call, post_process, pre_process, freduce, raising, warning_
 
 
 @pytest.mark.unittest
@@ -16,3 +18,168 @@ def test_dynamic_call(self):
         assert dynamic_call(lambda x, y, **kwargs: (kwargs, x, y))(1, k=2, y=3) == ({'k': 2}, 1, 3)
         assert dynamic_call(lambda x, y, *args, t=2, v=4, **kwargs: (args, kwargs, x, y, t, v))(1, 2, 3, 4, p=5, v=7) \
                == ((3, 4), {'p': 5}, 1, 2, 2, 7)
+
+    def __get_wrapped_function(self):
+        def _wrapper(func):
+            @wraps(func)
+            def _new_func(*args):
+                return func(sum(args))
+
+            return _new_func
+
+        @dynamic_call
+        @dynamic_call
+        @_wrapper
+        def f(x):
+            return x ** x
+
+        return f
+
+    def test_dynamic_call_nested_with_wrapper(self):
+        f = self.__get_wrapped_function()
+        assert f(1, 2, 3, 4) == 10 ** 10
+
+    def test_static_call(self):
+        f = self.__get_wrapped_function()
+        f = static_call(f, static_ok=False).__wrapped__
+        assert f(2) == 4
+        with pytest.raises(TypeError):
+            _ = f(1, 2, 3, 4)
+
+        def another_f(x):
+            return x ** x
+
+        with pytest.raises(TypeError):
+            _ = static_call(another_f, static_ok=False)
+
+    def test_pre_process(self):
+        @pre_process(lambda x, y: (-x, (x + 2) * y))
+        def plus(a, b):
+            return a + b
+
+        assert plus(1, 2) == 5
+
+        @pre_process(lambda x, y: ((), {'a': -x, 'b': (x + 2) * y}))
+        def plus2(a, b):
+            return a + b
+
+        assert plus2(1, 2) == 5
+
+        @pre_process(lambda x, y: {'a': -x, 'b': (x + 2) * y})
+        def plus3(a, b):
+            return a + b
+
+        assert plus3(1, 2) == 5
+
+        @pre_process(lambda x, y: ((-x, -x + 1, -x + 2), (y, y + 1, y + 2)))
+        def plus4(a, b):
+            return a + b
+
+        assert plus4(1, 2) == (-1, 0, 1, 2, 3, 4)
+
+        @pre_process(lambda x: -x)
+        def pw(a):
+            return a ** a
+
+        assert pw(-3) == 27
+
+    def test_post_process(self):
+        @post_process(lambda x: -x)
+        def plus(a, b):
+            return a + b
+
+        assert plus(1, 2) == -3
+
+        @post_process(lambda: None)
+        def plus2(a, b):
+            return a + b
+
+        assert plus2(1, 2) is None
+
+    def test_freduce(self):
+        @freduce(init=lambda neg=False: 1 if neg else 0)
+        def plus(a, b, neg: bool = False):
+            return a + b if not neg else a * b
+
+        assert plus() == 0
+        assert plus(1) == 1
+        assert plus(1, 2) == 3
+        assert plus(1, 2, 3, 4, 5) == 15
+        assert plus(1, 2, 3, 4, neg=True) == 24
+
+        @freduce()
+        def plus2(a, b, neg: bool = False):
+            return a + b if not neg else a * b
+
+        with pytest.raises(SyntaxError):
+            _ = plus2()
+        assert plus2(1) == 1
+        assert plus2(1, 2) == 3
+        assert plus2(1, 2, 3, 4, 5) == 15
+        assert plus2(1, 2, 3, 4, neg=True) == 24
+
+        @freduce(init=lambda neg=False: 1 if neg else 0, pass_kwargs=False)
+        def plus3(a, b, neg: bool = False):
+            return a + b if not neg else a * b
+
+        assert plus3() == 0
+        assert plus3(1) == 1
+        assert plus3(1, 2) == 3
+        assert plus3(1, 2, 3, 4, 5) == 15
+        with pytest.warns(SyntaxWarning):
+            assert plus3(1, 2, 3, 4, neg=True) == 10
+
+    def test_raising(self):
+        f1 = raising(lambda: RuntimeError)
+        f2 = raising(RuntimeError)
+        f3 = raising(RuntimeError())
+        with pytest.raises(RuntimeError):
+            f1()
+        with pytest.raises(RuntimeError):
+            f2()
+        with pytest.raises(RuntimeError):
+            f3()
+
+        f4 = raising(lambda x: RuntimeError if x < 0 else x)
+        assert f4(0) == 0
+        assert f4(1) == 1
+        with pytest.raises(RuntimeError):
+            f4(-1)
+
+    def test_warning(self):
+        f1 = warning_(lambda: RuntimeWarning)
+        f2 = warning_(RuntimeWarning)
+        f3 = warning_(RuntimeWarning())
+        f4 = warning_('warning')
+        with pytest.warns(RuntimeWarning):
+            f1()
+        with pytest.warns(RuntimeWarning):
+            f2()
+        with pytest.warns(RuntimeWarning):
+            f3()
+        with pytest.warns(UserWarning):
+            f4()
+
+        f5 = warning_(lambda x: RuntimeWarning if x < 0 else x)
+        with pytest.warns(None):
+            f5(1)
+        with pytest.warns(RuntimeWarning):
+            f5(-1)
+
+        f6 = warning_(lambda x: (RuntimeWarning, (), {}) if x < 0 else x)
+        with pytest.warns(None):
+            f6(1)
+        with pytest.warns(RuntimeWarning):
+            f6(-1)
+
+        f7 = warning_(lambda x: (RuntimeWarning, ()) if x < 0 else x)
+        with pytest.warns(None):
+            f7(1)
+        with pytest.warns(RuntimeWarning):
+            f7(-1)
+
+        f8 = warning_(lambda x: (RuntimeWarning, {}) if x < 0 else x)
+        with pytest.warns(None):
+            f8(1)
+        with pytest.warns(RuntimeWarning):
+            f8(-1)
diff --git a/test/utils/test_imports.py b/test/utils/test_imports.py
new file mode 100644
index 0000000000..2d514e6aa6
--- /dev/null
+++ b/test/utils/test_imports.py
@@ -0,0 +1,46 @@
+import pytest
+from easydict import EasyDict
+
+from treevalue.utils import import_object, quick_import_object, iter_import_objects
+
+OBJ = 1
+P_1 = 2
+P_2 = 3
+P_3 = 4
+QQ3 = EasyDict(
+    P_1=P_1,
+    P_2=P_2,
+    P_3=P_3,
+)
+
+
+@pytest.mark.unittest
+class TestUtilsImports:
+    def test_import_object(self):
+        assert import_object('OBJ', 'test.utils.test_imports') == 1
+        assert import_object('zip') == zip
+
+    def test_quick_import_object(self):
+        assert quick_import_object('test.utils.test_imports.OBJ') == (1, 'test.utils.test_imports', 'OBJ')
+        assert quick_import_object('zip') == (zip, '', 'zip')
+        assert quick_import_object('zip.__dict__') == (zip.__dict__, '', 'zip.__dict__')
+        assert quick_import_object('z*') == (zip, '', 'zip')
+        assert quick_import_object('test.utils.test_imports.P_?') == (P_1, 'test.utils.test_imports', 'P_1')
+        assert quick_import_object('test.utils.test_imports.???.*') == (P_1, 'test.utils.test_imports', 'QQ3.P_1')
+
+        with pytest.raises(ImportError):
+            quick_import_object('p233')
+        with pytest.raises(ImportError):
+            quick_import_object('zip.no_such_attr')
+
+    def test_iter_import_objects(self):
+        assert list(iter_import_objects('test.utils.test_imports.P_*')) == [
+            (P_1, 'test.utils.test_imports', 'P_1'),
+            (P_2, 'test.utils.test_imports', 'P_2'),
+            (P_3, 'test.utils.test_imports', 'P_3'),
+        ]
+        assert list(iter_import_objects('test.utils.test_imports.???.*')) == [
+            (P_1, 'test.utils.test_imports', 'QQ3.P_1'),
+            (P_2, 'test.utils.test_imports', 'QQ3.P_2'),
+            (P_3, 'test.utils.test_imports', 'QQ3.P_3'),
+        ]
diff --git a/test/utils/test_random.py b/test/utils/test_random.py
new file mode 100644
index 0000000000..87dfca471a
--- /dev/null
+++ b/test/utils/test_random.py
@@ -0,0 +1,25 @@
+import re
+
+import pytest
+
+from treevalue.utils import seed_random, random_hex, random_hex_with_timestamp
+
+
+@pytest.mark.unittest
+class TestUtilsRandom:
+    def test_seed_random(self):
+        with seed_random(0) as rnd:
+            a, b, c = rnd.randint(0x00, 0xff), rnd.randint(0x00, 0xff), rnd.randint(0x00, 0xff)
+        assert (a, b, c) == (197, 215, 20)
+
+        with seed_random(233) as rnd:
+            a, b, c = rnd.randint(0x00, 0xff), rnd.randint(0x00, 0xff), rnd.randint(0x00, 0xff)
+        assert (a, b, c) == (89, 118, 247)
+
+    def test_random_hex(self):
+        assert re.fullmatch(r'^[a-f0-9]{32}$', random_hex())
+        assert re.fullmatch(r'^[a-f0-9]{48}$', random_hex(48))
+
+    def test_random_hex_with_timestamp(self):
+        assert re.fullmatch(r'^\d{8}_\d{12}_[a-f0-9]{12}$', random_hex_with_timestamp())
+        assert re.fullmatch(r'^\d{8}_\d{12}_[a-f0-9]{48}$', random_hex_with_timestamp(48))
diff --git a/test/utils/test_tree.py b/test/utils/test_tree.py
index 89fb57c0af..c2fa486071 100644
--- a/test/utils/test_tree.py
+++ b/test/utils/test_tree.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-from treevalue.utils import build_tree
+from treevalue.utils import build_tree, build_graph
 
 
 @pytest.mark.unittest
@@ -10,9 +10,32 @@ class TestUtilsTree:
     def test_build_tree(self):
         t = build_tree(
             {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}, 'z': [1, 2], 'v': {'1': '2'}},
-            represent=lambda x: '<node>' if isinstance(x, dict) else repr(x),
-            recurse=lambda x: isinstance(x, dict),
+            repr_gen=lambda x: '<node>' if isinstance(x, dict) else repr(x),
         )
         assert json.loads(t.to_json(sort=True)) == {'<node>': {
             'children': ["'a' --> 1", "'b' --> 2", {"'v' --> <node>": {'children': ["'1' --> '2'"]}},
                          {"'x' --> <node>": {'children': ["'c' --> 3", "'d' --> 4"]}}, "'z' --> [1, 2]"]}}
+
+    def test_build_graph(self):
+        t = {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}}
+        g = build_graph((t, 't'), graph_title="Demo of build_graph.")
+        assert "Demo of build_graph." in g.source
+        assert "t.x" in g.source
+        assert 530 < len(g.source) <= 560
+
+        g2 = build_graph(t, graph_title="Demo 2 of build_graph.")
+        assert "Demo 2 of build_graph." in g2.source
+        assert "<root_0>" in g2.source
+        assert "<root_0>.x" in g2.source
+        assert 550 < len(g2.source) <= 580
+
+        g3 = build_graph((t,), graph_title="Demo 3 of build_graph.")
+        assert "Demo 3 of build_graph." in g3.source
+        assert "<root_0>" in g3.source
+        assert "<root_0>.x" in g3.source
+        assert 550 < len(g3.source) <= 580
+
+        g4 = build_graph((), graph_title="Demo 4 of build_graph.")
+        assert "Demo 4 of build_graph." in g4.source
+        assert "node" not in g4.source
+        assert 90 <= len(g4.source) <= 110