## 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. 书生浦语大模型全链路开源体系-书生浦语大模型实战营学习笔记1&大语言模型2

    大语言模型-2.书生浦语大模型全链路开源体系 书生浦语大模型实战营学习笔记-1.认识书生浦语大模型全链路开源体系 本系列随笔学习搬运第二期书生浦语大模型实战营的相关内容,通过使用InternLM的一套 ...

  2. postgresql数据库清理

    大量update或者delete后 磁盘空间会猛增.原理是postgresql并没有真正的删除 只是将删除数据的状态置为已删除,该空间不能记录被从新使用.若是删除的记录位于表的末端,其所占用的空间将会 ...

  3. IDEA+carbon.now.sh安装使用

    安装 打开IDEA,选择setting-->plugins 搜索carbon.now.sh,点击安装,重启IDEA即可. 使用 选择需要生成的代码,Ctrl+A全选.然后再代码中点击右键,找到o ...

  4. RediSearch的简单使用与总结

    前言 之前就有考虑过想要研究下RediSearch,号称高性能全文索引的功能,这几天闲来无事调研了一番. RediSearch 介绍 RediSearch 是 Redis Labs 提供的一款强大且高 ...

  5. VRRP 虚拟路由器冗余协议

    目录 文章目录 目录 为什么要使用 VRRP 技术? VRRP VRRP 的概念 VRRP 的工作原理 VRRP 的状态机 VRRP 的工作过程 VRRP 的选举机制 VRRP 的报文格式 VRRP ...

  6. python ddddocr图片验证码详解

    安装 下载地址:https://pypi.tuna.tsinghua.edu.cn/simple/ddddocr/ 安装命令: pip install D:\ChromeCoreDownloads\d ...

  7. 利用FileReader进行二进制文件传输

    一.读取本地二进制文件,上传(数据库文件为例) 二进制文件读取的时候应当直接读取成字节数组,以免在调试时造成误解.比如数据库文件里面的有些字段是utf8编码,因此,采用utf-8编码读出来也能看到一些 ...

  8. 使用docker 5分钟搭建一个博客(mysql+WordPress)

    目录 一.系统环境 二.前言 三.搭建博客 3.1 创建wordpress和mysql容器 3.2 在wordpress界面设置个人博客信息 3.3 WordPress容器创建命令的简化版本 一.系统 ...

  9. node child_process模块exec

    child_process是Node.js自带的核心模块之一,无需额外安装即可使用. child_process模块提供了创建子进程的功能,可以在Node.js中执行外部命令.脚本文件等,并与其进行交 ...

  10. React jsx中js表达式

    嵌入JS表达式 在jsx语法中,要把JS代码写到{ }中,所有标签必须要闭合. let num = 100 let bool = false; // JSX 语法 var myh1 = ( <d ...