自己写一个 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来 ...
随机推荐
- 【Oracle】Oracle数据库多实例安装
需求:因为需要从RAC的多实例迁移至单虚拟机的多实例.因此,简要概述一下,如何安装数据库的多实例. 不管是Oracle 11g还是10g的多实例,其基本思路都是一致的. 1.调用dbca 在root账 ...
- LLM生态下爬虫程序的现状与未来
最近出现一批与LLM有关的新的爬虫框架,一类是为LLM提供内容抓取解析的,比如 Jina Reader 和 FireCrawl ,可以将抓取的网页解析为markdown这样的对LLM友好的内容,例如m ...
- ShareConnect即将寿终正寝 Splashtop远程桌面会是最好的替代品
大家好,我是没有感情的翻译机器人,又见面了.同类产品ShareConnect即将退市,官方大大搞了个新闻稿.君叫臣翻,臣不得不翻.------没有感情的分割线------ShareConnect的使用 ...
- ASP.NET Core如何禁用模型验证(或者从模型状态中移除某些属性)?
这是一篇4年前的文章:[经验分享]在ASP.NET Core中,如果禁用某个请求的模型验证? 事隔多年,又有网友问到这个问题.我就来重新整理一下,顺便扩展一下之前的解决办法. ===== 这是一个来自 ...
- java学习之旅(day.07)
面向对象编程(oop) 面向过程思想:线性思维 步骤清晰简单,每一步做什么很明确 适合处理较为简单地问题 面向对象思想:总分 抽象 属性+方法=类 分类的思维模式,思考问题首先会解决问题需要哪些分类, ...
- js与jquery实例-拖动改变列宽和行高
js与jquery实例-拖动改变列宽和行高 如何通过javascript或者jquery实现改变表格宽度或者行高的功能?今天就把这个功能代码分享给大家,绝对原创哦,代码少而且易懂.先看效果图: htm ...
- 可以把erp当做一个分支-找自己的方向
之前一直在寻思自己应该做哪些方面,对所有编程的问题都在研究,又看到自己研究不透.现在,某一时刻看到,可以把erp当做一个分支. 就像游戏里的天赋点一样,进入这个分支... 这是一个专一的方面,和编程普 ...
- CSS——组合选择器
1.后代选择器(包括儿子和孙子) .c1 .c2{ color: red; } 2.子代选择器(只选择儿子) .c3 > .c5{ color: red; } 3.与选择器 选择p标签下面的.c ...
- C# wpf 使用GDI+实现截屏
wpf截屏系列第一章 使用GDI+实现截屏(本章)第二章 使用DockPanel制作截屏框第三章 实现截屏框实时截屏第四章 使用ffmpeg命令行实现录屏 文章目录wpf截屏系列前言一.引用Syste ...
- 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环 ...