自己写一个 NODE/ATTR 的结构
## 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 的结构的更多相关文章
- javascript如何用递归写一个简单的树形结构
现在有一个数据,需要你渲染出对应的列表出来: var data = [ {"id":1}, {"id":2}, {"id":3}, {&qu ...
- (网页)javascript如何用递归写一个简单的树形结构
转自博客园: 现在有一个数据,需要你渲染出对应的列表出来: var data = [ {"id":1}, {"id":2}, {"id":3 ...
- 用node.js从零开始去写一个简单的爬虫
如果你不会Python语言,正好又是一个node.js小白,看完这篇文章之后,一定会觉得受益匪浅,感受到自己又新get到了一门技能,如何用node.js从零开始去写一个简单的爬虫,十分钟时间就能搞定, ...
- 用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话
wechatBot 微信每日说,每日自动发送微信消息给你心爱的人 项目介绍 灵感来源 在掘金看到了一篇<用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件>后, 在评论区偶然 ...
- 使用 Node.js 写一个代码生成器
背景 第一次接触代码生成器用的是动软代码生成器,数据库设计好之后,一键生成后端 curd代码.之后也用过 CodeSmith , T4.目前市面上也有很多优秀的代码生成器,而且大部分都提供可视化界面操 ...
- 从 0 到 1 到完美,写一个 js 库、node 库、前端组件库
之前讲了很多关于项目工程化.前端架构.前端构建等方面的技术,这次说说怎么写一个完美的第三方库. 1. 选择合适的规范来写代码 js 模块化的发展大致有这样一个过程 iife => commonj ...
- 【原创】只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(四)
全系列Index: [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(一) [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(二) [原创]只学到二维数组和结构体,不用链表也能 ...
- 使用node.js 文档里的方法写一个web服务器
刚刚看了node.js文档里的一个小例子,就是用 node.js 写一个web服务器的小例子 上代码 (*^▽^*) //helloworld.js// 使用node.js写一个服务器 const h ...
- 使用原生node写一个聊天室
在学习node的时候都会练习做一个聊天室的项目,主要使用socket.io模块和http模块.这里我们使用更加原始的方式去写一个在命令行聊天的聊天室. http模块,socket.io都是高度封装之后 ...
- 【Part1】用JS写一个Blog(node + vue + mongoDB)
学习JS也有一段时间了,准备试着写一个博客项目,前后端分离开发,后端用node只提供数据接口,前端用vue-cli脚手架搭建,路由也由前端控制,数据异步交互用vue的一个插件vue-resourse来 ...
随机推荐
- 01、Java 安全-反序列化基础
Java 反序列化基础 1.ObjectOutputStream 与 ObjectInputStream类 1.1.ObjectOutputStream类 java.io.ObjectOutputSt ...
- WEB服务与NGINX(23)- nginx的四层负载均衡功能
目录 1. nginx实现四层的负载均衡 1.1 nginx四层负载配置参数 1.2 负载均衡示例-mariadb 1. nginx实现四层的负载均衡 nginx从1.9.0版本开始支持TCP模式的负 ...
- mysql如何优雅的备份数据
MySQL 有多种备份方式,以下是几种常用的备份方式: 使用 mysqldump 命令备份数据 mysqldump 是 MySQL 自带的备份工具,可以备份指定数据库或表的数据为 SQL 文件.可以通 ...
- 使用systemctl管理服务(nginx)
首先调整好路径信息,修改配置文件vim /usr/lib/systemd/system/nginx.service [Unit]Description=The nginx HTTP and rever ...
- 生物医学顶刊论文(JBHI-2024):TransFOL:药物相互作用中复杂关系推理的逻辑查询模型
(2024.5.17)JBHI-TransFOL:药物相互作用中复杂关系推理的逻辑查询模型 论文题目:TransFOL: A Logical Query Model for Complex Relat ...
- QShop商城--项目介绍
QShop商城-项目介绍 QShop商城,是全新推出的一款轻量级.高性能.前后端分离的电商系统,支持微信小程序,前后端源码100%开源,完美支持二次开发,让您快速搭建个性化独立商城. 技术架构:.Ne ...
- 将python文件转换成exe可执行文件
一.安装Pyinstaller pip install pyinstaller(Pyinstaller) 二.找到 .py文件的路径并执行如下命令 pyinstaller -F 要转换的文件.py 三 ...
- 深入探讨Function Calling:实现外部函数调用的工作原理
引言 Function Calling 是一个允许大型语言模型(如 GPT)在生成文本的过程中调用外部函数或服务的功能. Function Calling允许我们以 JSON 格式向 LLM 模型描述 ...
- OPA Gatekeeper:Kubernetes的策略和管理
目录 一.系统环境 二.前言 三.OPA Gatekeeper简介 四.在kubernetes上安装OPA Gatekeeper 五.gatekeeper规则 5.1 使用gatekeeper禁止某些 ...
- 关于正在开发中的DjangoStarter v3版本
前言 最近做的这个项目大量使用了 python 及其相关的生态,因此自然而然选择了我的 DjangoStarter 作为后端框架 之前 v2 版本是用 RestFramework 做接口的,后面我试用 ...