Skip to content

Commit 6152f14

Browse files
authored
Merge pull request #37 from opendilab/dev/pop
dev(hansbug): add pop method for TreeValue
2 parents ea00977 + 3591996 commit 6152f14

File tree

8 files changed

+198
-3
lines changed

8 files changed

+198
-3
lines changed

docs/source/api_doc/tree/common.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ TreeStorage
99
-----------
1010

1111
.. autoclass:: TreeStorage
12-
:members: get, set, del_, contains, size, empty, copy, deepcopy, deepcopyx, dump, deepdump, deepdumpx, jsondumpx, copy_from, deepcopy_from, deepcopyx_from, detach
12+
:members: get, get_or_default, pop, pop_or_default, set, del_, contains, size, empty, copy, deepcopy, deepcopyx, dump, deepdump, deepdumpx, jsondumpx, copy_from, deepcopy_from, deepcopyx_from, detach
1313

1414

1515
.. _apidoc_tree_common_raw:

docs/source/api_doc/tree/tree.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ TreeValue
99
---------------
1010

1111
.. autoclass:: TreeValue
12-
:members: __init__, __getattribute__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__, __getstate__, __setstate__, get, keys, values, items, __getitem__, __setitem__, __delitem__, _getitem_extern, _setitem_extern, _delitem_extern
12+
:members: __init__, __getattribute__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__, __getstate__, __setstate__, get, pop, keys, values, items, __getitem__, __setitem__, __delitem__, _getitem_extern, _setitem_extern, _delitem_extern
1313

1414

1515
.. _apidoc_tree_tree_delayed:

test/tree/common/test_storage.py

+104
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,110 @@ def test_get_or_default(self):
9797
assert t1.get_or_default('fff', delayed_partial(lambda: 2345)) == 2345
9898
assert not t1.contains('fff')
9999

100+
def test_pop(self):
101+
t = create_storage({'a': 1, 'b': 2, 'c': raw({'x': 3, 'y': 4}), 'd': {'x': 3, 'y': 4}})
102+
assert t.pop('a') == 1
103+
with pytest.raises(KeyError):
104+
t.pop('a')
105+
106+
assert t.pop('b') == 2
107+
assert t.pop('c') == {'x': 3, 'y': 4}
108+
109+
td = t.pop('d')
110+
assert isinstance(td, TreeStorage)
111+
assert td.get('x') == 3
112+
assert td.get('y') == 4
113+
114+
with pytest.raises(KeyError):
115+
t.pop('aksjdlasdkjf')
116+
117+
cnt1, cnt2, cnt3 = 0, 0, 0
118+
119+
def f1():
120+
nonlocal cnt1
121+
cnt1 += 1
122+
return 2
123+
124+
def f2(x, y):
125+
nonlocal cnt2
126+
cnt2 += 1
127+
return {'x': x, 'y': y}
128+
129+
def f3(x, y):
130+
nonlocal cnt3
131+
cnt3 += 1
132+
return create_storage({'x': x, 'y': raw(y)})
133+
134+
t2 = create_storage({
135+
'a': 1,
136+
'b': delayed_partial(f1),
137+
'c': delayed_partial(f2, delayed_partial(f1), 3),
138+
'd': delayed_partial(f3, 3, delayed_partial(f2, 3, 4))
139+
})
140+
141+
assert t2.pop('a') == 1
142+
143+
assert cnt1 == 0
144+
assert t2.pop('b') == 2
145+
assert cnt1 == 1
146+
with pytest.raises(KeyError):
147+
t2.pop('b')
148+
assert cnt1 == 1
149+
150+
assert (cnt1, cnt2) == (1, 0)
151+
assert t2.pop('c') == {'x': 2, 'y': 3}
152+
assert (cnt1, cnt2) == (2, 1)
153+
with pytest.raises(KeyError):
154+
t2.pop('c')
155+
assert (cnt1, cnt2) == (2, 1)
156+
157+
assert (cnt1, cnt2, cnt3) == (2, 1, 0)
158+
assert t2.get('d').pop('x') == 3
159+
assert t2.get('d').pop('y') == {'x': 3, 'y': 4}
160+
assert (cnt1, cnt2, cnt3) == (2, 2, 1)
161+
with pytest.raises(KeyError):
162+
t2.get('d').pop('x')
163+
with pytest.raises(KeyError):
164+
t2.get('d').pop('y')
165+
assert (cnt1, cnt2, cnt3) == (2, 2, 1)
166+
167+
def test_pop_or_default(self):
168+
t = create_storage({'a': 1, 'b': 2, 'c': raw({'x': 3, 'y': 4}), 'd': {'x': 3, 'y': 4}})
169+
assert t.pop_or_default('a', 233) == 1
170+
with pytest.raises(KeyError):
171+
t.pop('a')
172+
assert t.pop_or_default('a', 233) == 233
173+
174+
assert t.pop_or_default('b', 233) == 2
175+
assert t.pop_or_default('c', 233) == {'x': 3, 'y': 4}
176+
177+
td = t.pop_or_default('d', 233)
178+
assert isinstance(td, TreeStorage)
179+
assert td.pop_or_default('x', 233) == 3
180+
assert td.pop_or_default('y', 233) == 4
181+
182+
assert t.pop_or_default('fff', 233) == 233
183+
184+
t = create_storage({'a': 1, 'b': 2, 'c': raw({'x': 3, 'y': 4}), 'd': {'x': 3, 'y': 4}})
185+
t1 = create_storage({
186+
'a': delayed_partial(lambda: t.get('a')),
187+
'b': delayed_partial(lambda: t.get('b')),
188+
'c': delayed_partial(lambda: t.get('c')),
189+
'd': delayed_partial(lambda: t.get('d')),
190+
})
191+
assert t1.pop_or_default('a', 233) == 1
192+
assert t1.pop_or_default('b', 233) == 2
193+
assert t1.pop_or_default('c', 233) == {'x': 3, 'y': 4}
194+
195+
t1d = t1.pop_or_default('d', 233)
196+
assert isinstance(t1d, TreeStorage)
197+
assert t1d.pop_or_default('x', 233) == 3
198+
assert t1d.pop_or_default('y', 233) == 4
199+
200+
assert t1.pop_or_default('fff', 233) == 233
201+
assert t1.pop_or_default('fff', delayed_partial(lambda: 2345)) == 2345
202+
assert not t1.contains('fff')
203+
100204
def test_set(self):
101205
t = create_storage({})
102206
t.set('a', 1)

test/tree/tree/test_tree.py

+46
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,52 @@ def test_get(self):
282282
_ = tv1.get('e')
283283
assert tv1.get('e', 233) == 233
284284

285+
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
286+
tv2 = TreeValue({
287+
'a': delayed(lambda: tv1.a),
288+
'b': delayed(lambda: tv1.b),
289+
'c': delayed(lambda: tv1.c),
290+
})
291+
assert tv2.get('a') == 1
292+
assert tv2.get('b') == 2
293+
assert tv2.get('c') == TreeValue({'x': 2, 'y': 3})
294+
295+
def test_pop(self):
296+
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
297+
298+
assert tv1.pop('a') == 1
299+
with pytest.raises(KeyError):
300+
_ = tv1.pop('a')
301+
assert tv1.pop('a', 233) == 233
302+
303+
assert tv1.pop('b') == 2
304+
assert tv1.pop('c') == TreeValue({'x': 2, 'y': 3})
305+
assert tv1.pop('d') == {'x': 2, 'y': 3}
306+
with pytest.raises(KeyError):
307+
_ = tv1.pop('e')
308+
assert tv1.pop('e', 233) == 233
309+
310+
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
311+
tv2 = TreeValue({
312+
'a': delayed(lambda: tv1.a),
313+
'b': delayed(lambda: tv1.b),
314+
'c': delayed(lambda: tv1.c),
315+
})
316+
assert tv2.pop('a') == 1
317+
assert tv2.pop('b') == 2
318+
assert tv2.pop('c') == TreeValue({'x': 2, 'y': 3})
319+
320+
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
321+
tv3 = TreeValue({'a': 233, 'b': tv1, 'c': tv1})
322+
assert tv3.pop('b') == tv1
323+
assert tv3.pop('c') == tv1
324+
with pytest.raises(KeyError):
325+
tv3.pop('b')
326+
with pytest.raises(KeyError):
327+
tv3.pop('c')
328+
assert tv3.pop('b', 345) == 345
329+
assert tv3.pop('c', 345) == 345
330+
285331
def test_keys(self):
286332
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
287333
assert set(tv1.keys()) == {'a', 'b', 'c', 'd'}

treevalue/tree/common/storage.pxd

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ cdef class TreeStorage:
1212
cpdef public void set(self, str key, object value) except *
1313
cpdef public object get(self, str key)
1414
cpdef public object get_or_default(self, str key, object default)
15+
cpdef public object pop(self, str key)
16+
cpdef public object pop_or_default(self, str key, object default)
1517
cpdef public void del_(self, str key) except *
1618
cpdef public boolean contains(self, str key)
1719
cpdef public uint size(self)

treevalue/tree/common/storage.pyx

+20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ cdef class TreeStorage:
2222
cpdef public void set(self, str key, object value) except *:
2323
self.map[key] = value
2424

25+
# get and get_or_default is designed separately due to the consideration of performance
2526
cpdef public object get(self, str key):
2627
cdef object v, nv
2728
try:
@@ -35,6 +36,25 @@ cdef class TreeStorage:
3536
v = self.map.get(key, default)
3637
return _c_undelay_check_data(self.map, key, v)
3738

39+
# pop and pop_or_default is designed separately due to the consideration of performance
40+
cpdef public object pop(self, str key):
41+
cdef object v, nv, res
42+
try:
43+
v = self.map[key]
44+
res = _c_undelay_data(self.map, key, v)
45+
del self.map[key]
46+
return res
47+
except KeyError:
48+
raise KeyError(f"Key {repr(key)} not found in this tree.")
49+
50+
cpdef public object pop_or_default(self, str key, object default):
51+
cdef object v, nv, res
52+
v = self.map.get(key, default)
53+
res = _c_undelay_data(self.map, key, v)
54+
if key in self.map:
55+
del self.map[key]
56+
return res
57+
3858
cpdef public void del_(self, str key) except *:
3959
try:
4060
del self.map[key]

treevalue/tree/tree/tree.pxd

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ cdef class TreeValue:
1818
cpdef _setitem_extern(self, object key, object value)
1919
cpdef _delitem_extern(self, object key)
2020
cpdef get(self, str key, object default= *)
21+
cpdef pop(self, str key, object default= *)
2122

2223
cdef str _prefix_fix(object text, object prefix)
2324
cdef object _build_tree(TreeStorage st, object type_, str prefix, dict id_pool, tuple path)
@@ -28,4 +29,4 @@ cdef class DetachedDelayedProxy(DelayedProxy):
2829
cdef object val
2930

3031
cpdef object value(self)
31-
cpdef object fvalue(self)
32+
cpdef object fvalue(self)

treevalue/tree/tree/tree.pyx

+22
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,28 @@ cdef class TreeValue:
119119

120120
return self._unraw(value)
121121

122+
@cython.binding(True)
123+
cpdef pop(self, str key, object default=_GET_NO_DEFAULT):
124+
"""
125+
Overview:
126+
Pop item from the tree node.
127+
128+
Arguments:
129+
- key (:obj:`str`): Item's name.
130+
- default (:obj:`default`): Default value when this item is not found, default is \
131+
`_GET_NO_DEFAULT` which means just raise `KeyError` when not found.
132+
133+
Returns:
134+
- value: Item's value.
135+
"""
136+
cdef object value
137+
if default is _GET_NO_DEFAULT:
138+
value = self._st.pop(key)
139+
else:
140+
value = self._st.pop_or_default(key, default)
141+
142+
return self._unraw(value)
143+
122144
@cython.binding(True)
123145
cpdef _attr_extern(self, str key):
124146
r"""

0 commit comments

Comments
 (0)