## 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()

自己写一个 NODE/ATTR 的结构的更多相关文章

  1. javascript如何用递归写一个简单的树形结构

    现在有一个数据,需要你渲染出对应的列表出来: var data = [ {"id":1}, {"id":2}, {"id":3}, {&qu ...

  2. (网页)javascript如何用递归写一个简单的树形结构

    转自博客园: 现在有一个数据,需要你渲染出对应的列表出来: var data = [ {"id":1}, {"id":2}, {"id":3 ...

  3. 用node.js从零开始去写一个简单的爬虫

    如果你不会Python语言,正好又是一个node.js小白,看完这篇文章之后,一定会觉得受益匪浅,感受到自己又新get到了一门技能,如何用node.js从零开始去写一个简单的爬虫,十分钟时间就能搞定, ...

  4. 用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话

    wechatBot 微信每日说,每日自动发送微信消息给你心爱的人 项目介绍 灵感来源 在掘金看到了一篇<用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件>后, 在评论区偶然 ...

  5. 使用 Node.js 写一个代码生成器

    背景 第一次接触代码生成器用的是动软代码生成器,数据库设计好之后,一键生成后端 curd代码.之后也用过 CodeSmith , T4.目前市面上也有很多优秀的代码生成器,而且大部分都提供可视化界面操 ...

  6. 从 0 到 1 到完美,写一个 js 库、node 库、前端组件库

    之前讲了很多关于项目工程化.前端架构.前端构建等方面的技术,这次说说怎么写一个完美的第三方库. 1. 选择合适的规范来写代码 js 模块化的发展大致有这样一个过程 iife => commonj ...

  7. 【原创】只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(四)

    全系列Index: [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(一) [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(二) [原创]只学到二维数组和结构体,不用链表也能 ...

  8. 使用node.js 文档里的方法写一个web服务器

    刚刚看了node.js文档里的一个小例子,就是用 node.js 写一个web服务器的小例子 上代码 (*^▽^*) //helloworld.js// 使用node.js写一个服务器 const h ...

  9. 使用原生node写一个聊天室

    在学习node的时候都会练习做一个聊天室的项目,主要使用socket.io模块和http模块.这里我们使用更加原始的方式去写一个在命令行聊天的聊天室. http模块,socket.io都是高度封装之后 ...

  10. 【Part1】用JS写一个Blog(node + vue + mongoDB)

    学习JS也有一段时间了,准备试着写一个博客项目,前后端分离开发,后端用node只提供数据接口,前端用vue-cli脚手架搭建,路由也由前端控制,数据异步交互用vue的一个插件vue-resourse来 ...

随机推荐

  1. 【Oracle】Oracle数据库多实例安装

    需求:因为需要从RAC的多实例迁移至单虚拟机的多实例.因此,简要概述一下,如何安装数据库的多实例. 不管是Oracle 11g还是10g的多实例,其基本思路都是一致的. 1.调用dbca 在root账 ...

  2. LLM生态下爬虫程序的现状与未来

    最近出现一批与LLM有关的新的爬虫框架,一类是为LLM提供内容抓取解析的,比如 Jina Reader 和 FireCrawl ,可以将抓取的网页解析为markdown这样的对LLM友好的内容,例如m ...

  3. ShareConnect即将寿终正寝 Splashtop远程桌面会是最好的替代品

    大家好,我是没有感情的翻译机器人,又见面了.同类产品ShareConnect即将退市,官方大大搞了个新闻稿.君叫臣翻,臣不得不翻.------没有感情的分割线------ShareConnect的使用 ...

  4. ASP.NET Core如何禁用模型验证(或者从模型状态中移除某些属性)?

    这是一篇4年前的文章:[经验分享]在ASP.NET Core中,如果禁用某个请求的模型验证? 事隔多年,又有网友问到这个问题.我就来重新整理一下,顺便扩展一下之前的解决办法. ===== 这是一个来自 ...

  5. java学习之旅(day.07)

    面向对象编程(oop) 面向过程思想:线性思维 步骤清晰简单,每一步做什么很明确 适合处理较为简单地问题 面向对象思想:总分 抽象 属性+方法=类 分类的思维模式,思考问题首先会解决问题需要哪些分类, ...

  6. js与jquery实例-拖动改变列宽和行高

    js与jquery实例-拖动改变列宽和行高 如何通过javascript或者jquery实现改变表格宽度或者行高的功能?今天就把这个功能代码分享给大家,绝对原创哦,代码少而且易懂.先看效果图: htm ...

  7. 可以把erp当做一个分支-找自己的方向

    之前一直在寻思自己应该做哪些方面,对所有编程的问题都在研究,又看到自己研究不透.现在,某一时刻看到,可以把erp当做一个分支. 就像游戏里的天赋点一样,进入这个分支... 这是一个专一的方面,和编程普 ...

  8. CSS——组合选择器

    1.后代选择器(包括儿子和孙子) .c1 .c2{ color: red; } 2.子代选择器(只选择儿子) .c3 > .c5{ color: red; } 3.与选择器 选择p标签下面的.c ...

  9. C# wpf 使用GDI+实现截屏

    wpf截屏系列第一章 使用GDI+实现截屏(本章)第二章 使用DockPanel制作截屏框第三章 实现截屏框实时截屏第四章 使用ffmpeg命令行实现录屏 文章目录wpf截屏系列前言一.引用Syste ...

  10. nginx+php,nginx+tomcat动静分离实战

    1. 动静分离实战 1.1.1 nginx+tomcat 动静分离 主机 用途 10.0.0.63 tomcat服务器 10.0.0.64 nginx服务器 1.1.2 安装 java+tomcat环 ...