deepfos 1.1.60__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepfos/__init__.py +6 -0
- deepfos/_version.py +21 -0
- deepfos/algo/__init__.py +0 -0
- deepfos/algo/graph.py +171 -0
- deepfos/algo/segtree.py +31 -0
- deepfos/api/V1_1/__init__.py +0 -0
- deepfos/api/V1_1/business_model.py +119 -0
- deepfos/api/V1_1/dimension.py +599 -0
- deepfos/api/V1_1/models/__init__.py +0 -0
- deepfos/api/V1_1/models/business_model.py +1033 -0
- deepfos/api/V1_1/models/dimension.py +2768 -0
- deepfos/api/V1_2/__init__.py +0 -0
- deepfos/api/V1_2/dimension.py +285 -0
- deepfos/api/V1_2/models/__init__.py +0 -0
- deepfos/api/V1_2/models/dimension.py +2923 -0
- deepfos/api/__init__.py +0 -0
- deepfos/api/account.py +167 -0
- deepfos/api/accounting_engines.py +147 -0
- deepfos/api/app.py +626 -0
- deepfos/api/approval_process.py +198 -0
- deepfos/api/base.py +983 -0
- deepfos/api/business_model.py +160 -0
- deepfos/api/consolidation.py +129 -0
- deepfos/api/consolidation_process.py +106 -0
- deepfos/api/datatable.py +341 -0
- deepfos/api/deep_pipeline.py +61 -0
- deepfos/api/deepconnector.py +36 -0
- deepfos/api/deepfos_task.py +92 -0
- deepfos/api/deepmodel.py +188 -0
- deepfos/api/dimension.py +486 -0
- deepfos/api/financial_model.py +319 -0
- deepfos/api/journal_model.py +119 -0
- deepfos/api/journal_template.py +132 -0
- deepfos/api/memory_financial_model.py +98 -0
- deepfos/api/models/__init__.py +3 -0
- deepfos/api/models/account.py +483 -0
- deepfos/api/models/accounting_engines.py +756 -0
- deepfos/api/models/app.py +1338 -0
- deepfos/api/models/approval_process.py +1043 -0
- deepfos/api/models/base.py +234 -0
- deepfos/api/models/business_model.py +805 -0
- deepfos/api/models/consolidation.py +711 -0
- deepfos/api/models/consolidation_process.py +248 -0
- deepfos/api/models/datatable_mysql.py +427 -0
- deepfos/api/models/deep_pipeline.py +55 -0
- deepfos/api/models/deepconnector.py +28 -0
- deepfos/api/models/deepfos_task.py +386 -0
- deepfos/api/models/deepmodel.py +308 -0
- deepfos/api/models/dimension.py +1576 -0
- deepfos/api/models/financial_model.py +1796 -0
- deepfos/api/models/journal_model.py +341 -0
- deepfos/api/models/journal_template.py +854 -0
- deepfos/api/models/memory_financial_model.py +478 -0
- deepfos/api/models/platform.py +178 -0
- deepfos/api/models/python.py +221 -0
- deepfos/api/models/reconciliation_engine.py +411 -0
- deepfos/api/models/reconciliation_report.py +161 -0
- deepfos/api/models/role_strategy.py +884 -0
- deepfos/api/models/smartlist.py +237 -0
- deepfos/api/models/space.py +1137 -0
- deepfos/api/models/system.py +1065 -0
- deepfos/api/models/variable.py +463 -0
- deepfos/api/models/workflow.py +946 -0
- deepfos/api/platform.py +199 -0
- deepfos/api/python.py +90 -0
- deepfos/api/reconciliation_engine.py +181 -0
- deepfos/api/reconciliation_report.py +64 -0
- deepfos/api/role_strategy.py +234 -0
- deepfos/api/smartlist.py +69 -0
- deepfos/api/space.py +582 -0
- deepfos/api/system.py +372 -0
- deepfos/api/variable.py +154 -0
- deepfos/api/workflow.py +264 -0
- deepfos/boost/__init__.py +6 -0
- deepfos/boost/py_jstream.py +89 -0
- deepfos/boost/py_pandas.py +20 -0
- deepfos/cache.py +121 -0
- deepfos/config.py +6 -0
- deepfos/core/__init__.py +27 -0
- deepfos/core/cube/__init__.py +10 -0
- deepfos/core/cube/_base.py +462 -0
- deepfos/core/cube/constants.py +21 -0
- deepfos/core/cube/cube.py +408 -0
- deepfos/core/cube/formula.py +707 -0
- deepfos/core/cube/syscube.py +532 -0
- deepfos/core/cube/typing.py +7 -0
- deepfos/core/cube/utils.py +238 -0
- deepfos/core/dimension/__init__.py +11 -0
- deepfos/core/dimension/_base.py +506 -0
- deepfos/core/dimension/dimcreator.py +184 -0
- deepfos/core/dimension/dimension.py +472 -0
- deepfos/core/dimension/dimexpr.py +271 -0
- deepfos/core/dimension/dimmember.py +155 -0
- deepfos/core/dimension/eledimension.py +22 -0
- deepfos/core/dimension/filters.py +99 -0
- deepfos/core/dimension/sysdimension.py +168 -0
- deepfos/core/logictable/__init__.py +5 -0
- deepfos/core/logictable/_cache.py +141 -0
- deepfos/core/logictable/_operator.py +663 -0
- deepfos/core/logictable/nodemixin.py +673 -0
- deepfos/core/logictable/sqlcondition.py +609 -0
- deepfos/core/logictable/tablemodel.py +497 -0
- deepfos/db/__init__.py +36 -0
- deepfos/db/cipher.py +660 -0
- deepfos/db/clickhouse.py +191 -0
- deepfos/db/connector.py +195 -0
- deepfos/db/daclickhouse.py +171 -0
- deepfos/db/dameng.py +101 -0
- deepfos/db/damysql.py +189 -0
- deepfos/db/dbkits.py +358 -0
- deepfos/db/deepengine.py +99 -0
- deepfos/db/deepmodel.py +82 -0
- deepfos/db/deepmodel_kingbase.py +83 -0
- deepfos/db/edb.py +214 -0
- deepfos/db/gauss.py +83 -0
- deepfos/db/kingbase.py +83 -0
- deepfos/db/mysql.py +184 -0
- deepfos/db/oracle.py +131 -0
- deepfos/db/postgresql.py +192 -0
- deepfos/db/sqlserver.py +99 -0
- deepfos/db/utils.py +135 -0
- deepfos/element/__init__.py +89 -0
- deepfos/element/accounting.py +348 -0
- deepfos/element/apvlprocess.py +215 -0
- deepfos/element/base.py +398 -0
- deepfos/element/bizmodel.py +1269 -0
- deepfos/element/datatable.py +2467 -0
- deepfos/element/deep_pipeline.py +186 -0
- deepfos/element/deepconnector.py +59 -0
- deepfos/element/deepmodel.py +1806 -0
- deepfos/element/dimension.py +1254 -0
- deepfos/element/fact_table.py +427 -0
- deepfos/element/finmodel.py +1485 -0
- deepfos/element/journal.py +840 -0
- deepfos/element/journal_template.py +943 -0
- deepfos/element/pyscript.py +412 -0
- deepfos/element/reconciliation.py +553 -0
- deepfos/element/rolestrategy.py +243 -0
- deepfos/element/smartlist.py +457 -0
- deepfos/element/variable.py +756 -0
- deepfos/element/workflow.py +560 -0
- deepfos/exceptions/__init__.py +239 -0
- deepfos/exceptions/hook.py +86 -0
- deepfos/lazy.py +104 -0
- deepfos/lazy_import.py +84 -0
- deepfos/lib/__init__.py +0 -0
- deepfos/lib/_javaobj.py +366 -0
- deepfos/lib/asynchronous.py +879 -0
- deepfos/lib/concurrency.py +107 -0
- deepfos/lib/constant.py +39 -0
- deepfos/lib/decorator.py +310 -0
- deepfos/lib/deepchart.py +778 -0
- deepfos/lib/deepux.py +477 -0
- deepfos/lib/discovery.py +273 -0
- deepfos/lib/edb_lexer.py +789 -0
- deepfos/lib/eureka.py +156 -0
- deepfos/lib/filterparser.py +751 -0
- deepfos/lib/httpcli.py +106 -0
- deepfos/lib/jsonstreamer.py +80 -0
- deepfos/lib/msg.py +394 -0
- deepfos/lib/nacos.py +225 -0
- deepfos/lib/patch.py +92 -0
- deepfos/lib/redis.py +241 -0
- deepfos/lib/serutils.py +181 -0
- deepfos/lib/stopwatch.py +99 -0
- deepfos/lib/subtask.py +572 -0
- deepfos/lib/sysutils.py +703 -0
- deepfos/lib/utils.py +1003 -0
- deepfos/local.py +160 -0
- deepfos/options.py +670 -0
- deepfos/translation.py +237 -0
- deepfos-1.1.60.dist-info/METADATA +33 -0
- deepfos-1.1.60.dist-info/RECORD +175 -0
- deepfos-1.1.60.dist-info/WHEEL +5 -0
- deepfos-1.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
from operator import methodcaller
|
|
2
|
+
import weakref
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LoopError(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TreeError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NamedTuple(tuple):
|
|
14
|
+
def __new__(cls, *args, **kwargs):
|
|
15
|
+
ins = super().__new__(cls, *args, **kwargs)
|
|
16
|
+
for ele in ins:
|
|
17
|
+
setattr(ins, ele.name, ele)
|
|
18
|
+
return ins
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MetaNodeMixin(type):
|
|
22
|
+
"""
|
|
23
|
+
给使用这个元类的类提供方便定义树结构的接口。
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
.. code-block:: python
|
|
27
|
+
|
|
28
|
+
class Root(metaclass=MetaNodeMixin):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
class ChildA(metaclass=MetaNodeMixin):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
class ChildB(metaclass=MetaNodeMixin):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
class GrandChildA(metaclass=MetaNodeMixin):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
ChildA.set_parent(Root)
|
|
41
|
+
ChildB.set_parent(Root)
|
|
42
|
+
GrandChildA.set_parent(ChildA)
|
|
43
|
+
# 经过上述定义后,将形成如下树结构
|
|
44
|
+
'''
|
|
45
|
+
Root--ChildA--GrandChildA
|
|
46
|
+
└─ChildB
|
|
47
|
+
'''
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def parent(cls):
|
|
52
|
+
"""父节点"""
|
|
53
|
+
try:
|
|
54
|
+
return cls.__parent()
|
|
55
|
+
except AttributeError:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def children(cls):
|
|
60
|
+
"""子节点"""
|
|
61
|
+
return [child() for child in cls.__children_ref]
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def __children_ref(cls):
|
|
65
|
+
"""子节点的弱引用列表"""
|
|
66
|
+
try:
|
|
67
|
+
return cls.__children
|
|
68
|
+
except AttributeError:
|
|
69
|
+
cls.__children = []
|
|
70
|
+
return cls.__children
|
|
71
|
+
|
|
72
|
+
def set_parent(cls, node):
|
|
73
|
+
"""设置父节点。
|
|
74
|
+
|
|
75
|
+
将指定的节点设为父节点。如果当前节点已有父节点,
|
|
76
|
+
将首先把当前节点(及其子树)从原树中移除,
|
|
77
|
+
再把当前节点(及其子树)接入新的树中。
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
node: 待设定的父节点
|
|
81
|
+
"""
|
|
82
|
+
if node is not None and not isinstance(node, MetaNodeMixin):
|
|
83
|
+
raise TreeError(f"父节点 {node!r} 不是 'NodeMixin'.")
|
|
84
|
+
|
|
85
|
+
if cls.parent is not node:
|
|
86
|
+
cls._check_loop(node)
|
|
87
|
+
cls._detach(cls.parent)
|
|
88
|
+
cls._attach(node)
|
|
89
|
+
|
|
90
|
+
def _check_loop(cls, new_parent):
|
|
91
|
+
if new_parent is None:
|
|
92
|
+
return
|
|
93
|
+
if new_parent is cls:
|
|
94
|
+
raise LoopError("父节点不能为自身.")
|
|
95
|
+
if any(ancestor is cls for ancestor in new_parent.iter_to_root()):
|
|
96
|
+
raise LoopError(f"无法设置父节点. {cls!r}已经是{new_parent!r}的祖先.")
|
|
97
|
+
|
|
98
|
+
def _detach(cls, parent):
|
|
99
|
+
if parent is None:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
parent.children.remove(cls)
|
|
104
|
+
except ValueError:
|
|
105
|
+
raise TreeError("Tree is corrupt.")
|
|
106
|
+
|
|
107
|
+
def _attach(cls, new_parent):
|
|
108
|
+
if new_parent is None:
|
|
109
|
+
return # 不用做任何操作,因为parent默认就是None
|
|
110
|
+
|
|
111
|
+
parentchildren = new_parent.__children_ref
|
|
112
|
+
|
|
113
|
+
if any(child is cls for child in parentchildren):
|
|
114
|
+
raise TreeError("Tree is corrupt.")
|
|
115
|
+
parentchildren.append(weakref.ref(cls))
|
|
116
|
+
cls.__parent = weakref.ref(new_parent)
|
|
117
|
+
|
|
118
|
+
def iter_to_root(cls):
|
|
119
|
+
"""遍历至根节点
|
|
120
|
+
|
|
121
|
+
从当前节点触发遍历至根节点,包括自身。
|
|
122
|
+
"""
|
|
123
|
+
node = cls
|
|
124
|
+
while node is not None:
|
|
125
|
+
yield node
|
|
126
|
+
node = node.parent
|
|
127
|
+
|
|
128
|
+
def iter_descendants(cls):
|
|
129
|
+
"""遍历所有后代节点
|
|
130
|
+
|
|
131
|
+
先序遍历所有后代节点,不包括自身。
|
|
132
|
+
"""
|
|
133
|
+
for child in cls.children:
|
|
134
|
+
if child is not None:
|
|
135
|
+
yield child
|
|
136
|
+
yield from child.iter_descendants()
|
|
137
|
+
|
|
138
|
+
def iter_from_root(cls):
|
|
139
|
+
"""从根节点遍历至当前节点
|
|
140
|
+
|
|
141
|
+
从根节点遍历至当前节点,包括自身。
|
|
142
|
+
"""
|
|
143
|
+
for node in reversed(list(cls.iter_to_root())):
|
|
144
|
+
yield node
|
|
145
|
+
|
|
146
|
+
#: 祖先节点
|
|
147
|
+
ancestors = property(lambda self: tuple(self.iter_to_root())[1:])
|
|
148
|
+
#: 节点在树中的深度
|
|
149
|
+
depth = property(lambda self: len(self.ancestors))
|
|
150
|
+
#: tuple: 后代节点
|
|
151
|
+
descendants = property(lambda self: tuple(self.iter_descendants()))
|
|
152
|
+
#: 根节点
|
|
153
|
+
root = property(lambda self: list(self.iter_to_root())[-1])
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def siblings(cls):
|
|
157
|
+
"""
|
|
158
|
+
兄弟节点
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
tuple: 如有,返回所有兄弟节点;否则返回空元组
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
parent = cls.parent
|
|
165
|
+
if parent is None:
|
|
166
|
+
return tuple()
|
|
167
|
+
else:
|
|
168
|
+
return tuple(node for node in parent.children if node is not cls)
|
|
169
|
+
|
|
170
|
+
#: 是否叶子节点
|
|
171
|
+
is_leaf = property(lambda self: not bool(self.children))
|
|
172
|
+
#: 是否根节点
|
|
173
|
+
is_root = property(lambda self: self.parent is None)
|
|
174
|
+
#: tuple: 同一颗树的所有节点
|
|
175
|
+
family = property(lambda self: tuple((self.root, *self.root.descendants)))
|
|
176
|
+
|
|
177
|
+
def common_ancestor(cls, *others):
|
|
178
|
+
"""获取最小共同祖先
|
|
179
|
+
|
|
180
|
+
获取当前节点与其他节点的最小共同祖先,包括其本身。
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
*others: 其他节点
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
最小共同祖先
|
|
187
|
+
"""
|
|
188
|
+
common = None
|
|
189
|
+
|
|
190
|
+
for antr_me, *antr_others in zip(cls.iter_from_root(), *map(methodcaller('iter_from_root'), others)):
|
|
191
|
+
if all(antr is antr_me for antr in antr_others):
|
|
192
|
+
common = antr_me
|
|
193
|
+
else:
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
if common is None:
|
|
197
|
+
raise TreeError(f"{cls!r}和{others!r}不属于同一颗树")
|
|
198
|
+
return common
|
|
199
|
+
|
|
200
|
+
def iter_to_descendant(cls, descendant):
|
|
201
|
+
"""
|
|
202
|
+
遍历自身到后代节点所经过的所有节点,不包括自身。
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
descendant: 后代节点
|
|
206
|
+
Raises:
|
|
207
|
+
ValueError: descendant不是自己或自己的后代,
|
|
208
|
+
Note:
|
|
209
|
+
传入 ``descendant==cls`` 并不会引起错误,但也不会返回任何节点
|
|
210
|
+
"""
|
|
211
|
+
found = False
|
|
212
|
+
|
|
213
|
+
for antr in descendant.iter_from_root():
|
|
214
|
+
if found:
|
|
215
|
+
yield antr
|
|
216
|
+
else:
|
|
217
|
+
if antr is not cls:
|
|
218
|
+
continue
|
|
219
|
+
else:
|
|
220
|
+
found = True
|
|
221
|
+
|
|
222
|
+
if found is False:
|
|
223
|
+
raise ValueError(f"{descendant!r}不是{cls!r}的后代")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class NodeMixin:
|
|
227
|
+
"""
|
|
228
|
+
给使用这个元类的类提供方便定义树结构的接口。
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
.. code-block:: python
|
|
232
|
+
|
|
233
|
+
class Root(NodeMixin):
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
class ChildA(NodeMixin):
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
class ChildB(NodeMixin):
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
class GrandChildA(NodeMixin):
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
ChildA.set_parent(Root)
|
|
246
|
+
ChildB.set_parent(Root)
|
|
247
|
+
GrandChildA.set_parent(ChildA)
|
|
248
|
+
# 经过上述定义后,将形成如下树结构
|
|
249
|
+
'''
|
|
250
|
+
Root--ChildA--GrandChildA
|
|
251
|
+
└─ChildB
|
|
252
|
+
'''
|
|
253
|
+
"""
|
|
254
|
+
@property
|
|
255
|
+
def parent(self):
|
|
256
|
+
"""父节点"""
|
|
257
|
+
try:
|
|
258
|
+
return self.__parent()
|
|
259
|
+
except AttributeError:
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def ichildren(self):
|
|
264
|
+
"""返回节点的直接孩子节点,包含自身"""
|
|
265
|
+
return [self, *self.children]
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def children(self):
|
|
269
|
+
"""返回当前节点的直接孩子节点,不包含自身,当前节点无孩子则返回空列表"""
|
|
270
|
+
try:
|
|
271
|
+
return self.__children
|
|
272
|
+
except AttributeError:
|
|
273
|
+
self.__children = []
|
|
274
|
+
return self.__children
|
|
275
|
+
|
|
276
|
+
def set_parent(self, node):
|
|
277
|
+
"""
|
|
278
|
+
将指定的节点设为父节点。
|
|
279
|
+
如果当前节点已有父节点,将首先把当前节点(及其子树)从原树中移除,
|
|
280
|
+
再把当前节点(及其子树)接入新的树中。
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
node: 待设定的父节点
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
if node is not None and not isinstance(node, NodeMixin):
|
|
287
|
+
raise TreeError(f"父节点 {node!r} 不是 '{self.__class__.__name__}'.")
|
|
288
|
+
|
|
289
|
+
if self.parent is not node:
|
|
290
|
+
self._check_loop(node)
|
|
291
|
+
self._detach(self.parent)
|
|
292
|
+
self._attach(node)
|
|
293
|
+
|
|
294
|
+
def add_child(self, node):
|
|
295
|
+
"""
|
|
296
|
+
将指定的节点设为孩子节点。
|
|
297
|
+
如果指定节点已有父节点,将首先把指定节点(及其子树)从原树中移除,
|
|
298
|
+
再把指定节点(及其子树)接入新的树中。
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
node: 待设定的孩子节点
|
|
302
|
+
"""
|
|
303
|
+
if not isinstance(node, NodeMixin):
|
|
304
|
+
raise TreeError(f"子节点 {node!r} 不是 '{self.__class__.__name__}'.")
|
|
305
|
+
|
|
306
|
+
if node not in self.children:
|
|
307
|
+
node._check_loop(self)
|
|
308
|
+
node._detach(node.parent)
|
|
309
|
+
node._attach(self)
|
|
310
|
+
|
|
311
|
+
def _check_loop(self, new_parent):
|
|
312
|
+
if new_parent is None:
|
|
313
|
+
return
|
|
314
|
+
if new_parent is self:
|
|
315
|
+
raise LoopError("父节点不能为自身.")
|
|
316
|
+
if any(ancestor is self for ancestor in new_parent.iter_to_root()):
|
|
317
|
+
raise LoopError(f"无法设置父节点. {self!r}已经是{new_parent!r}的祖先.")
|
|
318
|
+
|
|
319
|
+
def _detach(self, parent):
|
|
320
|
+
if parent is None:
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
parent.children.remove(self)
|
|
325
|
+
except ValueError:
|
|
326
|
+
raise TreeError("Tree is corrupt.")
|
|
327
|
+
|
|
328
|
+
def _attach(self, new_parent):
|
|
329
|
+
"""将一棵树连接到父结点上"""
|
|
330
|
+
if new_parent is None:
|
|
331
|
+
return # 不用做任何操作,因为parent默认就是None
|
|
332
|
+
|
|
333
|
+
parentchildren = new_parent.children
|
|
334
|
+
|
|
335
|
+
if any(child is self for child in parentchildren):
|
|
336
|
+
raise TreeError("Tree is corrupt.")
|
|
337
|
+
parentchildren.append(self)
|
|
338
|
+
self.__parent = weakref.ref(new_parent)
|
|
339
|
+
|
|
340
|
+
def iter_to_root(self):
|
|
341
|
+
"""从当前节点迭代至根节点,包括自身。返回生成器。"""
|
|
342
|
+
node = self
|
|
343
|
+
while node is not None:
|
|
344
|
+
yield node
|
|
345
|
+
node = node.parent
|
|
346
|
+
|
|
347
|
+
def iter_descendants(self, include=False):
|
|
348
|
+
"""先序遍历所有后代节点,不包括自身。返回节点列表有顺序"""
|
|
349
|
+
if include:
|
|
350
|
+
yield self
|
|
351
|
+
for child in self.children:
|
|
352
|
+
if child is not None:
|
|
353
|
+
yield from child.iter_descendants(include=True)
|
|
354
|
+
|
|
355
|
+
def iter_from_root(self):
|
|
356
|
+
"""从根节点迭代至当前节点,包括自身。"""
|
|
357
|
+
for node in reversed(list(self.iter_to_root())):
|
|
358
|
+
yield node
|
|
359
|
+
|
|
360
|
+
#: 祖先节点
|
|
361
|
+
ancestors = property(lambda self: tuple(self.iter_to_root())[1:])
|
|
362
|
+
#: 节点在树中的深度
|
|
363
|
+
depth = property(lambda self: len(self.ancestors))
|
|
364
|
+
#: tuple: 后代节点
|
|
365
|
+
descendant = property(lambda self: list(self.iter_descendants()))
|
|
366
|
+
#: tuple: 后代节点,包括自身
|
|
367
|
+
idescendant = property(lambda self: list(self.iter_descendants(include=True)))
|
|
368
|
+
|
|
369
|
+
#: 根节点
|
|
370
|
+
root = property(lambda self: list(self.iter_to_root())[-1])
|
|
371
|
+
|
|
372
|
+
def iter_base(self):
|
|
373
|
+
"""遍历当前子树的所有叶子节点"""
|
|
374
|
+
for node in self.iter_descendants():
|
|
375
|
+
if node.is_leaf:
|
|
376
|
+
yield node
|
|
377
|
+
|
|
378
|
+
#: 子树的所有叶子节点
|
|
379
|
+
base = property(lambda self: list(self.iter_base()))
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def ibase(self):
|
|
383
|
+
"""子树的所有叶子节点,包含节点自身。"""
|
|
384
|
+
return [self, *self.base]
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def siblings(self):
|
|
388
|
+
"""
|
|
389
|
+
兄弟节点,不包含自身。
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
tuple: 如有,返回所有兄弟节点;否则返回空元组
|
|
393
|
+
|
|
394
|
+
"""
|
|
395
|
+
parent = self.parent
|
|
396
|
+
if parent is None:
|
|
397
|
+
return tuple()
|
|
398
|
+
else:
|
|
399
|
+
return tuple(node for node in parent.children if node is not self)
|
|
400
|
+
|
|
401
|
+
#: 是否叶子节点
|
|
402
|
+
is_leaf = property(lambda self: not bool(self.children))
|
|
403
|
+
#: 是否根节点
|
|
404
|
+
is_root = property(lambda self: self.parent is None)
|
|
405
|
+
#: tuple: 同一颗树的所有节点
|
|
406
|
+
family = property(lambda self: self.root.idescendant)
|
|
407
|
+
|
|
408
|
+
def common_ancestor(self, *others):
|
|
409
|
+
"""
|
|
410
|
+
获取当前节点与其他节点的最小共同祖先,包括其本身。
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
*others: 其他节点
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
最小共同祖先
|
|
417
|
+
|
|
418
|
+
"""
|
|
419
|
+
common = None
|
|
420
|
+
|
|
421
|
+
for antr_me, *antr_others in zip(self.iter_from_root(),
|
|
422
|
+
*map(methodcaller('iter_from_root'), others)):
|
|
423
|
+
if all(antr is antr_me for antr in antr_others):
|
|
424
|
+
common = antr_me
|
|
425
|
+
else:
|
|
426
|
+
break
|
|
427
|
+
|
|
428
|
+
if common is None:
|
|
429
|
+
raise TreeError(f"{self!r}和{others!r}不属于同一颗树")
|
|
430
|
+
return common
|
|
431
|
+
|
|
432
|
+
def iter_to_descendant(self, descendant):
|
|
433
|
+
"""
|
|
434
|
+
遍历自身到后代节点所经过的所有节点,不包括自身。
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
descendant: 后代节点
|
|
438
|
+
|
|
439
|
+
Raises:
|
|
440
|
+
descendant不是自己或自己的后代时,抛出 `ValueError` 异常
|
|
441
|
+
|
|
442
|
+
Note:
|
|
443
|
+
传入 `descendant==self` 并不会引起错误,但也不会返回任何节点
|
|
444
|
+
"""
|
|
445
|
+
found = False
|
|
446
|
+
|
|
447
|
+
for antr in descendant.iter_from_root():
|
|
448
|
+
if found:
|
|
449
|
+
yield antr
|
|
450
|
+
else:
|
|
451
|
+
if antr is not self:
|
|
452
|
+
continue
|
|
453
|
+
else:
|
|
454
|
+
found = True
|
|
455
|
+
|
|
456
|
+
if found is False:
|
|
457
|
+
raise ValueError(f"{descendant!r}不是{self!r}的后代")
|
|
458
|
+
|
|
459
|
+
def iter_level(self, from_offset, to_offset, include=True):
|
|
460
|
+
"""
|
|
461
|
+
返回与当前节点位置相对的节点,向上的节点只包括父节点,向下的节点以广度优先顺序遍历。
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
from_offset: 相对节点的开始位置
|
|
465
|
+
to_offset: 相对节点的结束位置,包括结束点
|
|
466
|
+
include: 是否包括自身节点
|
|
467
|
+
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
if to_offset < from_offset:
|
|
471
|
+
raise ValueError("Stop level should be greater than start.")
|
|
472
|
+
|
|
473
|
+
parent_list = []
|
|
474
|
+
parent = self.parent
|
|
475
|
+
search_up_cnt = from_offset
|
|
476
|
+
|
|
477
|
+
while search_up_cnt < 0 and parent:
|
|
478
|
+
parent_list.append(parent)
|
|
479
|
+
parent = parent.parent
|
|
480
|
+
search_up_cnt += 1
|
|
481
|
+
|
|
482
|
+
parent_list = parent_list[from_offset - to_offset - 1:]
|
|
483
|
+
|
|
484
|
+
if parent_list:
|
|
485
|
+
for node in reversed(parent_list):
|
|
486
|
+
yield node
|
|
487
|
+
|
|
488
|
+
if include:
|
|
489
|
+
yield self
|
|
490
|
+
|
|
491
|
+
if to_offset < 0:
|
|
492
|
+
return
|
|
493
|
+
|
|
494
|
+
yield from bfs(self, depth=to_offset, include=False)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class ShareableNodeMixin(NodeMixin):
|
|
498
|
+
_ref_shared_from = None
|
|
499
|
+
|
|
500
|
+
def add_child(self, node):
|
|
501
|
+
raise NotImplemented
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def children(self):
|
|
505
|
+
"""返回当前节点的直接孩子节点,不包含自身,当前节点无孩子则返回空列表"""
|
|
506
|
+
if shared_from := self.shared_from:
|
|
507
|
+
return shared_from.children
|
|
508
|
+
else:
|
|
509
|
+
return super().children
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def shared_by(self):
|
|
513
|
+
"""父节点"""
|
|
514
|
+
try:
|
|
515
|
+
return self.__shared_by
|
|
516
|
+
except AttributeError:
|
|
517
|
+
self.__shared_by = []
|
|
518
|
+
return self.__shared_by
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def is_shared(self):
|
|
522
|
+
return bool(self.shared_by)
|
|
523
|
+
|
|
524
|
+
@property
|
|
525
|
+
def shared_from(self):
|
|
526
|
+
if self._ref_shared_from:
|
|
527
|
+
return self._ref_shared_from()
|
|
528
|
+
else:
|
|
529
|
+
return None
|
|
530
|
+
|
|
531
|
+
def add_shared(self, node):
|
|
532
|
+
self.shared_by.append(node)
|
|
533
|
+
node._ref_shared_from = weakref.ref(self)
|
|
534
|
+
|
|
535
|
+
@property
|
|
536
|
+
def parent(self):
|
|
537
|
+
"""父节点"""
|
|
538
|
+
try:
|
|
539
|
+
if parent := self.__parent:
|
|
540
|
+
return [p() for p in parent]
|
|
541
|
+
else:
|
|
542
|
+
return None
|
|
543
|
+
except AttributeError:
|
|
544
|
+
self.__parent = [] # noqa
|
|
545
|
+
return None
|
|
546
|
+
|
|
547
|
+
def set_parent(self, node, check_loop: bool = False):
|
|
548
|
+
if node is not None and not isinstance(node, NodeMixin):
|
|
549
|
+
raise TreeError(f"父节点 {node!r} 不是 '{self.__class__.__name__}'.")
|
|
550
|
+
|
|
551
|
+
parent = self.parent
|
|
552
|
+
|
|
553
|
+
if node is None:
|
|
554
|
+
if parent is None:
|
|
555
|
+
return
|
|
556
|
+
|
|
557
|
+
for p in self.parent:
|
|
558
|
+
p.children.remove(self)
|
|
559
|
+
self.parent.clear()
|
|
560
|
+
|
|
561
|
+
else:
|
|
562
|
+
if check_loop:
|
|
563
|
+
self._check_loop(node)
|
|
564
|
+
self._attach(node)
|
|
565
|
+
|
|
566
|
+
def _attach(self, new_parent):
|
|
567
|
+
"""将一棵树连接到父结点上"""
|
|
568
|
+
parentchildren = new_parent.children
|
|
569
|
+
|
|
570
|
+
if any(child is self for child in parentchildren):
|
|
571
|
+
return
|
|
572
|
+
parentchildren.append(self)
|
|
573
|
+
self.__parent.append(weakref.ref(new_parent))
|
|
574
|
+
|
|
575
|
+
def iter_to_root(self, exclude=None):
|
|
576
|
+
"""从当前节点迭代至根节点,包括自身。返回生成器。"""
|
|
577
|
+
exclude = exclude or set()
|
|
578
|
+
yield self
|
|
579
|
+
exclude.add(self)
|
|
580
|
+
for p in self.parent or []:
|
|
581
|
+
yield from p.iter_to_root(exclude)
|
|
582
|
+
|
|
583
|
+
@property
|
|
584
|
+
def siblings(self):
|
|
585
|
+
sib = set()
|
|
586
|
+
for parent in self.parent or []:
|
|
587
|
+
if parent is not None:
|
|
588
|
+
sib.update(node for node in parent.children if node is not self)
|
|
589
|
+
return tuple(sib)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def bfs(node, depth=-1, include=True):
|
|
593
|
+
"""
|
|
594
|
+
广度优先遍历树
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
node(NodeMixin): 遍历以 ``node`` 为根节点的维度树
|
|
598
|
+
depth(int): 遍历的深度
|
|
599
|
+
include(bool): 是或否包含自身
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
返回生成器,包含遍历到的所有节点
|
|
603
|
+
"""
|
|
604
|
+
if depth == 0:
|
|
605
|
+
return
|
|
606
|
+
elif depth > 0:
|
|
607
|
+
depth += node.depth + 1
|
|
608
|
+
|
|
609
|
+
if include:
|
|
610
|
+
yield node
|
|
611
|
+
|
|
612
|
+
node_to_visit = node.children[:]
|
|
613
|
+
|
|
614
|
+
while node_to_visit:
|
|
615
|
+
child = node_to_visit.pop(0)
|
|
616
|
+
if child.depth == depth:
|
|
617
|
+
break
|
|
618
|
+
|
|
619
|
+
yield child
|
|
620
|
+
|
|
621
|
+
for grandchild in child.children:
|
|
622
|
+
node_to_visit.append(grandchild)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
class TreeRenderer:
|
|
626
|
+
"""渲染树形结构"""
|
|
627
|
+
def __init__(self):
|
|
628
|
+
self.blank = ' '
|
|
629
|
+
self.low = '└── '
|
|
630
|
+
self.mid = '├── '
|
|
631
|
+
self.gap = '| '
|
|
632
|
+
self.len = len(self.blank)
|
|
633
|
+
|
|
634
|
+
def iter_line(self, root, sty='', fill=''):
|
|
635
|
+
yield fill[:-self.len] + sty * (len(fill) > 0) + str(root)
|
|
636
|
+
|
|
637
|
+
child_num = len(root.children)
|
|
638
|
+
for idx, child in enumerate(root.children, 1):
|
|
639
|
+
if idx == child_num:
|
|
640
|
+
yield from self.iter_line(child, sty=self.low, fill=fill + self.blank)
|
|
641
|
+
else:
|
|
642
|
+
yield from self.iter_line(child, sty=self.mid, fill=fill + self.gap)
|
|
643
|
+
|
|
644
|
+
def render(self, root) -> str:
|
|
645
|
+
"""
|
|
646
|
+
渲染树结构
|
|
647
|
+
|
|
648
|
+
Args:
|
|
649
|
+
root(NodeMixin): 树的根节点
|
|
650
|
+
|
|
651
|
+
Examples:
|
|
652
|
+
.. code-block:: python
|
|
653
|
+
|
|
654
|
+
# dim.root 为需要打印的维度树的根节点
|
|
655
|
+
print(TreeRenderer().render(dim.root))
|
|
656
|
+
# 经过上述定义后,将形成如下树结构
|
|
657
|
+
'''
|
|
658
|
+
#root
|
|
659
|
+
└── TotalPeriod
|
|
660
|
+
└── Q1
|
|
661
|
+
├── 1
|
|
662
|
+
├── 2
|
|
663
|
+
└── 3
|
|
664
|
+
'''
|
|
665
|
+
"""
|
|
666
|
+
if isinstance(root, (NodeMixin, MetaNodeMixin)):
|
|
667
|
+
return '\n'.join(self.iter_line(root))
|
|
668
|
+
else:
|
|
669
|
+
raise TypeError(f'{root.__class__.__name__}不是NodeMixin或MetaNodeMixin')
|
|
670
|
+
|
|
671
|
+
def show(self, root):
|
|
672
|
+
"""打印树结构"""
|
|
673
|
+
print(self.render(root))
|