提交 029f029e 编写于 作者: HansBug's avatar HansBug 😆

Merge branch 'main' into dev/potc

......@@ -36,3 +36,46 @@ RawWrapper
.. autoclass:: RawWrapper
:members: __init__, value
.. _apidoc_tree_common_delayed_partial:
delayed_partial
----------------------
.. autofunction:: delayed_partial
.. _apidoc_tree_common_undelay:
undelay
---------------
.. autofunction:: undelay
.. _apidoc_tree_common_delayedproxy:
DelayedProxy
-------------------
.. autoclass:: DelayedProxy
:members: value, fvalue
.. _apidoc_tree_common_delayedvalueproxy:
DelayedValueProxy
-------------------
.. autoclass:: DelayedValueProxy
:members: __cinit__, value, fvalue
.. _apidoc_tree_common_delayedfuncproxy:
DelayedFuncProxy
-------------------
.. autoclass:: DelayedFuncProxy
:members: __cinit__, value, fvalue
......@@ -12,6 +12,14 @@ TreeValue
:members: __init__, __getattribute__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__, __getstate__, __setstate__, get
.. _apidoc_tree_tree_delayed:
delayed
---------------
.. autofunction:: delayed
.. _apidoc_tree_tree_jsonify:
jsonify
......
......@@ -2,7 +2,7 @@ sphinx~=3.2.0
sphinx_rtd_theme~=0.4.3
enum_tools
sphinx-toolbox
plantumlcli>=0.0.2
plantumlcli>=0.0.4
packaging
sphinx-multiversion~=0.2.4
where~=1.0.2
......
import pytest
from treevalue.tree.common import DelayedProxy, delayed_partial
@pytest.mark.unittest
class TestTreeDelay:
def test_delayed_partial_simple(self):
cnt = 0
def f():
nonlocal cnt
cnt += 1
return 1
pv = delayed_partial(f)
assert cnt == 0
assert isinstance(pv, DelayedProxy)
assert pv.value() == 1
assert cnt == 1
assert pv.value() == 1
assert cnt == 1
def test_delayed_partial_func(self):
cnt = 0
def f(x, y):
nonlocal cnt
cnt += 1
return x + y * 2 + 1
pv = delayed_partial(f, 2, y=3)
assert cnt == 0
assert isinstance(pv, DelayedProxy)
assert pv.value() == 9
assert cnt == 1
assert pv.value() == 9
assert cnt == 1
def test_delayed_partial_complex(self):
cnt1, cnt2 = 0, 0
def f1():
nonlocal cnt1
cnt1 += 1
return 1
def f2(x, y):
nonlocal cnt2
cnt2 += 1
return (x + 1) ** 2 + y + 2
pv = delayed_partial(f2, delayed_partial(f1), delayed_partial(f1))
assert cnt1 == 0
assert cnt2 == 0
assert isinstance(pv, DelayedProxy)
assert pv.value() == 7
assert cnt1 == 2
assert cnt2 == 1
assert pv.value() == 7
assert cnt1 == 2
assert cnt2 == 1
......@@ -2,10 +2,10 @@ import pickle
import pytest
from treevalue.tree.common import create_storage, raw, TreeStorage
from treevalue.tree.common import create_storage, raw, TreeStorage, delayed_partial
# noinspection PyArgumentList,DuplicatedCode
# noinspection PyArgumentList,DuplicatedCode,PyTypeChecker
@pytest.mark.unittest
class TestTreeStorage:
def test_init(self):
......@@ -23,6 +23,52 @@ class TestTreeStorage:
with pytest.raises(KeyError):
_ = t.get('fff')
cnt1, cnt2, cnt3 = 0, 0, 0
def f1():
nonlocal cnt1
cnt1 += 1
return 2
def f2(x, y):
nonlocal cnt2
cnt2 += 1
return {'x': x, 'y': y}
def f3(x, y):
nonlocal cnt3
cnt3 += 1
return create_storage({'x': x, 'y': raw(y)})
t2 = create_storage({
'a': 1,
'b': delayed_partial(f1),
'c': delayed_partial(f2, delayed_partial(f1), 3),
'd': delayed_partial(f3, 3, delayed_partial(f2, 3, 4))
})
assert t2.get('a') == 1
assert cnt1 == 0
assert t2.get('b') == 2
assert cnt1 == 1
assert t2.get('b') == 2
assert cnt1 == 1
assert (cnt1, cnt2) == (1, 0)
assert t2.get('c') == {'x': 2, 'y': 3}
assert (cnt1, cnt2) == (2, 1)
assert t2.get('c') == {'x': 2, 'y': 3}
assert (cnt1, cnt2) == (2, 1)
assert (cnt1, cnt2, cnt3) == (2, 1, 0)
assert t2.get('d').get('x') == 3
assert t2.get('d').get('y') == {'x': 3, 'y': 4}
assert (cnt1, cnt2, cnt3) == (2, 2, 1)
assert t2.get('d').get('x') == 3
assert t2.get('d').get('y') == {'x': 3, 'y': 4}
assert (cnt1, cnt2, cnt3) == (2, 2, 1)
def test_get_or_default(self):
t = create_storage({'a': 1, 'b': 2, 'c': raw({'x': 3, 'y': 4}), 'd': {'x': 3, 'y': 4}})
assert t.get_or_default('a', 233) == 1
......@@ -34,6 +80,23 @@ class TestTreeStorage:
assert t.get_or_default('fff', 233) == 233
t1 = create_storage({
'a': delayed_partial(lambda: t.get('a')),
'b': delayed_partial(lambda: t.get('b')),
'c': delayed_partial(lambda: t.get('c')),
'd': delayed_partial(lambda: t.get('d')),
})
assert t1.get_or_default('a', 233) == 1
assert t1.get_or_default('b', 233) == 2
assert t1.get_or_default('c', 233) == {'x': 3, 'y': 4}
assert isinstance(t1.get_or_default('d', 233), TreeStorage)
assert t1.get_or_default('d', 233).get_or_default('x', 233) == 3
assert t1.get_or_default('d', 233).get_or_default('y', 233) == 4
assert t1.get_or_default('fff', 233) == 233
assert t1.get_or_default('fff', delayed_partial(lambda: 2345)) == 2345
assert not t1.contains('fff')
def test_set(self):
t = create_storage({})
t.set('a', 1)
......@@ -122,6 +185,19 @@ class TestTreeStorage:
assert _dumped['d']['x'] == 3
assert _dumped['d']['y'] == 4
t2 = create_storage({
'a': 1,
'b': delayed_partial(lambda x: x + 1, 1),
'c': delayed_partial(lambda: h1),
'd': delayed_partial(lambda: create_storage(h2)),
})
_dumped = t2.dump()
assert _dumped['a'] == 1
assert _dumped['b'] == 2
assert _dumped['c'].value() is h1
assert _dumped['d']['x'] == 3
assert _dumped['d']['y'] == 4
def test_deepdump(self):
h1 = {'x': 3, 'y': 4}
h2 = {'x': 3, 'y': 4}
......@@ -177,7 +253,7 @@ class TestTreeStorage:
h2 = {'x': 3, 'y': 4}
t = create_storage({'a': 1, 'b': 2, 'c': raw(h1), 'd': h2})
t1 = t.deepcopyx(lambda x: -x if isinstance(x, int) else {'holy': 'shit'})
t1 = t.deepcopyx(lambda x: -x if isinstance(x, int) else {'holy': 'shit'}, False)
assert t1.get('a') == -1
assert t1.get('b') == -2
assert t1.get('c') == {'holy': 'shit'}
......@@ -232,6 +308,19 @@ class TestTreeStorage:
assert t1.get('f').get('y') == 4
assert t1.get('f') is not t.get('f')
t2 = create_storage({
'a': delayed_partial(lambda: 11),
'b': delayed_partial(lambda: 22),
'c': delayed_partial(lambda: {'x': 3, 'y': 5}),
'd': delayed_partial(lambda: create_storage({'x': 3, 'y': 7})),
})
t1.copy_from(t2)
assert t1.get('a') == 11
assert t1.get('b') == 22
assert t1.get('c') == {'x': 3, 'y': 5}
assert t1.get('d').get('x') == 3
assert t1.get('d').get('y') == 7
def test_deepcopy_from(self):
h1 = {'x': 3, 'y': 4}
h2 = {'x': 3, 'y': 4}
......@@ -253,6 +342,19 @@ class TestTreeStorage:
assert t1.get('f').get('y') == 4
assert t1.get('f') is not t.get('f')
t2 = create_storage({
'a': delayed_partial(lambda: 11),
'b': delayed_partial(lambda: 22),
'c': delayed_partial(lambda: {'x': 3, 'y': 5}),
'd': delayed_partial(lambda: create_storage({'x': 3, 'y': 7})),
})
t1.deepcopy_from(t2)
assert t1.get('a') == 11
assert t1.get('b') == 22
assert t1.get('c') == {'x': 3, 'y': 5}
assert t1.get('d').get('x') == 3
assert t1.get('d').get('y') == 7
def test_repr(self):
h1 = {'x': 3, 'y': 4}
h2 = {'x': 3, 'y': 4}
......@@ -272,6 +374,21 @@ class TestTreeStorage:
assert t == t
assert t == t1
assert t != t2
assert t != None
t3 = create_storage({
'a': delayed_partial(lambda: 11),
'b': delayed_partial(lambda: 22),
'c': delayed_partial(lambda: {'x': 3, 'y': 5}),
'd': delayed_partial(lambda: create_storage({'x': 3, 'y': 7})),
})
t4 = create_storage({
'a': delayed_partial(lambda: t3.get('a')),
'b': delayed_partial(lambda: t3.get('b')),
'c': delayed_partial(lambda: t3.get('c')),
'd': delayed_partial(lambda: t3.get('d')),
})
assert t3 == t4
def test_keys(self):
h1 = {'x': 3, 'y': 4}
......@@ -286,10 +403,20 @@ class TestTreeStorage:
t = create_storage({'a': 1, 'b': 2, 'd': h1})
assert set(t.get('d').values()) == {3, 4}
assert len(t.values()) == 3
assert len(list(t.values())) == 3
assert 1 in t.values()
assert 2 in t.values()
t1 = create_storage({
'a': delayed_partial(lambda: t.get('a')),
'b': delayed_partial(lambda: t.get('b')),
'd': delayed_partial(lambda: t.get('d')),
})
assert set(t1.get('d').values()) == {3, 4}
assert len(list(t1.values())) == 3
assert 1 in t1.values()
assert 2 in t1.values()
def test_items(self):
h1 = {'x': 3, 'y': 4}
t = create_storage({'a': 1, 'b': 2, 'd': raw(h1)})
......@@ -303,3 +430,39 @@ class TestTreeStorage:
assert v == h1
else:
pytest.fail('Should not reach here.')
t1 = create_storage({
'a': delayed_partial(lambda: t.get('a')),
'b': delayed_partial(lambda: t.get('b')),
'd': delayed_partial(lambda: t.get('d')),
})
for k, v in t1.items():
if k == 'a':
assert v == 1
elif k == 'b':
assert v == 2
elif k == 'd':
assert v == h1
else:
pytest.fail('Should not reach here.')
def test_hash(self):
h = {}
h1 = {'x': 3, 'y': 4}
t = create_storage({'a': 1, 'b': 2, 'd': h1})
t1 = create_storage({
'a': delayed_partial(lambda: t.get('a')),
'b': delayed_partial(lambda: t.get('b')),
'd': delayed_partial(lambda: t.get('d')),
})
t2 = create_storage({
'a': delayed_partial(lambda: t.get('a')),
'b': delayed_partial(lambda: 3),
'd': delayed_partial(lambda: t.get('d')),
})
h[t] = 1
assert t1 in h
assert h[t1] == 1
assert t2 not in h
......@@ -3,7 +3,7 @@ from operator import __mul__
import pytest
from treevalue.tree import func_treelize, TreeValue, method_treelize, classmethod_treelize
from treevalue.tree import func_treelize, TreeValue, method_treelize, classmethod_treelize, delayed
# noinspection DuplicatedCode
......@@ -257,3 +257,147 @@ class TestTreeFuncFunc:
'f': [4],
},
})
def test_delay_support(self):
@func_treelize(return_type=TreeValue)
def f(x, y, z):
return x + y * 2 + z * 3
t1 = TreeValue({
'a': 1,
'b': delayed(lambda x: x ** 2, 3),
'c': {'x': 2, 'y': delayed(lambda: 4)},
})
t2 = TreeValue({
'a': delayed(lambda x: x + 1, t1.a),
'b': delayed(lambda: t1.c.y),
'c': delayed(lambda: 5),
})
t3 = delayed(lambda: 6)
assert f(t1, t2, t3) == TreeValue({
'a': 23, 'b': 35,
'c': {'x': 30, 'y': 32},
})
t1 = TreeValue({
'a': 1,
'b': delayed(lambda x: x ** 2, 3),
'c': {'x': 2, 'y': delayed(lambda: 4)},
})
t2 = TreeValue({
'a': delayed(lambda x: x + 1, t1.a),
'b': delayed(lambda: t1.c.y),
'c': delayed(lambda: 5),
})
t3 = delayed(lambda: 6)
assert f(x=t1, y=t2, z=t3) == TreeValue({
'a': 23, 'b': 35,
'c': {'x': 30, 'y': 32},
})
def test_delayed_treelize(self):
t1 = TreeValue({
'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4},
})
t2 = TreeValue({
'a': 11, 'b': 23, 'x': {'c': 35, 'd': 47},
})
cnt_1 = 0
@func_treelize(delayed=True)
def total(a, b):
nonlocal cnt_1
cnt_1 += 1
return a + b
# positional
t3 = total(t1, t2)
assert cnt_1 == 0
assert t3.a == 12
assert cnt_1 == 1
assert t3.x == TreeValue({'c': 38, 'd': 51})
assert cnt_1 == 3
assert t3 == TreeValue({
'a': 12, 'b': 25, 'x': {'c': 38, 'd': 51}
})
assert cnt_1 == 4
# keyword
cnt_1 = 0
t3 = total(a=t1, b=t2)
assert cnt_1 == 0
assert t3.a == 12
assert cnt_1 == 1
assert t3.x == TreeValue({'c': 38, 'd': 51})
assert cnt_1 == 3
assert t3 == TreeValue({
'a': 12, 'b': 25, 'x': {'c': 38, 'd': 51}
})
assert cnt_1 == 4
# positional, with constant
cnt_1 = 0
t3 = total(1, t2)
assert cnt_1 == 0
assert t3.a == 12
assert cnt_1 == 1
assert t3.x == TreeValue({'c': 36, 'd': 48})
assert cnt_1 == 3
assert t3 == TreeValue({
'a': 12, 'b': 24, 'x': {'c': 36, 'd': 48}
})
assert cnt_1 == 4
# keyword, with constant
cnt_1 = 0
t3 = total(b=1, a=t2)
assert cnt_1 == 0
assert t3.a == 12
assert cnt_1 == 1
assert t3.x == TreeValue({'c': 36, 'd': 48})
assert cnt_1 == 3
assert t3 == TreeValue({
'a': 12, 'b': 24, 'x': {'c': 36, 'd': 48}
})
assert cnt_1 == 4
# positional, with delay
cnt_1 = 0
t4 = TreeValue({'v': delayed(lambda: t1)})
t5 = TreeValue({'v': delayed(lambda: t2)})
t6 = total(t4, t5)
assert cnt_1 == 0
assert t6.v.a == 12
assert cnt_1 == 1
assert t6.v.x == TreeValue({'c': 38, 'd': 51})
assert cnt_1 == 3
assert t6 == TreeValue({
'v': {'a': 12, 'b': 25, 'x': {'c': 38, 'd': 51}},
})
assert cnt_1 == 4
# keyword, with delay
cnt_1 = 0
t4 = TreeValue({'v': delayed(lambda: t1)})
t5 = TreeValue({'v': delayed(lambda: t2)})
t6 = total(a=t4, b=t5)
assert cnt_1 == 0
assert t6.v.a == 12
assert cnt_1 == 1
assert t6.v.x == TreeValue({'c': 38, 'd': 51})
assert cnt_1 == 3
assert t6 == TreeValue({
'v': {'a': 12, 'b': 25, 'x': {'c': 38, 'd': 51}},
})
assert cnt_1 == 4
......@@ -38,3 +38,46 @@ class TestTreeFuncOuter:
with pytest.raises(KeyError):
_ = ssum(t1, t3)
def test_delayed_treelize(self):
t1 = TreeValue({
'a': 1, 'x': {'c': 3, 'd': 4},
})
t2 = TreeValue({
'a': 11, 'b': 23, 'x': {'c': 35, },
})
cnt_1 = 0
@func_treelize(delayed=True, mode='outer', missing=0)
def total(a, b):
nonlocal cnt_1
cnt_1 += 1
return a + b
# positional
t3 = total(t1, t2)
assert cnt_1 == 0
assert t3.a == 12
assert cnt_1 == 1
assert t3.x == TreeValue({'c': 38, 'd': 4})
assert cnt_1 == 3
assert t3 == TreeValue({
'a': 12, 'b': 23, 'x': {'c': 38, 'd': 4}
})
assert cnt_1 == 4
# keyword
cnt_1 = 0
t3 = total(a=t1, b=t2)
assert cnt_1 == 0
assert t3.a == 12
assert cnt_1 == 1
assert t3.x == TreeValue({'c': 38, 'd': 4})
assert cnt_1 == 3
assert t3 == TreeValue({
'a': 12, 'b': 23, 'x': {'c': 38, 'd': 4}
})
assert cnt_1 == 4
......@@ -5,7 +5,7 @@ from typing import Type
import numpy as np
import pytest
from treevalue.tree import func_treelize, TreeValue, raw, mapping
from treevalue.tree import func_treelize, TreeValue, raw, mapping, delayed
def get_tree_test(tree_value_clazz: Type[TreeValue]):
......@@ -356,8 +356,38 @@ def get_tree_test(tree_value_clazz: Type[TreeValue]):
assert t2.add(t1) == tree_value_clazz({'a': 2, 'b': 4, 'x': {'c': 6, 'd': 8}})
def test_map(self):
cnt = 0
def f(x):
nonlocal cnt
cnt += 1
return x + 2
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}})
assert cnt == 0
t2 = t1.map(f)
assert cnt == 4
assert t2 == tree_value_clazz({'a': 3, 'b': 4, 'x': {'c': 5, 'd': 6}})
cnt = 0
t3 = tree_value_clazz({
'a': delayed(lambda: t1.a),
'b': delayed(lambda: t1.b),
'x': delayed(lambda: t1.x),
})
assert cnt == 0
t4 = t3.map(f, delayed=True)
assert cnt == 0
assert t4.a == 3
assert cnt == 1
assert t4 == tree_value_clazz({'a': 3, 'b': 4, 'x': {'c': 5, 'd': 6}})
assert cnt == 4
assert t4.a == 3
assert cnt == 4
def test_type(self):
t1 = tree_value_clazz({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
......@@ -572,4 +602,23 @@ def get_tree_test(tree_value_clazz: Type[TreeValue]):
assert ssum(t1, t2) == tree_value_clazz({'a': 12, 'b': 22, 'x': {'c': 36, 'd': 52}})
cnt_1 = 0
@tree_value_clazz.func(delayed=True)
def ssumx(x, y):
nonlocal cnt_1
cnt_1 += 1
return x + y
cnt_1 = 0
t3 = ssumx(t1, t2)
assert cnt_1 == 0
assert t3.a == 12
assert cnt_1 == 1
assert t3.x == tree_value_clazz({'c': 36, 'd': 52})
assert cnt_1 == 3
assert t3 == tree_value_clazz({'a': 12, 'b': 22, 'x': {'c': 36, 'd': 52}})
assert cnt_1 == 4
return _TestClass
import pytest
from treevalue.tree import TreeValue, raw, flatten, unflatten, flatten_values, flatten_keys
from treevalue.tree import TreeValue, raw, flatten, unflatten, flatten_values, flatten_keys, delayed
class MyTreeValue(TreeValue):
......@@ -22,15 +22,37 @@ class TestTreeTreeFlatten:
(('d', 'y'), 4)
]
t1 = TreeValue({
'a': delayed(lambda: t.a),
'b': delayed(lambda: t.b),
'c': delayed(lambda: t.c),
'd': delayed(lambda: t.d),
})
flatted = sorted(flatten(t1))
assert flatted == [
(('a',), 1),
(('b',), 2),
(('c',), {'x': 3, 'y': 4}),
(('d', 'x'), 3),
(('d', 'y'), 4)
]
def test_flatten_values(self):
t = TreeValue({'a': 1, 'b': 5, 'c': {'x': 3, 'y': 4}, 'd': {'x': 3, 'y': 4}})
flatted_values = sorted(flatten_values(t))
assert flatted_values == [1, 3, 3, 4, 4, 5]
t1 = TreeValue({
'a': delayed(lambda: t.a),
'b': delayed(lambda: t.b),
'c': delayed(lambda: t.c),
'd': delayed(lambda: t.d),
})
flatted_values = sorted(flatten_values(t1))
assert flatted_values == [1, 3, 3, 4, 4, 5]
def test_flatten_keys(self):
t = TreeValue({'a': 1, 'd': {'x': 3, 'y': 4}, 'e': raw({'x': 3, 'y': 4}), 'b': 5, 'c': {'x': 3, 'y': 4}})
flatted_keys = sorted(flatten_keys(t))
assert flatted_keys == [
('a',),
......@@ -42,6 +64,24 @@ class TestTreeTreeFlatten:
('e',),
]
t1 = TreeValue({
'a': delayed(lambda: t.a),
'b': delayed(lambda: t.b),
'c': delayed(lambda: t.c),
'd': delayed(lambda: t.d),
'e': delayed(lambda: t.e),
})
flatted_keys = sorted(flatten_keys(t1))
assert flatted_keys == [
('a',),
('b',),
('c', 'x',),
('c', 'y',),
('d', 'x',),
('d', 'y',),
('e',),
]
def test_unflatten(self):
flatted = [
(('a',), 1),
......
......@@ -3,7 +3,7 @@ from operator import __mul__
import pytest
from treevalue.tree import TreeValue, mapping, raw, mask, filter_, reduce_
from treevalue.tree import TreeValue, mapping, raw, mask, filter_, reduce_, delayed
# noinspection DuplicatedCode
......@@ -30,6 +30,59 @@ class TestTreeTreeFunctional:
})
assert tv6 == TreeValue({'a': 1.0, 'b': 2.0, 'c': {'x': 2.0, 'y': 3.0}})
tv8 = TreeValue({'v': delayed(lambda: tv1)})
assert mapping(tv8, lambda x: x + 2) == TreeValue({'v': {
'a': 3, 'b': 4, 'c': {'x': 4, 'y': 5}
}})
def test_mapping_delayed(self):
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}})
tv8 = TreeValue({'v': delayed(lambda: tv1)})
assert mapping(tv8, lambda x: x + 2, delayed=True) == TreeValue({'v': {
'a': 3, 'b': 4, 'c': {'x': 4, 'y': 5}
}})
cnt_f, cnt_v = 0, 0
def f(x):
nonlocal cnt_f
cnt_f += 1
return TreeValue({
'a': x * 2, 'b': x + 1, 'c': x ** 2,
})
def v():
nonlocal cnt_v
cnt_v += 1
return 3
t = TreeValue({
'a': 1, 'b': delayed(f, 1),
'x': {'c': delayed(v), 'd': 4, },
'y': delayed(f, 3),
})
t1 = mapping(t, lambda x: (x + 3) ** 2, delayed=True)
assert cnt_v == 0
assert cnt_f == 0
assert t1 == TreeValue({
'a': 16,
'b': {'a': 25, 'b': 25, 'c': 16, },
'x': {'c': 36, 'd': 49, },
'y': {'a': 81, 'b': 49, 'c': 144, },
})
assert cnt_v == 1
assert cnt_f == 2
assert t == TreeValue({
'a': 1,
'b': {'a': 2, 'b': 2, 'c': 1, },
'x': {'c': 3, 'd': 4, },
'y': {'a': 6, 'b': 4, 'c': 9, },
})
assert cnt_v == 1
assert cnt_f == 2
def test_mask(self):
class MyTreeValue(TreeValue):
pass
......@@ -46,6 +99,10 @@ class TestTreeTreeFunctional:
with pytest.raises(TypeError):
assert mask(t2, m2)
t1 = TreeValue({'v': delayed(lambda: t)})
m11 = TreeValue({'v': delayed(lambda: m1)})
assert mask(t1, m11) == TreeValue({'v': {'a': 1}})
def test_filter(self):
class MyTreeValue(TreeValue):
pass
......@@ -56,6 +113,9 @@ class TestTreeTreeFunctional:
assert filter_(t, lambda x: x < 3, remove_empty=False) == MyTreeValue({'a': 1, 'b': 2, 'x': {}})
assert filter_(t, lambda x: x % 2 == 1) == MyTreeValue({'a': 1, 'x': {'c': 3}})
t2 = TreeValue({'v': delayed(lambda: t)})
assert filter_(t2, lambda x: x < 3) == TreeValue({'v': {'a': 1, 'b': 2}})
def test_reduce(self):
class MyTreeValue(TreeValue):
pass
......@@ -74,3 +134,8 @@ class TestTreeTreeFunctional:
assert reduce_(t2, lambda **kwargs: TreeValue(
{k + k: (v ** 2 if not isinstance(v, TreeValue) else v) for k, v in kwargs.items()})) == MyTreeValue(
{'aa': 1, 'bb': 4, 'xx': {'cc': 9, 'dd': 16}})
t1 = MyTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t3 = TreeValue({'v': delayed(lambda: t1), 'v2': delayed(lambda: t1)})
assert reduce_(t3, lambda **kwargs: sum(kwargs.values())) == 20
assert reduce_(t3, lambda **kwargs: reduce(__mul__, list(kwargs.values()))) == 576
import pytest
from treevalue.tree import jsonify, TreeValue, clone, typetrans, raw, walk
from treevalue.tree import jsonify, TreeValue, clone, typetrans, raw, walk, delayed
# noinspection DuplicatedCode
......@@ -19,6 +19,18 @@ class TestTreeTreeService:
}
assert tv2.c == {'x': 2, 'y': 3}
tv3 = TreeValue({
'a': delayed(lambda: tv1.a),
'b': delayed(lambda: tv1.b),
'c1': delayed(lambda: tv1.c),
'c2': delayed(lambda: tv2.c),
})
assert jsonify(tv3) == {
'a': 1, 'b': 2, 'c1': {'x': 2, 'y': 3}, 'c2': {'x': 2, 'y': 3}
}
assert tv3.c1 == TreeValue({'x': 2, 'y': 3})
assert tv3.c2 == {'x': 2, 'y': 3}
def test_clone(self):
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}})
tv2 = clone(tv1)
......@@ -63,6 +75,14 @@ class TestTreeTreeService:
assert tv5.x.c is not tv3.x.c
assert tv5.x.d is not tv3.x.d
tv6 = TreeValue({
'a': delayed(lambda: tv3.a),
'b': delayed(lambda: tv3.b),
'x': delayed(lambda: tv3.x),
})
tv7 = clone(tv6, lambda x: x)
assert tv7 == tv3
def test_typetrans(self):
class MyTreeValue(TreeValue):
pass
......@@ -99,3 +119,15 @@ class TestTreeTreeService:
('c', 'x',): 2,
('c', 'y',): 3,
}
tv2 = MyTreeValue({
'a': delayed(lambda: tv1.a),
'b': delayed(lambda: tv1.b),
'c': delayed(lambda: tv1.c),
})
assert dict(walk(tv2)) == {
('a',): 1,
('b',): 2,
('c', 'x',): 2,
('c', 'y',): 3,
}
import pytest
from treevalue.tree import TreeValue, mapping, union, raw, subside, rise
from treevalue.tree import TreeValue, mapping, union, raw, subside, rise, delayed
# noinspection DuplicatedCode
......@@ -16,10 +16,37 @@ class TestTreeTreeStructural:
t1 = MyTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
assert union(t, t1) == TreeValue({'a': (1, 1), 'b': (2, 2), 'x': {'c': (3, 3), 'd': (4, 4)}})
assert union(t, t1, return_type=MyTreeValue) == MyTreeValue(
{'a': (1, 1), 'b': (2, 2), 'x': {'c': (3, 3), 'd': (4, 4)}})
assert union(t1, t) == MyTreeValue({'a': (1, 1), 'b': (2, 2), 'x': {'c': (3, 3), 'd': (4, 4)}})
assert union(1, 2) == (1, 2)
assert union(1, 2, return_type=TreeValue) == (1, 2)
tp = MyTreeValue({'v': delayed(lambda: t)})
tp1 = TreeValue({'v': delayed(lambda: t1)})
assert union(tp, tp1) == MyTreeValue({'v': {'a': (1, 1), 'b': (2, 2), 'x': {'c': (3, 3), 'd': (4, 4)}}})
assert union(tp1, tp) == TreeValue({'v': {'a': (1, 1), 'b': (2, 2), 'x': {'c': (3, 3), 'd': (4, 4)}}})
t = MyTreeValue({'a': 1, 'b': 2, 'x': {'c': 3}})
t1 = TreeValue({
'a': delayed(lambda: t.x.c),
'x': {
'c': delayed(lambda: t.a),
'd': delayed(lambda: t.b),
}
})
assert union(t, t1, mode='outer', missing=None) == MyTreeValue({
'a': (1, 3), 'b': (2, None), 'x': {'c': (3, 1), 'd': (None, 2)},
})
def test_union_delayed(self):
class MyTreeValue(TreeValue):
pass
t = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t1 = MyTreeValue({'a': 11, 'b': 22, 'x': {'c': 33, 'd': 44}})
assert union(t, t1, delayed=True) == TreeValue({'a': (1, 11), 'b': (2, 22), 'x': {'c': (3, 33), 'd': (4, 44)}})
def test_subside(self):
assert subside({'a': (1, 2), 'b': [3, 4]}) == {'a': (1, 2), 'b': [3, 4]}
assert subside({'a': (1, 2), 'b': [3, 4]}, return_type=TreeValue) == {'a': (1, 2), 'b': [3, 4]}
......@@ -88,6 +115,30 @@ class TestTreeTreeStructural:
assert subside({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}, 'e': [3, 4, 5]}) == \
{'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}, 'e': [3, 4, 5]}
def test_subside_delayed(self):
class MyTreeValue(TreeValue):
pass
original2 = {
'a': TreeValue({'a': 1, 'b': 2}),
'x': {
'c': MyTreeValue({'a': 3, 'b': 4}),
'd': [
MyTreeValue({'a': 5, 'b': 6}),
MyTreeValue({'a': 7, 'b': 8}),
]
},
'k': '233'
}
assert subside(original2, delayed=True) == 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]}}),
})
assert subside(original2, return_type=MyTreeValue, delayed=True) == MyTreeValue({
'a': raw({'a': 1, 'k': '233', 'x': {'c': 3, 'd': [5, 7]}}),
'b': raw({'a': 2, 'k': '233', 'x': {'c': 4, 'd': [6, 8]}}),
})
def test_rise(self):
t1 = TreeValue({'x': raw({'a': [1, 2], 'b': [2, 3]}), 'y': raw({'a': [5, 6, 7], 'b': [7, 8]})})
assert rise(t1) == {
......@@ -203,3 +254,21 @@ class TestTreeTreeStructural:
rise(t5, template=[object, object, object, object, object, ...])
assert rise(1) == 1
t1 = TreeValue({'x': raw({'a': [1, 2], 'b': [2, 3]}), 'y': raw({'a': [5, 6, 7], 'b': [7, 8]})})
assert rise(t1) == {
'a': TreeValue({'x': [1, 2], 'y': [5, 6, 7]}),
'b': [
TreeValue({'x': 2, 'y': 7}),
TreeValue({'x': 3, 'y': 8}),
]
}
t10 = MyTreeValue({'v': delayed(lambda: t1)})
assert rise(t10) == {
'a': MyTreeValue({'v': {'x': [1, 2], 'y': [5, 6, 7]}}),
'b': [
MyTreeValue({'v': {'x': 2, 'y': 7}}),
MyTreeValue({'v': {'x': 3, 'y': 8}}),
]
}
......@@ -3,7 +3,7 @@ import re
import pytest
from treevalue import raw, TreeValue
from treevalue import raw, TreeValue, delayed
class _Container:
......@@ -121,6 +121,20 @@ class TestTreeTreeTree:
assert "c --> <TreeValue" in repr(tv2)
assert "(The same address as <root>)" in repr(tv2)
tv3 = TreeValue({
'a': delayed(lambda: tv1.a),
'b': delayed(lambda: tv1.b),
'c': delayed(lambda: tv1.c),
})
assert re.match(r"<TreeValue 0x[0-9a-f]+>", repr(tv3))
assert re.match(r"<TreeValue 0x[0-9a-f]+>", repr(tv3.c))
assert "a --> 1" in str(tv3)
assert "b --> 2" in str(tv3)
assert "x --> 2" in str(tv3)
assert "y --> 3" in str(tv3)
assert "c --> <TreeValue" in str(tv3)
def test_tree_value_iter(self):
# Attention: dict(tv1) is not supported in python 3.7+
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}})
......
from .base import raw, unraw, RawWrapper
from .delay import DelayedProxy, delayed_partial, undelay, DelayedValueProxy, DelayedFuncProxy
from .storage import TreeStorage, create_storage
......@@ -39,7 +39,7 @@ cdef class RawWrapper:
self.val = state
@cython.binding(True)
cpdef public object raw(object obj):
cpdef inline object raw(object obj):
"""
Overview:
Try wrap the given ``obj`` to raw wrapper.
......@@ -57,7 +57,7 @@ cpdef public object raw(object obj):
return obj
@cython.binding(True)
cpdef public object unraw(object wrapped):
cpdef inline object unraw(object wrapped):
"""
Overview:
Try unwrap the given ``wrapped`` to original object.
......
# distutils:language=c++
# cython:language_level=3
from libcpp cimport bool
cdef class DelayedProxy:
cpdef object value(self)
cpdef object fvalue(self)
cdef class DelayedValueProxy(DelayedProxy):
cdef readonly object func
cdef readonly bool calculated
cdef object val
cpdef object value(self)
cdef class DelayedFuncProxy(DelayedProxy):
cdef readonly object func
cdef readonly tuple args
cdef readonly dict kwargs
cdef readonly bool calculated
cdef object val
cpdef object value(self)
cdef DelayedProxy _c_delayed_partial(func, args, kwargs)
cpdef object undelay(object p, bool is_final= *)
# distutils:language=c++
# cython:language_level=3
import cython
from libcpp cimport bool
cdef class DelayedProxy:
"""
Overview:
Base class of all the delayed proxy class.
"""
cpdef object value(self):
r"""
Overview:
Get value of the delayed proxy.
Should make sure the result is cached.
Can be accessed in :func:`treevalue.tree.common.undelay` when ``is_final`` is ``False``.
Returns:
- value (:obj:`object`): Calculation result.
"""
raise NotImplementedError # pragma: no cover
cpdef object fvalue(self):
r"""
Overview:
Get value of the delayed proxy.
Can be accessed in :func:`treevalue.tree.common.undelay` when ``is_final`` is ``True``.
Returns:
- value (:obj:`object`): Calculation result.
"""
return self.value()
cdef class DelayedValueProxy(DelayedProxy):
"""
Overview:
Simple function delayed proxy.
"""
def __cinit__(self, object func):
"""
Overview:
Constructor of class :class:`treevalue.tree.common.DelayedValueProxy`.
Arguments:
- func (:obj:`object`): Function to be called, which can be called without arguments. \
Delayed proxy is supported.
"""
self.func = func
self.calculated = False
self.val = None
cpdef object value(self):
cdef object f
if not self.calculated:
f = undelay(self.func, False)
self.val = f()
self.calculated = True
return self.val
cdef class DelayedFuncProxy(DelayedProxy):
"""
Overview:
Simple function delayed proxy.
"""
def __cinit__(self, object func, tuple args, dict kwargs):
"""
Overview:
Constructor of class :class:`treevalue.tree.common.DelayedFuncProxy`.
Arguments:
- func (:obj:`object`): Function to be called, which can be called with given arguments. \
Delayed proxy is supported.
- args (:obj:`tuple`): Positional arguments to be used, delayed proxy is supported.
- kwargs (:obj:`dict`): Key-word arguments to be used, delayed proxy is supported.
"""
self.func = func
self.args = args
self.kwargs = kwargs
self.calculated = False
self.val = None
cpdef object value(self):
cdef list pas
cdef dict pks
cdef str key
cdef object item
cdef object f
if not self.calculated:
pas = []
pks = {}
f = undelay(self.func, False)
for item in self.args:
pas.append(undelay(item, False))
for key, item in self.kwargs.items():
pks[key] = undelay(item, False)
self.val = f(*pas, **pks)
self.calculated = True
return self.val
cdef inline DelayedProxy _c_delayed_partial(func, args, kwargs):
if args or kwargs:
return DelayedFuncProxy(func, args, kwargs)
else:
return DelayedValueProxy(func)
@cython.binding(True)
def delayed_partial(func, *args, **kwargs):
"""
Overview:
Build a delayed partial object.
Similar to :func:`functools.partial`.
Returns:
- delayed: Delayed object.
"""
return _c_delayed_partial(func, args, kwargs)
@cython.binding(True)
cpdef inline object undelay(object p, bool is_final=True):
r"""
Overview:
Get the value of a given object, it can be a delayed proxy, a simple object or \
a nested delayed proxy.
Arguments:
- p (:obj:`object`): Given object to be undelay.
- is_final (:obj:`bool`): Is final value getting or not, default is ``True``.
Returns:
- value (:obj:`object): Actual value of the given ``p``.
"""
if isinstance(p, DelayedProxy):
if is_final:
return p.fvalue()
else:
return p.value()
else:
return p
# distutils:language=c++
# cython:language_level=3
from libcpp cimport bool
ctypedef unsigned char boolean
ctypedef unsigned int uint
cdef void _key_validate(const char*key) except *
cdef void _key_validate(const char *key) except *
cdef class TreeStorage:
cdef readonly dict map
......@@ -19,13 +21,16 @@ cdef class TreeStorage:
cpdef public dict dump(self)
cpdef public dict deepdump(self)
cpdef public dict deepdumpx(self, copy_func)
cpdef public dict jsondumpx(self, copy_func, object need_raw)
cpdef public dict jsondumpx(self, copy_func, bool need_raw, bool allow_delayed)
cpdef public TreeStorage copy(self)
cpdef public TreeStorage deepcopy(self)
cpdef public TreeStorage deepcopyx(self, copy_func)
cpdef public TreeStorage deepcopyx(self, copy_func, bool allow_delayed)
cpdef public dict detach(self)
cpdef public void copy_from(self, TreeStorage ts)
cpdef public void deepcopy_from(self, TreeStorage ts)
cpdef public void deepcopyx_from(self, TreeStorage ts, copy_func)
cpdef public void deepcopyx_from(self, TreeStorage ts, copy_func, bool allow_delayed)
cpdef public object create_storage(dict value)
cdef object _c_undelay_data(dict data, object k, object v)
cdef object _c_undelay_not_none_data(dict data, object k, object v)
cdef object _c_undelay_check_data(dict data, object k, object v)
......@@ -4,13 +4,15 @@
from copy import deepcopy
from libc.string cimport strlen
from libcpp cimport bool
from .base cimport raw, unraw
from .delay cimport undelay
cdef inline object _keep_object(object obj):
return obj
cdef inline void _key_validate(const char*key) except *:
cdef inline void _key_validate(const char *key) except *:
cdef int n = strlen(key)
if n < 1:
raise KeyError(f'Key {repr(key)} is too short, minimum length is 1 but {n} found.')
......@@ -35,13 +37,17 @@ cdef class TreeStorage:
self.map[key] = value
cpdef public object get(self, str key):
cdef object v, nv
try:
return self.map[key]
v = self.map[key]
return _c_undelay_data(self.map, key, v)
except KeyError:
raise KeyError(f"Key {repr(key)} not found in this tree.")
cpdef public object get_or_default(self, str key, object default):
return self.map.get(key, default)
cdef object v, nv
v = self.map.get(key, default)
return _c_undelay_check_data(self.map, key, v)
cpdef public void del_(self, str key) except *:
try:
......@@ -65,17 +71,20 @@ cdef class TreeStorage:
return self.deepdumpx(deepcopy)
cpdef public dict deepdumpx(self, copy_func):
return self.jsondumpx(copy_func, True)
return self.jsondumpx(copy_func, True, False)
cpdef public dict jsondumpx(self, copy_func, object need_raw):
cpdef public dict jsondumpx(self, copy_func, bool need_raw, bool allow_delayed):
cdef dict result = {}
cdef str k
cdef object v, obj
cdef object v, obj, nv
for k, v in self.map.items():
if not allow_delayed:
v = _c_undelay_data(self.map, k, v)
if isinstance(v, TreeStorage):
result[k] = v.jsondumpx(copy_func, need_raw)
result[k] = v.jsondumpx(copy_func, need_raw, allow_delayed)
else:
obj = copy_func(v)
obj = copy_func(v) if not allow_delayed else v
if need_raw:
obj = raw(obj)
result[k] = obj
......@@ -83,43 +92,48 @@ cdef class TreeStorage:
return result
cpdef public TreeStorage copy(self):
return self.deepcopyx(_keep_object)
return self.deepcopyx(_keep_object, True)
cpdef public TreeStorage deepcopy(self):
return self.deepcopyx(deepcopy)
return self.deepcopyx(deepcopy, False)
cpdef public TreeStorage deepcopyx(self, copy_func):
cdef type cls = type(self)
return create_storage(self.deepdumpx(copy_func))
cpdef public TreeStorage deepcopyx(self, copy_func, bool allow_delayed):
return create_storage(self.jsondumpx(copy_func, True, allow_delayed))
cpdef public dict detach(self):
return self.map
cpdef public void copy_from(self, TreeStorage ts):
self.deepcopyx_from(ts, _keep_object)
self.deepcopyx_from(ts, _keep_object, True)
cpdef public void deepcopy_from(self, TreeStorage ts):
self.deepcopyx_from(ts, deepcopy)
self.deepcopyx_from(ts, deepcopy, False)
cpdef public void deepcopyx_from(self, TreeStorage ts, copy_func):
cpdef public void deepcopyx_from(self, TreeStorage ts, copy_func, bool allow_delayed):
cdef dict detached = ts.detach()
cdef set keys = set(self.map.keys()) | set(detached.keys())
cdef str k
cdef object
cdef object v, nv
cdef TreeStorage newv
for k in keys:
if k in detached:
v = detached[k]
if not allow_delayed:
v = _c_undelay_data(detached, k, v)
if isinstance(v, TreeStorage):
if k in self.map and isinstance(self.map[k], TreeStorage):
self.map[k].copy_from(v)
self.map[k].deepcopyx_from(v, copy_func, allow_delayed)
else:
newv = TreeStorage({})
newv.copy_from(v)
newv.deepcopyx_from(v, copy_func, allow_delayed)
self.map[k] = newv
else:
self.map[k] = copy_func(v)
if not allow_delayed:
self.map[k] = copy_func(v)
else:
self.map[k] = v
else:
del self.map[k]
......@@ -145,12 +159,16 @@ cdef class TreeStorage:
cdef list other_keys = sorted(other.detach().keys())
cdef str key
cdef object self_v
cdef object other_v
cdef object self_v, self_nv
cdef object other_v, other_nv
if self_keys == other_keys:
for key in self_keys:
self_v = self.map[key]
self_v = _c_undelay_data(self.map, key, self_v)
other_v = other_map[key]
other_v = _c_undelay_data(other_map, key, other_v)
if self_v != other_v:
return False
return True
......@@ -161,7 +179,7 @@ cdef class TreeStorage:
cdef str k
cdef object v
cdef list _items = []
for k, v in sorted(self.map.items(), key=lambda x: x[0]):
for k, v in sorted(self.items(), key=lambda x: x[0]):
_items.append((k, v))
return hash(tuple(_items))
......@@ -170,10 +188,18 @@ cdef class TreeStorage:
return self.map.keys()
def values(self):
return self.map.values()
cdef str k
cdef object v, nv
for k, v in self.map.items():
yield _c_undelay_data(self.map, k, v)
def items(self):
return self.map.items()
cdef str k
cdef object v, nv
for k, v in self.map.items():
v = _c_undelay_data(self.map, k, v)
yield k, v
cpdef object create_storage(dict value):
cdef dict _map = {}
......@@ -187,3 +213,21 @@ cpdef object create_storage(dict value):
_map[k] = unraw(v)
return TreeStorage(_map)
cdef inline object _c_undelay_data(dict data, object k, object v):
cdef object nv = undelay(v)
if nv is not v:
data[k] = nv
return nv
cdef inline object _c_undelay_not_none_data(dict data, object k, object v):
cdef object nv = undelay(v)
if nv is not v and k is not None:
data[k] = nv
return nv
cdef inline object _c_undelay_check_data(dict data, object k, object v):
cdef object nv = undelay(v)
if nv is not v and k in data:
data[k] = nv
return nv
\ No newline at end of file
......@@ -5,11 +5,14 @@ from libcpp cimport bool
from .modes cimport _e_tree_mode
cdef object _c_func_treelize_run(object func, list args, dict kwargs,
_e_tree_mode mode, bool inherit, bool allow_missing, object missing_func)
cdef object _c_wrap_func_treelize_run(object func, list args, dict kwargs, _e_tree_mode mode, bool inherit,
bool allow_missing, object missing_func, bool delayed)
cdef object _c_func_treelize_run(object func, list args, dict kwargs, _e_tree_mode mode, bool inherit,
bool allow_missing, object missing_func, bool delayed)
cpdef object _d_func_treelize(object func, object mode, object return_type, bool inherit, object missing,
object subside, object rise)
cdef _c_common_value(object item)
bool delayed, object subside, object rise)
cdef object _c_common_value(object item)
cdef tuple _c_missing_process(object missing)
cpdef object func_treelize(object mode= *, object return_type= *, bool inherit= *, object missing= *,
object subside= *, object rise= *)
bool delayed= *, object subside= *, object rise= *)
......@@ -8,20 +8,40 @@ from hbutils.design import SingletonMark
from libcpp cimport bool
from .modes cimport _e_tree_mode, _c_keyset, _c_load_mode, _c_check
from ..common.storage cimport TreeStorage
from ..common.delay import delayed_partial
from ..common.delay cimport undelay
from ..common.storage cimport TreeStorage, _c_undelay_not_none_data, _c_undelay_data
from ..tree.structural cimport _c_subside, _c_rise
from ..tree.tree cimport TreeValue
_VALUE_IS_MISSING = SingletonMark('value_is_missing')
MISSING_NOT_ALLOW = SingletonMark("missing_not_allow")
cdef inline object _c_wrap_func_treelize_run(object func, list args, dict kwargs, _e_tree_mode mode, bool inherit,
bool allow_missing, object missing_func, bool delayed):
cdef list _l_args = []
cdef dict _d_kwargs = {}
cdef str k, ak
cdef object av, v, nv
for av, k, v in args:
_l_args.append(_c_undelay_not_none_data(av, k, v))
for ak, (av, k, v) in kwargs.items():
_d_kwargs[ak] = _c_undelay_not_none_data(av, k, v)
return _c_func_treelize_run(func, _l_args, _d_kwargs,
mode, inherit, allow_missing, missing_func, delayed)
cdef object _c_func_treelize_run(object func, list args, dict kwargs,
_e_tree_mode mode, bool inherit, bool allow_missing, object missing_func):
cdef object _c_func_treelize_run(object func, list args, dict kwargs, _e_tree_mode mode, bool inherit,
bool allow_missing, object missing_func, bool delayed):
cdef list ck_args = []
cdef list ck_kwargs = []
cdef bool has_tree = False
cdef str k
cdef object v
cdef object v, nv
for v in args:
if isinstance(v, TreeStorage):
ck_args.append((v.detach(), True))
......@@ -66,17 +86,28 @@ cdef object _c_func_treelize_run(object func, list args, dict kwargs,
for i, (av, at) in enumerate(ck_args):
if at:
try:
_l_args.append(av[k])
v = av[k]
if delayed:
_l_args.append((av, k, v))
else:
v = _c_undelay_data(av, k, v)
_l_args.append(v)
except KeyError:
if allow_missing:
_l_args.append(_VALUE_IS_MISSING)
if delayed:
_l_args.append((None, None, _VALUE_IS_MISSING))
else:
_l_args.append(_VALUE_IS_MISSING)
else:
raise KeyError("Missing is off, key {key} not found in {item}.".format(
key=repr(k), item=repr(av),
))
else:
if inherit:
_l_args.append(av)
if delayed:
_l_args.append((None, None, av))
else:
_l_args.append(undelay(av))
else:
raise TypeError("Inherit is off, tree value expected but {type} found in args {index}.".format(
type=repr(type(av).__name__), index=repr(i),
......@@ -86,29 +117,45 @@ cdef object _c_func_treelize_run(object func, list args, dict kwargs,
for ak, av, at in ck_kwargs:
if at:
try:
_d_kwargs[ak] = av[k]
v = av[k]
if delayed:
_d_kwargs[ak] = (av, k, v)
else:
v = _c_undelay_data(av, k, v)
_d_kwargs[ak] = v
except KeyError:
if allow_missing:
_d_kwargs[ak] = _VALUE_IS_MISSING
if delayed:
_d_kwargs[ak] = (None, None, _VALUE_IS_MISSING)
else:
_d_kwargs[ak] = _VALUE_IS_MISSING
else:
raise KeyError("Missing is off, key {key} not found in {item}.".format(
key=repr(k), item=repr(av),
))
else:
if inherit:
_d_kwargs[ak] = av
if delayed:
_d_kwargs[ak] = (None, None, av)
else:
_d_kwargs[ak] = undelay(av)
else:
raise TypeError("Inherit is off, tree value expected but {type} found in args {index}.".format(
type=repr(type(av).__name__), index=repr(ak),
))
_d_res[k] = _c_func_treelize_run(func, _l_args, _d_kwargs,
mode, inherit, allow_missing, missing_func)
if delayed:
_d_res[k] = delayed_partial(_c_wrap_func_treelize_run, func, _l_args, _d_kwargs,
mode, inherit, allow_missing, missing_func, delayed)
else:
_d_res[k] = _c_func_treelize_run(func, _l_args, _d_kwargs,
mode, inherit, allow_missing, missing_func, delayed)
return TreeStorage(_d_res)
def _w_subside_func(object value, bool dict_=True, bool list_=True, bool tuple_=True, bool inherit=True):
return _c_subside(value, dict_, list_, tuple_, inherit)[0]
def _w_subside_func(object value, bool dict_=True, bool list_=True, bool tuple_=True, bool inherit=True,
object mode='strict', object missing=MISSING_NOT_ALLOW, bool delayed=False):
return _c_subside(value, dict_, list_, tuple_, inherit, mode, missing, delayed)[0]
def _w_rise_func(object tree, bool dict_=True, bool list_=True, bool tuple_=True, object template=None):
return _c_rise(tree, dict_, list_, tuple_, template)
......@@ -116,16 +163,18 @@ def _w_rise_func(object tree, bool dict_=True, bool list_=True, bool tuple_=True
# runtime function
def _w_func_treelize_run(*args, object __w_func, _e_tree_mode __w_mode, object __w_return_type,
bool __w_inherit, bool __w_allow_missing, object __w_missing_func,
object __w_subside, object __w_rise, **kwargs):
bool __w_delayed, object __w_subside, object __w_rise, **kwargs):
cdef list _a_args = [(item._detach() if isinstance(item, TreeValue) else item) for item in args]
cdef dict _a_kwargs = {k: (v._detach() if isinstance(v, TreeValue) else v) for k, v in kwargs.items()}
cdef dict _w_subside_cfg
if __w_subside is not None:
_a_args = [_w_subside_func(item, **__w_subside) for item in _a_args]
_a_kwargs = {key: _w_subside_func(value, **__w_subside) for key, value in _a_kwargs.items()}
_w_subside_cfg = {'delayed': __w_delayed, **__w_subside}
_a_args = [_w_subside_func(item, **_w_subside_cfg) for item in _a_args]
_a_kwargs = {key: _w_subside_func(value, **_w_subside_cfg) for key, value in _a_kwargs.items()}
cdef object _st_res = _c_func_treelize_run(__w_func, _a_args, _a_kwargs, __w_mode,
__w_inherit, __w_allow_missing, __w_missing_func)
__w_inherit, __w_allow_missing, __w_missing_func, __w_delayed)
cdef object _o_res
if __w_return_type is not None:
......@@ -144,15 +193,10 @@ def _w_func_treelize_run(*args, object __w_func, _e_tree_mode __w_mode, object _
else:
return None
MISSING_NOT_ALLOW = SingletonMark("missing_not_allow")
cdef _c_common_value(object item):
cdef object _c_common_value(object item):
return item
# build-time function
cpdef object _d_func_treelize(object func, object mode, object return_type, bool inherit, object missing,
object subside, object rise):
cdef _e_tree_mode _v_mode = _c_load_mode(mode)
cdef inline tuple _c_missing_process(object missing):
cdef bool allow_missing
cdef object missing_func
if missing is MISSING_NOT_ALLOW:
......@@ -162,6 +206,16 @@ cpdef object _d_func_treelize(object func, object mode, object return_type, bool
allow_missing = True
missing_func = missing if callable(missing) else partial(_c_common_value, missing)
return allow_missing, missing_func
# build-time function
cpdef object _d_func_treelize(object func, object mode, object return_type, bool inherit, object missing,
bool delayed, object subside, object rise):
cdef _e_tree_mode _v_mode = _c_load_mode(mode)
cdef bool allow_missing
cdef object missing_func
allow_missing, missing_func = _c_missing_process(missing)
cdef object _v_subside, _v_rise
if subside is not None and not isinstance(subside, dict):
_v_subside = {} if subside else None
......@@ -175,12 +229,12 @@ cpdef object _d_func_treelize(object func, object mode, object return_type, bool
_c_check(_v_mode, return_type, inherit, allow_missing, missing_func)
return partial(_w_func_treelize_run, __w_func=func, __w_mode=_v_mode, __w_return_type=return_type,
__w_inherit=inherit, __w_allow_missing=allow_missing, __w_missing_func=missing_func,
__w_subside=_v_subside, __w_rise=_v_rise)
__w_delayed=delayed, __w_subside=_v_subside, __w_rise=_v_rise)
@cython.binding(True)
cpdef object func_treelize(object mode='strict', object return_type=TreeValue,
bool inherit=True, object missing=MISSING_NOT_ALLOW,
object subside=None, object rise=None):
bool delayed=False, object subside=None, object rise=None):
"""
Overview:
Wrap a common function to tree-supported function.
......@@ -191,6 +245,8 @@ cpdef object func_treelize(object mode='strict', object return_type=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.
- delayed (:obj:`bool`): Enable delayed mode or not, the calculation will be delayed when enabled, \
default is ``False``, which means to all the calculation at once.
- 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.
......@@ -213,4 +269,4 @@ cpdef object func_treelize(object mode='strict', object return_type=TreeValue,
>>> ssum(t1, t2) # TreeValue({'a': 12, 'b': 24, 'x': {'c': 36, 'd': 9}})
"""
return partial(_d_func_treelize, mode=mode, return_type=return_type,
inherit=inherit, missing=missing, subside=subside, rise=rise)
inherit=inherit, missing=missing, delayed=delayed, subside=subside, rise=rise)
......@@ -12,7 +12,7 @@ TreeClassType_ = TypeVar("TreeClassType_", bound=TreeValue)
def func_treelize(mode: str = 'strict', return_type: Optional[Type[TreeClassType_]] = TreeValue,
inherit: bool = True, missing: Union[Any, Callable] = MISSING_NOT_ALLOW,
inherit: bool = True, missing: Union[Any, Callable] = MISSING_NOT_ALLOW, delayed: bool = False,
subside: Union[Mapping, bool, None] = None, rise: Union[Mapping, bool, None] = None):
"""
Overview:
......@@ -21,9 +21,11 @@ def func_treelize(mode: str = 'strict', return_type: Optional[Type[TreeClassType
Arguments:
- mode (:obj:`str`): Mode of the wrapping, default is `strict`.
- 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`.
- inherit (:obj:`bool`): Allow inheriting 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.
- delayed (:obj:`bool`): Enable delayed mode or not, the calculation will be delayed when enabled, \
default is ``False``, which means to all the calculation at once.
- 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.
......@@ -47,7 +49,7 @@ def func_treelize(mode: str = 'strict', return_type: Optional[Type[TreeClassType
"""
def _decorator(func):
_treelized = _c_func_treelize(mode, return_type, inherit, missing, subside, rise)(func)
_treelized = _c_func_treelize(mode, return_type, inherit, missing, delayed, subside, rise)(func)
@wraps(func)
def _new_func(*args, **kwargs):
......@@ -82,7 +84,7 @@ def method_treelize(mode: str = 'strict', return_type: Optional[Type[TreeClassTy
- mode (:obj:`str`): Mode of the wrapping, default is `strict`.
- 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`.
- inherit (:obj:`bool`): Allow inheriting 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, \
......@@ -154,7 +156,7 @@ def classmethod_treelize(mode: str = 'strict', return_type: Optional[Type[TreeCl
- mode (:obj:`str`): Mode of the wrapping, default is `strict`.
- 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`.
- inherit (:obj:`bool`): Allow inheriting 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, \
......
......@@ -147,13 +147,14 @@ def general_tree_value(base: Optional[Mapping[str, Any]] = None,
return typetrans(self, clazz)
@_decorate_method
def map(self, mapper):
def map(self, mapper, delayed=False):
"""
Overview:
Do mapping on every value in this tree.
Arguments:
- func (:obj:): Function for mapping
- delayed (:obj:`bool`): Enable delayed mode for this mapping.
Returns:
- tree (:obj:`_TreeValue`): Mapped tree value object.
......@@ -164,7 +165,7 @@ def general_tree_value(base: Optional[Mapping[str, Any]] = None,
>>> t.map(lambda: 1) # FastTreeValue({'a': 1, 'b': 1, 'x': {'c': 1, 'd': 1}})
>>> t.map(lambda x, p: p) # FastTreeValue({'a': ('a',), 'b': ('b',), 'x': {'c': ('x', 'c'), 'd': ('x', 'd')}})
"""
return mapping(self, mapper)
return mapping(self, mapper, delayed)
@_decorate_method
def mask(self, mask_: TreeValue, remove_empty: bool = True):
......@@ -308,9 +309,8 @@ def general_tree_value(base: Optional[Mapping[str, Any]] = None,
@classmethod
@_decorate_method
def func(cls, mode: str = 'strict', inherit: bool = True,
missing: Union[Any, Callable] = MISSING_NOT_ALLOW,
subside: Union[Mapping, bool, None] = None,
rise: Union[Mapping, bool, None] = None):
missing: Union[Any, Callable] = MISSING_NOT_ALLOW, delayed: bool = False,
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.
......@@ -320,6 +320,8 @@ def general_tree_value(base: Optional[Mapping[str, Any]] = None,
- 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.
- delayed (:obj:`bool`): Enable delayed mode or not, the calculation will be delayed when enabled, \
default is ``False``, which means to all the calculation at once.
- 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.
......@@ -343,7 +345,7 @@ def general_tree_value(base: Optional[Mapping[str, Any]] = None,
>>> 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)
return func_treelize(mode, cls, inherit, missing, delayed, subside, rise)
@classmethod
@_decorate_method
......
......@@ -4,4 +4,4 @@ from .graph import graphics
from .io import loads, load, dumps, dump
from .service import jsonify, clone, typetrans, walk
from .structural import subside, union, rise
from .tree import TreeValue
from .tree import TreeValue, delayed
......@@ -6,15 +6,16 @@
import cython
from .tree cimport TreeValue
from ..common.storage cimport TreeStorage
from ..common.storage cimport TreeStorage, _c_undelay_data
cdef void _c_flatten(TreeStorage st, tuple path, list res) except *:
cdef dict data = st.detach()
cdef tuple curpath
cdef str k
cdef object v
cdef object v, nv
for k, v in data.items():
v = _c_undelay_data(data, k, v)
curpath = path + (k,)
if isinstance(v, TreeStorage):
_c_flatten(v, curpath, res)
......@@ -47,8 +48,9 @@ cdef void _c_flatten_values(TreeStorage st, list res) except *:
cdef dict data = st.detach()
cdef str k
cdef object v
cdef object v, nv
for k, v in data.items():
v = _c_undelay_data(data, k, v)
if isinstance(v, TreeStorage):
_c_flatten_values(v, res)
else:
......@@ -75,8 +77,9 @@ cdef void _c_flatten_keys(TreeStorage st, tuple path, list res) except *:
cdef tuple curpath
cdef str k
cdef object v
cdef object v, nv
for k, v in data.items():
v = _c_undelay_data(data, k, v)
curpath = path + (k,)
if isinstance(v, TreeStorage):
_c_flatten_keys(v, curpath, res)
......
......@@ -12,8 +12,9 @@ cdef object _c_no_arg(object func, object v, object p)
cdef object _c_one_arg(object func, object v, object p)
cdef object _c_two_args(object func, object v, object p)
cdef object _c_wrap_mapping_func(object func)
cdef TreeStorage _c_mapping(TreeStorage st, object func, tuple path)
cpdef TreeValue mapping(TreeValue tree, object func)
cdef object _c_delayed_mapping(object so, object func, tuple path, bool delayed)
cdef TreeStorage _c_mapping(TreeStorage st, object func, tuple path, bool delayed)
cpdef TreeValue mapping(TreeValue tree, object func, bool delayed= *)
cdef TreeStorage _c_filter_(TreeStorage st, object func, tuple path, bool remove_empty)
cpdef TreeValue filter_(TreeValue tree, object func, bool remove_empty= *)
cdef object _c_mask(TreeStorage st, object sm, tuple path, bool remove_empty)
......
......@@ -8,7 +8,9 @@ from functools import partial
from libcpp cimport bool
from .tree cimport TreeValue
from ..common.storage cimport TreeStorage
from ..common.delay cimport undelay
from ..common.delay import delayed_partial
from ..common.storage cimport TreeStorage, _c_undelay_data
cdef inline object _c_no_arg(object func, object v, object p):
return func()
......@@ -33,24 +35,40 @@ cdef inline object _c_wrap_mapping_func(object func):
else:
return partial(_c_no_arg, func)
cdef TreeStorage _c_mapping(TreeStorage st, object func, tuple path):
cdef object _c_delayed_mapping(object so, object func, tuple path, bool delayed):
cdef object nso = undelay(so)
if isinstance(nso, TreeValue):
nso = nso._detach()
if isinstance(nso, TreeStorage):
return _c_mapping(nso, func, path, delayed)
else:
return func(nso, path)
cdef TreeStorage _c_mapping(TreeStorage st, object func, tuple path, bool delayed):
cdef dict _d_st = st.detach()
cdef dict _d_res = {}
cdef str k
cdef object v
cdef object v, nv
cdef tuple curpath
for k, v in _d_st.items():
if not delayed:
v = _c_undelay_data(_d_st, k, v)
curpath = path + (k,)
if isinstance(v, TreeStorage):
_d_res[k] = _c_mapping(v, func, curpath)
_d_res[k] = _c_mapping(v, func, curpath, delayed)
else:
_d_res[k] = func(v, curpath)
if delayed:
_d_res[k] = delayed_partial(_c_delayed_mapping, v, func, curpath, delayed)
else:
_d_res[k] = func(v, curpath)
return TreeStorage(_d_res)
@cython.binding(True)
cpdef TreeValue mapping(TreeValue tree, object func):
cpdef TreeValue mapping(TreeValue tree, object func, bool delayed=False):
"""
Overview:
Do mapping on every value in this tree.
......@@ -80,17 +98,18 @@ cpdef TreeValue mapping(TreeValue tree, object func):
>>> mapping(t, lambda: 1) # TreeValue({'a': 1, 'b': 1, 'x': {'c': 1, 'd': 1}})
>>> mapping(t, lambda x, p: p) # TreeValue({'a': ('a',), 'b': ('b',), 'x': {'c': ('x', 'c'), 'd': ('x', 'd')}})
"""
return type(tree)(_c_mapping(tree._detach(), _c_wrap_mapping_func(func), ()))
return type(tree)(_c_mapping(tree._detach(), _c_wrap_mapping_func(func), (), delayed))
cdef TreeStorage _c_filter_(TreeStorage st, object func, tuple path, bool remove_empty):
cdef dict _d_st = st.detach()
cdef dict _d_res = {}
cdef str k
cdef object v
cdef object v, nv
cdef tuple curpath
cdef TreeStorage curst
for k, v in _d_st.items():
v = _c_undelay_data(_d_st, k, v)
curpath = path + (k,)
if isinstance(v, TreeStorage):
curst = _c_filter_(v, func, curpath, remove_empty)
......@@ -148,9 +167,11 @@ cdef object _c_mask(TreeStorage st, object sm, tuple path, bool remove_empty):
cdef tuple curpath
cdef object curres
for k, v in _d_st.items():
v = _c_undelay_data(_d_st, k, v)
curpath = path + (k,)
if _b_tree_mask:
mv = _d_sm[k]
mv = _c_undelay_data(_d_sm, k, mv)
else:
mv = sm
......@@ -194,10 +215,11 @@ cdef object _c_reduce(TreeStorage st, object func, tuple path, object return_typ
cdef dict _d_kwargs = {}
cdef str k
cdef object v
cdef object v, nv
cdef tuple curpath
cdef object curst
for k, v in _d_st.items():
v = _c_undelay_data(_d_st, k, v)
curpath = path + (k,)
if isinstance(v, TreeStorage):
curst = _c_reduce(v, func, curpath, return_type)
......
......@@ -9,7 +9,7 @@ import cython
from libcpp cimport bool
from .tree cimport TreeValue
from ..common.storage cimport TreeStorage
from ..common.storage cimport TreeStorage, _c_undelay_data
cdef object _keep_object(object obj):
return obj
......@@ -29,7 +29,7 @@ cpdef object jsonify(TreeValue val):
Example:
>>> jsonify(TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})) # {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}}
"""
return val._detach().jsondumpx(_keep_object, False)
return val._detach().jsondumpx(_keep_object, False, False)
@cython.binding(True)
cpdef TreeValue clone(TreeValue t, object copy_value=None):
......@@ -50,10 +50,18 @@ cpdef TreeValue clone(TreeValue t, object copy_value=None):
>>> t = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
>>> clone(t.x) # TreeValue({'c': 3, 'd': 4})
"""
cdef bool need_copy
if not callable(copy_value):
copy_value = copy.deepcopy if copy_value else _keep_object
if copy_value:
need_copy = True
copy_value = copy.deepcopy
else:
need_copy = False
copy_value = _keep_object
else:
need_copy = True
return type(t)(t._detach().deepcopyx(copy_value))
return type(t)(t._detach().deepcopyx(copy_value, not need_copy))
@cython.binding(True)
cpdef TreeValue typetrans(TreeValue t, object return_type):
......@@ -87,9 +95,10 @@ def _p_walk(TreeStorage tree, object type_, tuple path, bool include_nodes):
cdef dict data = tree.detach()
cdef str k
cdef object v
cdef object v, nv
cdef tuple curpath
for k, v in data.items():
v = _c_undelay_data(data, k, v)
curpath = path + (k,)
if isinstance(v, TreeStorage):
yield from _p_walk(v, type_, curpath, include_nodes)
......
......@@ -11,10 +11,11 @@ cdef class _SubsideCall:
cdef object _c_subside_process(tuple value, object it)
cdef tuple _c_subside_build(object value, bool dict_, bool list_, bool tuple_)
cdef void _c_subside_missing()
cdef object _c_subside(object value, bool dict_, bool list_, bool tuple_, bool inherit)
cdef object _c_subside(object value, bool dict_, bool list_, bool tuple_, bool inherit,
object mode, object missing, bool delayed)
cdef object _c_subside_keep_type(object t)
cpdef object subside(object value, bool dict_= *, bool list_= *, bool tuple_= *,
object return_type= *, bool inherit= *)
object return_type= *, bool inherit= *, object mode= *, object missing= *, bool delayed= *)
cdef object _c_rise_tree_builder(tuple p, object it)
cdef tuple _c_rise_tree_process(object t)
......
......@@ -6,13 +6,16 @@
from itertools import chain
import cython
from hbutils.design import SingletonMark
from libcpp cimport bool
from .tree cimport TreeValue
from ..common.storage cimport TreeStorage
from ..func.cfunc cimport _c_func_treelize_run
from ..common.storage cimport TreeStorage, _c_undelay_data
from ..func.cfunc cimport _c_func_treelize_run, _c_missing_process
from ..func.modes cimport _c_load_mode
MISSING_NOT_ALLOW = SingletonMark("missing_not_allow")
cdef object _c_subside_process(tuple value, object it):
cdef type type_
cdef list items
......@@ -76,25 +79,29 @@ cdef tuple _c_subside_build(object value, bool dict_, bool list_, bool tuple_):
else:
return (object, None), (value,), ()
STRICT = _c_load_mode('STRICT')
cdef inline void _c_subside_missing():
pass
cdef object _c_subside(object value, bool dict_, bool list_, bool tuple_, bool inherit):
cdef object _c_subside(object value, bool dict_, bool list_, bool tuple_, bool inherit,
object mode, object missing, bool delayed):
cdef object builder, _i_args, _i_types
builder, _i_args, _i_types = _c_subside_build(value, dict_, list_, tuple_)
cdef list args = list(_i_args)
cdef bool allow_missing
cdef object missing_func
allow_missing, missing_func = _c_missing_process(missing)
return _c_func_treelize_run(_SubsideCall(builder), args, {},
STRICT, inherit, False, _c_subside_missing), _i_types
_c_load_mode(mode), inherit, allow_missing, missing_func, delayed), _i_types
cdef inline object _c_subside_keep_type(object t):
return t
@cython.binding(True)
cpdef object subside(object value, bool dict_=True, bool list_=True, bool tuple_=True,
object return_type=None, bool inherit=True):
object return_type=None, bool inherit=True,
object mode='strict', object missing=MISSING_NOT_ALLOW, bool delayed=False):
"""
Overview:
Drift down the structures (list, tuple, dict) down to the tree's value.
......@@ -108,6 +115,11 @@ cpdef object subside(object value, bool dict_=True, bool list_=True, bool tuple_
will be auto detected when there is exactly one tree value type in this original value, \
otherwise the default will be `TreeValue`.
- inherit (:obj:`bool`): Allow inherit in wrapped function, default is `True`.
- mode (:obj:`str`): Mode of the wrapping, default is `strict`.
- 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.
- delayed (:obj:`bool`): Enable delayed mode or not, the calculation will be delayed when enabled, \
default is ``False``, which means to all the calculation at once.
Returns:
- return (:obj:`_TreeValue`): Subsided tree value.
......@@ -132,7 +144,7 @@ cpdef object subside(object value, bool dict_=True, bool list_=True, bool tuple_
>>> #}), all structures above the tree values are subsided to the bottom of the tree.
"""
cdef object result, _i_types
result, _i_types = _c_subside(value, dict_, list_, tuple_, inherit)
result, _i_types = _c_subside(value, dict_, list_, tuple_, inherit, mode, missing, delayed)
cdef object type_
cdef set types
......@@ -150,7 +162,8 @@ cpdef object subside(object value, bool dict_=True, bool list_=True, bool tuple_
return type_(result)
@cython.binding(True)
def union(*trees, object return_type=None, bool inherit=True):
def union(*trees, object return_type=None, bool inherit=True,
object mode='strict', object missing=MISSING_NOT_ALLOW, bool delayed=False):
"""
Overview:
Union tree values together.
......@@ -158,7 +171,12 @@ def union(*trees, object return_type=None, bool inherit=True):
Arguments:
- trees (:obj:`_TreeValue`): Tree value objects.
- return_type (:obj:`Optional[Type[_ClassType]]`): Return type of the wrapped function, default is `TreeValue`.
- inherit (:obj:`bool`): Allow inherit in wrapped function, default is `True`.
- inherit (:obj:`bool`): Allow inheriting in wrapped function, default is `True`.
- mode (:obj:`str`): Mode of the wrapping, default is `strict`.
- 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.
- delayed (:obj:`bool`): Enable delayed mode or not, the calculation will be delayed when enabled, \
default is ``False``, which means to all the calculation at once.
Returns:
- result (:obj:`TreeValue`): Unionised tree value.
......@@ -169,7 +187,7 @@ def union(*trees, object return_type=None, bool inherit=True):
>>> union(t, tx) # TreeValue({'a': (1, True), 'b': (2, False), 'x': {'c': (3, True), 'd': (4, False)}})
"""
cdef object result, _i_types
result, _i_types = _c_subside(tuple(trees), True, True, True, inherit)
result, _i_types = _c_subside(tuple(trees), True, True, True, inherit, mode, missing, delayed)
cdef object type_
cdef list types
......@@ -200,7 +218,7 @@ cdef object _c_rise_tree_builder(tuple p, object it):
cdef tuple _c_rise_tree_process(object t):
cdef str k
cdef object v
cdef object v, nv
cdef list _l_items, _l_values
cdef object _i_item, _i_value
cdef dict detached
......@@ -209,6 +227,7 @@ cdef tuple _c_rise_tree_process(object t):
_l_items = []
_l_values = []
for k, v in detached.items():
v = _c_undelay_data(detached, k, v)
_i_item, _i_value = _c_rise_tree_process(v)
_l_items.append((k, _i_item))
_l_values.append(_i_value)
......@@ -262,7 +281,7 @@ cdef tuple _c_rise_struct_process(list objs, object template):
_l_obj_0 = len(objs[0])
if _l_obj_0 < _l_temp - 2:
raise ValueError(f"At least {repr(_l_temp - 2)} value expected due to template "
f"{repr(template)}, but length is {repr(_l_obj_0)}.")
f"{repr(template)}, but length is {repr(_l_obj_0)}.")
_a_template = type(template)(chain(template[:-2], (template[-2],) * (_l_obj_0 - _l_temp + 2)))
else:
......
# distutils:language=c++
# cython:language_level=3
from libcpp cimport bool
from ..common.delay cimport DelayedProxy
from ..common.storage cimport TreeStorage
cdef class TreeValue:
......@@ -15,3 +18,11 @@ cdef class TreeValue:
cdef str _prefix_fix(object text, object prefix)
cdef object _build_tree(TreeStorage st, object type_, str prefix, dict id_pool, tuple path)
cdef class DetachedDelayedProxy(DelayedProxy):
cdef DelayedProxy proxy
cdef readonly bool calculated
cdef object val
cpdef object value(self)
cpdef object fvalue(self)
\ No newline at end of file
......@@ -7,7 +7,8 @@ from operator import itemgetter
import cython
from hbutils.design import SingletonMark
from ..common.storage cimport TreeStorage, create_storage
from ..common.delay cimport undelay, _c_delayed_partial, DelayedProxy
from ..common.storage cimport TreeStorage, create_storage, _c_undelay_data
from ...utils import format_tree
_GET_NO_DEFAULT = SingletonMark('get_no_default')
......@@ -385,7 +386,7 @@ cdef object _build_tree(TreeStorage st, object type_, str prefix, dict id_pool,
cdef list children = []
cdef str k, _t_prefix
cdef object v
cdef object v, nv
cdef dict data
cdef tuple curpath
if nid in id_pool:
......@@ -395,6 +396,7 @@ cdef object _build_tree(TreeStorage st, object type_, str prefix, dict id_pool,
id_pool[nid] = path
data = st.detach()
for k, v in sorted(data.items()):
v = _c_undelay_data(data, k, v)
curpath = path + (k,)
_t_prefix = f'{k} --> '
if isinstance(v, TreeStorage):
......@@ -404,3 +406,67 @@ cdef object _build_tree(TreeStorage st, object type_, str prefix, dict id_pool,
self_repr = _prefix_fix(self_repr, prefix)
return self_repr, children
cdef class DetachedDelayedProxy(DelayedProxy):
def __init__(self, DelayedProxy proxy):
self.proxy = proxy
self.calculated = False
self.val = None
cpdef object value(self):
if not self.calculated:
self.val = undelay(self.proxy, False)
self.calculated = True
return self.val
cpdef object fvalue(self):
cdef object v = self.value()
if isinstance(v, TreeValue):
v = v._detach()
return v
@cython.binding(True)
def delayed(func, *args, **kwargs):
r"""
Overview:
Use delayed function in treevalue.
The given ``func`` will not be called until its value is accessed, and \
it will be only called once, after that the delayed node will be replaced by the actual value.
Arguments:
- func: Delayed function.
- args: Positional arguments.
- kwargs: Key-word arguments.
Examples::
>>> from treevalue import TreeValue, delayed
>>>
>>> def f(x):
>>> print('f is called, x is', x)
>>> return x ** x
>>>
>>> t = TreeValue({'a': delayed(f, 2), 'x': delayed(f, 3)})
>>> t.a
f is called, x is 2
4
>>> t.x
f is called, x is 3
27
>>> t.a
4
>>> t.x
27
>>> t = TreeValue({'a': delayed(f, 2), 'x': delayed(f, 3)})
>>> print(t)
f is called, x is 2
f is called, x is 3
<TreeValue 0x7f0fb7f03198>
├── a --> 4
└── x --> 27
>>> print(t)
<TreeValue 0x7f0fb7f03198>
├── a --> 4
└── x --> 27
"""
return DetachedDelayedProxy(_c_delayed_partial(func, args, kwargs))
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册