## python 3.8 以上
from typing import Dict, List, TypeVar, Tuple, Generic, get_args
import json
T = TypeVar("T")
# 数据的默认值
def get_dft(tp):
if issubclass(tp, str):
return ""
elif issubclass(tp, int):
return 0
elif issubclass(tp, float):
return 0.0
elif hasattr(tp, "_user_default_"): # 如果是自定类, 实现了这个属性, 也可以用
return tp._user_default_()
else:
return None
# 字段的实现, 也做了类型检查,但现在只能是基本类
class base_field(Generic[T]):
"""字段基类"""
name: str
tp: type # 用于类型检查
def __init__(self, default=None, show=True):
self.name = ""
self.tp = None # 初始化时,类型是没有设定的,这是因为 无法获取到 T 的具体值.
self._default = default # 默认值
self.is_show = show # to dict 时, 是否显示出来
def __get__(self, instance, owner) -> T:
"""从 instance 的 __field_data__ 里取值, 并返回"""
value_con = getattr(instance, "__field_data__", None)
if value_con is None:
setattr(instance, "__field_data__", {})
value_con = getattr(instance, "__field_data__")
dd = value_con.get(self.name, None)
# 当取值是NONE 时, 试着获取默认值
if dd is None and not instance.__field_default__[self.name]:
dd = self._default
if dd is None:
print(
f":: WARN :: value is None : {instance.__class__.__name__}(). {self.name}"
)
return dd
def __set__(self, instance, value):
"""往 instance 的 __field_data__ 里填值"""
value_con = getattr(instance, "__field_data__", None)
if value_con is None:
setattr(instance, "__field_data__", {})
value_con = getattr(instance, "__field_data__")
if isinstance(value, self.tp):
value_con[self.name] = value
else:
raise TypeError(f"应该填入 {self.tp},实填{type(value)}:{str(value)[:30]}")
if not instance.__field_default__[self.name]:
if self._default is None:
dft = get_dft(self.tp)
if dft is None:
print(
f"::_set_ WARN :: default is None : {instance.__class__.__name__}(). {self.name}"
)
self._default = get_dft(self.tp)
instance.__field_default__[self.name] = True
def __set_name__(self, owner, name):
"""在类初始化时调用这个函数,\n
把自己放到类的 __field_setting__ 里去,\n
获取到具体的 T 的类型, 把它绑定到 self.tp 上.
"""
assert self.name == "", f"-> 字段已经有名称,不能重复命名 :now <{self.name}> , {name} "
assert str(name).startswith("a_") # 字段强制 用 a_ 开头
self.name = name
__field_setting__ = getattr(owner, "__field_setting__", None)
if __field_setting__ is None:
setattr(owner, "__field_setting__", {})
__field_setting__ = getattr(owner, "__field_setting__")
ff = __field_setting__.get(name, None)
if ff is not None:
raise NameError(f"只能有一个同名字段.{name}")
else:
self.name = name
__field_setting__[name] = self
self.tp = get_args(self.__orig_class__)[0] # ##
if self._default is None:
dft = get_dft(self.tp)
if dft is None:
print(
f"::_set_ WARN :: default is None : {owner.__class__.__name__}(). {self.name}"
)
self._default = get_dft(self.tp)
class Attr(base_field[T]):
"""
Attr(default=None, show=True) \n
用字段 实现 属性定义
"""
class node_meta(type):
"""
元类 用于控制 NODE 初始化时的一些动作
"""
_class_list = {}
_fn_temp = lambda: print("没找到函数名称")
def __new__(cls, name, bases, attrs):
# 初始化时, 把自己的 __field_setting__ 与父类的 __field_setting__ 隔离开
if name == "node_base":
fs = {}
else:
fs = {}
for i in bases:
for n, fds in i.__field_setting__.items():
fs[n] = fds
attrs["__field_setting__"] = fs
# --
# 生成类
obj_tp = super().__new__(cls, name, bases, attrs)
node_meta._class_list[name] = obj_tp
# 初始化默认值的读取标志
if getattr(obj_tp, "__field_default__", None) is None:
setattr(obj_tp, "__field_default__", {})
for ff, vv in attrs.items():
if ff.startswith("a_"):
obj_tp.__field_default__[ff] = False
# 执行子类初始化钩子
getattr(obj_tp, "__on_sub_class__", cls._fn_temp)()
return obj_tp
class node_base(metaclass=node_meta):
"""NODE 的 基类"""
@classmethod
def __on_sub_class__(cls):
"""子类钩子"""
pass
@classmethod
def __all_sub_classes__(cls):
return node_meta._class_list
def to_dict(self, all_show=False):
"""导出 dict形式,用于传输"""
raise NotImplementedError
@classmethod
def from_dict(cls, d: "List") -> "node_base":
"""导入 dict形式,用于传输"""
assert isinstance(d, List)
assert isinstance(d[0], Dict)
t1: Dict = d[0]
assert t1.get("_node_type_", None) is not None
nd: type = node_meta._class_list[t1["_node_type_"]]
return nd.from_dict(d)
def __str__(self):
return str(self.to_dict())
__repr__ = __str__
@classmethod
def _user_default_(cls):
if hasattr(cls, "_user_default_data_"):
return cls._user_default_data_
else:
ret = cls()
cls._user_default_data_ = ret
return ret
class Node_desc(node_base):
"""这是一种描述用的类,不可以添加子结点"""
a_count = Attr[int](0)
def to_dict(self, all_show=False):
r = [
{
"_node_type_": self.__class__.__name__,
"_attrs": {
k: getattr(self, k)
for k, v in self.__field_setting__.items()
if all_show or v.is_show
},
}
]
return r
@classmethod
def from_dict(cls, d: List) -> node_base:
assert isinstance(d, List)
assert isinstance(d[0], Dict)
t1: Dict = d[0]
assert t1.get("_node_type_", None) is not None
ndtp: type = node_meta._class_list[t1["_node_type_"]]
if issubclass(ndtp, cls):
ret = ndtp()
atrs = t1.get("_attrs", {})
for k, v in atrs.items():
atr: base_field = ndtp.__field_setting__[k]
if issubclass(atr.tp, node_base):
if v is None or not isinstance(v, list) or len(v) == 0:
av = None
else:
av = atr.tp.from_dict(v)
setattr(ret, k, av)
else:
setattr(ret, k, v)
return ret
raise TypeError("NODE DESC 数据格式不对")
class Node(node_base):
"""常规结点,有属性, 他可以添加子结点, 并为子结点指定一个描述符,记录不同的状态"""
child_type = None
desc_type = Node_desc
a_name = Attr[str]("")
def __init__(self) -> None:
self.__field_default__ = {}
for i, v in self.__class__.__field_default__.items():
self.__field_default__[i] = False
self.__data: List["Node"] = []
self._desc_dict: Dict[str, Node_desc] = {}
self._names_of_child: List[str] = []
def __getitem__(self, key) -> "Node":
return self.__data[key]
def __setitem__(self, key, v: "Node"):
assert isinstance(v, Node)
if self.child_type is not None:
assert isinstance(v, self.child_type)
self.__data[key] = v
if v.a_name in self._names_of_child:
self._desc_dict[v.a_name] = self.desc_type()
self._desc_dict[v.a_name].a_count = 1
self._names_of_child = [i.a_name for i in self.__data]
def append(self, item: "Node", desc: Node_desc = None):
if desc is None:
assert isinstance(item, Node)
if item.a_name in self._names_of_child:
self._desc_dict[item.a_name].a_count += 1
else:
self.__data.append(item)
self._desc_dict[item.a_name] = self.desc_type()
self._desc_dict[item.a_name].a_count = 1
self._names_of_child = [i.a_name for i in self.__data]
else:
assert isinstance(item, Node)
if self.child_type is not None:
assert isinstance(item, self.child_type)
assert item.a_name not in self._names_of_child
assert isinstance(desc, self.desc_type)
self.__data.append(item)
self._desc_dict[item.a_name] = desc
self._names_of_child = [i.a_name for i in self.__data]
def remove(self, item: "Node"):
assert isinstance(item, Node)
if self.child_type is not None:
assert isinstance(item, self.child_type)
if item.a_name in self._names_of_child:
self._desc_dict.pop(item.a_name)
for i in self.__data:
if i.a_name == item.a_name:
self.__data.remove(i)
break
def index(self, item: "Node"):
assert isinstance(item, Node)
if self.child_type is not None:
assert isinstance(item, self.child_type)
r = 0
for i in self.__data:
if i.a_name == item.a_name:
return i
r += 1
return -1
def pop(self, index: "Node | int" = -1):
if isinstance(index, int):
nod = self[index]
elif isinstance(index, Node):
if self.child_type is not None:
assert isinstance(index, self.child_type)
nod = self[self.index(index)]
cc = self._desc_dict[nod.a_name].a_count
if cc == 1:
self.remove(nod)
return nod
elif cc > 1:
self._desc_dict[nod.a_name].a_count -= 1
return nod
else:
raise ValueError(f"无法 POP ,数量过少. <{self.a_name}>")
def __len__(self):
return len(self.__data)
def to_dict(self, all_show=False):
rr = [{"_node_type_": self.__class__.__name__, "_attrs": {}}]
for k, v in self.__field_setting__.items():
if all_show or v.is_show:
vv = getattr(self, k)
if isinstance(vv, node_base):
vv = vv.to_dict()
rr[0]["_attrs"][k] = vv
for i in self.__data:
rr.append(
[self._desc_dict[i.a_name].to_dict(all_show), i.to_dict(all_show)]
)
return rr
@classmethod
def from_dict(cls, d: "List"):
assert isinstance(d, List)
assert isinstance(d[0], Dict)
t1: Dict = d[0]
assert t1.get("_node_type_", None) is not None
ndtp = node_meta._class_list[t1["_node_type_"]]
if issubclass(ndtp, Node):
ret = ndtp()
atrs = t1.get("_attrs", {})
for k, v in atrs.items():
atr: base_field = ndtp.__field_setting__[k]
if issubclass(atr.tp, node_base):
if v is None or not isinstance(v, list) or len(v) == 0:
av = None
else:
av = atr.tp.from_dict(v)
setattr(ret, k, av)
else:
setattr(ret, k, v)
children = d[1:]
dsc_tp = ndtp.desc_type
for c in children:
dsc = dsc_tp.from_dict(c[0])
# print(c[1])
c_chd = Node.from_dict(c[1])
ret.append(c_chd, dsc)
return ret
else:
raise TypeError("NODE 数据格式不对")
def node_from_dict(data):
return node_base.from_dict(data)
def node_to_json(n: Node):
rr = n.to_dict(True)
return json.dumps(rr, ensure_ascii=False)
def node_from_json(data: str):
return node_from_dict(json.loads(data))
if __name__ == "__main__":
def test1():
print("-" * 80)
class A(Node):
a_width = Attr[int](50)
class B(Node):
a_deep = Attr[int](65)
class AA(A):
a_height = Attr[int](15, False)
a = A()
aa = AA()
c = A()
print(aa.a_width)
a.a_name = "a_1"
aa.a_name = "a_2"
c.a_name = "a_3"
a.a_width = 30
aa.a_width = 20
aa.a_height = 40
a.append(c)
aa.append(a)
xx = aa.to_dict(True)
print(xx)
yy = node_base.from_dict(xx)
print(yy.to_dict(True))
def test2():
print("-" * 80)
class Z1(Node_desc):
a_adjusted = Attr[bool](False)
class Z2(Node):
a_velocity = Attr[float](0.0)
a_position = Attr[float](0.0)
a_force = Attr[float](0.0)
class Z3(Node):
child_type = Z2
a_direction = Attr[int](1)
class Z4(Node):
child_type = Z2
a_base_velocity = Attr[float](0.0)
a_base_full_length = Attr[float](0.0)
class Z5(Node):
desc_type = Z1
child_type = Z3
a_base_velocity = Attr[float](0.0)
a_base_full_length = Attr[float](0.0)
class Z6(Node):
child_type = (Z4, Z5)
a_time = Attr[str]()
a_desc = Attr[str]()
zf = Z6()
zn_otp = Z4()
p1 = Z2()
zn_otp.append(p1)
zf.append(zn_otp)
print(zf)
zf2 = Z6()
zn_atp = Z5()
zn_t1 = Z3()
p1 = Z2()
zf2.append(zn_atp)
zn_atp.append(zn_t1)
zn_t1.append(p1)
print(zf2)
def test3():
print("-" * 80)
class width(Node):
a_value = Attr[int](80)
class A(Node):
a_width = Attr[width]()
aa = A()
v1 = width()
aa.a_width = v1
aa.a_width.a_value = 75
ajs = node_to_json(aa)
print("json", ajs)
bb = node_from_json(ajs)
print("new", bb)
print("old", aa)
print("-" * 20)
aa = A()
ajs = node_to_json(aa)
print("json", ajs)
bb = node_from_json(ajs)
print("new", bb)
print("old", aa)
def test4():
print("-" * 80)
class Z1(Node_desc):
a_adjusted = Attr[bool](False)
class Z2(Node):
a_velocity = Attr[float](0.0)
a_position = Attr[float](0.0)
a_force = Attr[float](0.0)
class Z3(Node):
child_type = Z2
a_direction = Attr[int](1)
class Z4(Node):
child_type = Z2
desc_type = Z1
a_base_velocity = Attr[float](0.0)
a_base_full_length = Attr[float](0.0)
a_travel_up = Attr[Z3]()
a_travel_down = Attr[Z3]()
class Z5(Node):
child_type = Z4
a_time = Attr[str]("")
a_desc = Attr[str]("")
zf = Z5()
zn_otp = Z4()
p1 = Z2()
zn_otp.append(p1)
zf.append(zn_otp)
print(1, zf)
zn_up = Z3()
zn_down = Z3()
zn_otp.a_travel_down = zn_down
zn_otp.a_travel_up = zn_up
print(2, zf)
zff = node_from_json(node_to_json(zf))
print(3, zff)
test1()
test2()
test3()
test4()