perf(vast): AST 节点类加 __slots__,去 __dict__ 提速降内存
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。
This commit is contained in:
1 parent
50c0703afd
commit
9605af8ea1
2 files changed
+15
-8
No files matched your search
+13
-6
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
Reference in new issue
Block a user