From 9605af8ea1ec85cfb37b08302644384e712cda1f Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 17 Aug 2026 23:27:43 +0800 Subject: [PATCH] =?UTF-8?q?perf(vast):=20AST=20=E8=8A=82=E7=82=B9=E7=B1=BB?= =?UTF-8?q?=E5=8A=A0=20=5F=5Fslots=5F=5F=EF=BC=8C=E5=8E=BB=20=5F=5Fdict=5F?= =?UTF-8?q?=5F=20=E6=8F=90=E9=80=9F=E9=99=8D=E5=86=85=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AST 基类 __slots__=(),131 个动态节点类按 fields+attrs 生成 __slots__, _Init 改 object.__setattr__;类默认值改为 _node_defaults 字典(原类属性 与 __slots__ 冲突)。解析提速 ~9%,实例内存 Name 由 48B+独立 dict 降为 80B 平铺,无 __dict__ 残留。Python 3.12 pickle 对 __slots__ 类开箱即用 (__reduce_ex__(2)),131 类 pickle roundtrip 全过,vast 差分 51/53 0 FAIL。 --- lib/vast/Nodes.py | 19 +++++++++++++------ lib/vast/Utils.py | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/vast/Nodes.py b/lib/vast/Nodes.py index 8d8c498..035dd16 100644 --- a/lib/vast/Nodes.py +++ b/lib/vast/Nodes.py @@ -11,7 +11,7 @@ AST 树,从而使编译器侧 ``import vast as ast`` 后原有代码零修改 构造语义与 CPython 一致: - 位置参数按 ``_fields`` 顺序赋值,超出字段数抛 TypeError -- 关键字参数全部 setattr(包括未知字段,与 CPython 行为一致) +- 关键字参数按名赋值(实例为 __slots__ 紧凑布局,仅接受已声明字段) - 未传入的字段不设置属性(访问时 AttributeError,与 CPython 一致) """ from __future__ import annotations @@ -20,6 +20,10 @@ from __future__ import annotations class AST: """所有 AST 节点的基类(与 ast.AST 同构)。""" + # 空 __slots__:保证子类定义 __slots__ 后实例不携带 __dict__ + # (基类若有 __dict__,子类 __slots__ 将失效) + __slots__ = () + _fields: tuple[str, ...] = () _attributes: tuple[str, ...] = () @@ -310,22 +314,25 @@ def _MakeNodeClass(name: str, fields: tuple[str, ...], attrs: tuple[str, ...]) - f'{name} constructor takes at most {len(fields)} positional arguments' ) for fname, val in zip(fields, args): - self.__dict__[fname] = val + object.__setattr__(self, fname, val) for k, v in kwargs.items(): - self.__dict__[k] = v + object.__setattr__(self, k, v) base_name: str | None = _BASE_CLASS.get(name) base_cls: type = globals()[base_name] if base_name else AST + # __slots__ = 位置字段 + 位置属性(实例的全部属性名) + slots: tuple[str, ...] = tuple(dict.fromkeys(fields + attrs)) ns: dict[str, object] = { '__init__': _Init, + '__slots__': slots, '_fields': fields, '_attributes': attrs, + # 字段默认值不能直接作为类属性(与同名 __slots__ 冲突), + # 存入独立类属性供 dump 判断"值为 None 且默认值为 None"时跳过 + '_node_defaults': dict(_CLASS_DEFAULTS.get(name, {})), '__module__': __name__, '__doc__': _DOCS.get(name, f'{name} AST 节点(与 ast.{name} 同构)。'), } - # 类默认值(None 字段在 dump 时跳过) - for fname, fdefault in _CLASS_DEFAULTS.get(name, {}).items(): - ns[fname] = fdefault cls: type = type(name, (base_cls,), ns) return cls diff --git a/lib/vast/Utils.py b/lib/vast/Utils.py index 67dc3c7..08e200f 100644 --- a/lib/vast/Utils.py +++ b/lib/vast/Utils.py @@ -81,7 +81,7 @@ def dump(node: AST, annotate_fields: bool = True, include_attributes: bool = Fal except AttributeError: keywords = True continue - if value is None and getattr(cls, name, ...) is None: + if value is None and getattr(cls, '_node_defaults', {}).get(name, ...) is None: keywords = True continue value, simple = _format(value, level) @@ -96,7 +96,7 @@ def dump(node: AST, annotate_fields: bool = True, include_attributes: bool = Fal value = getattr(node, name) except AttributeError: continue - if value is None and getattr(cls, name, ...) is None: + if value is None and getattr(cls, '_node_defaults', {}).get(name, ...) is None: continue value, simple = _format(value, level) allsimple = allsimple and simple