399 lines
14 KiB
Python
399 lines
14 KiB
Python
|
|
import functools
|
|||
|
|
import inspect
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
from typing import Any, Callable, get_type_hints
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
# 数据结构定义
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class ParamInfo:
|
|||
|
|
"""单个参数的元信息"""
|
|||
|
|
name: str
|
|||
|
|
annotation: type | None
|
|||
|
|
default: Any
|
|||
|
|
kind: str
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def has_default(self) -> bool:
|
|||
|
|
return self.default is not inspect.Parameter.empty
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def type_name(self) -> str:
|
|||
|
|
if self.annotation is None:
|
|||
|
|
return "Any"
|
|||
|
|
return getattr(self.annotation, "__name__", str(self.annotation))
|
|||
|
|
|
|||
|
|
def __repr__(self):
|
|||
|
|
default_str = f" = {self.default!r}" if self.has_default else ""
|
|||
|
|
return f"{self.name}: {self.type_name}{default_str}"
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class CommandInfo:
|
|||
|
|
"""函数完整元信息"""
|
|||
|
|
name: str # 注册名
|
|||
|
|
qualname: str # 限定名,如 DroneController.takeoff
|
|||
|
|
module: object
|
|||
|
|
doc: str | None
|
|||
|
|
params: list[ParamInfo]
|
|||
|
|
return_annotation: type | None
|
|||
|
|
func: Callable
|
|||
|
|
is_method: bool = False # 是否为类方法
|
|||
|
|
class_name: str | None = None # 所属类名
|
|||
|
|
method_type: str = "method" # method / classmethod / staticmethod
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def return_type_name(self) -> str:
|
|||
|
|
if self.return_annotation is None:
|
|||
|
|
return "Any"
|
|||
|
|
return getattr(self.return_annotation, "__name__", str(self.return_annotation))
|
|||
|
|
|
|||
|
|
def signature(self) -> str:
|
|||
|
|
params_str = ", ".join(repr(p) for p in self.params)
|
|||
|
|
prefix = f"{self.class_name}." if self.class_name else ""
|
|||
|
|
return f"{prefix}{self.name}({params_str}) -> {self.return_type_name}"
|
|||
|
|
|
|||
|
|
def to_dict(self) -> dict:
|
|||
|
|
return {
|
|||
|
|
"name": self.name,
|
|||
|
|
"qualname": self.qualname,
|
|||
|
|
"module": self.module,
|
|||
|
|
"doc": self.doc,
|
|||
|
|
"is_method": self.is_method,
|
|||
|
|
"class_name": self.class_name,
|
|||
|
|
"method_type": self.method_type,
|
|||
|
|
"return_type": self.return_type_name,
|
|||
|
|
"params": [
|
|||
|
|
{
|
|||
|
|
"name": p.name,
|
|||
|
|
"type": p.type_name,
|
|||
|
|
"default": p.default if p.has_default else "<required>",
|
|||
|
|
"kind": p.kind,
|
|||
|
|
}
|
|||
|
|
for p in self.params
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
# 注册表(单例)
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
class CommandRegistry:
|
|||
|
|
"""全局函数注册表"""
|
|||
|
|
|
|||
|
|
_instance: "CommandRegistry | None" = None
|
|||
|
|
|
|||
|
|
def __new__(cls):
|
|||
|
|
if cls._instance is None:
|
|||
|
|
cls._instance = super().__new__(cls)
|
|||
|
|
cls._instance._registry: dict[str, CommandInfo] = {}
|
|||
|
|
return cls._instance
|
|||
|
|
|
|||
|
|
def register(self, info: CommandInfo) -> None:
|
|||
|
|
self._registry[info.name] = info
|
|||
|
|
|
|||
|
|
def get(self, name: str) -> CommandInfo | None:
|
|||
|
|
return self._registry.get(name)
|
|||
|
|
|
|||
|
|
def all(self) -> dict[str, CommandInfo]:
|
|||
|
|
return dict(self._registry)
|
|||
|
|
|
|||
|
|
def names(self) -> list[str]:
|
|||
|
|
return list(self._registry.keys())
|
|||
|
|
|
|||
|
|
def by_class(self, class_name: str) -> list[CommandInfo]:
|
|||
|
|
"""查询某个类下注册的所有方法"""
|
|||
|
|
return [i for i in self._registry.values() if i.class_name == class_name]
|
|||
|
|
|
|||
|
|
def __repr__(self):
|
|||
|
|
lines = [f"CommandRegistry ({len(self._registry)} entries):"]
|
|||
|
|
# 按 class_name 分组展示
|
|||
|
|
groups: dict[str | None, list[CommandInfo]] = {}
|
|||
|
|
for info in self._registry.values():
|
|||
|
|
groups.setdefault(info.class_name, []).append(info)
|
|||
|
|
for cls_name, infos in groups.items():
|
|||
|
|
label = f"[class {cls_name}]" if cls_name else "[functions]"
|
|||
|
|
lines.append(f" {label}")
|
|||
|
|
for info in infos:
|
|||
|
|
tag = f"@{info.method_type}" if info.method_type != "method" else ""
|
|||
|
|
lines.append(f" {'⚙' if info.is_method else '•'} {info.signature()} {tag}")
|
|||
|
|
return "\n".join(lines)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 全局注册表实例
|
|||
|
|
registry = CommandRegistry()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
# 内部解析工具
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def _parse_func_info(
|
|||
|
|
func: Callable,
|
|||
|
|
reg_name: str,
|
|||
|
|
skip_params: set[str],
|
|||
|
|
method_type: str,
|
|||
|
|
) -> CommandInfo:
|
|||
|
|
"""从函数对象中提取完整元信息"""
|
|||
|
|
sig = inspect.signature(func)
|
|||
|
|
try:
|
|||
|
|
hints = get_type_hints(func)
|
|||
|
|
except Exception:
|
|||
|
|
hints = {}
|
|||
|
|
|
|||
|
|
params: list[ParamInfo] = []
|
|||
|
|
for param_name, param in sig.parameters.items():
|
|||
|
|
if param_name in skip_params:
|
|||
|
|
continue
|
|||
|
|
params.append(ParamInfo(
|
|||
|
|
name=param_name,
|
|||
|
|
annotation=hints.get(param_name),
|
|||
|
|
default=param.default,
|
|||
|
|
kind=param.kind.name,
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
# 从 qualname 推断类名,如 "MyClass.method" → "MyClass"
|
|||
|
|
parts = func.__qualname__.split(".")
|
|||
|
|
class_name = parts[-2] if len(parts) >= 2 and "<locals>" not in parts[-2] else None
|
|||
|
|
is_method = class_name is not None
|
|||
|
|
|
|||
|
|
return CommandInfo(
|
|||
|
|
name=reg_name,
|
|||
|
|
qualname=func.__qualname__,
|
|||
|
|
module=func,
|
|||
|
|
doc=inspect.getdoc(func),
|
|||
|
|
params=params,
|
|||
|
|
return_annotation=hints.get("return"),
|
|||
|
|
func=func,
|
|||
|
|
is_method=is_method,
|
|||
|
|
class_name=class_name,
|
|||
|
|
method_type=method_type,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
# 装饰器实现
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def register(
|
|||
|
|
name: str | None = None,
|
|||
|
|
*,
|
|||
|
|
override: bool = False,
|
|||
|
|
registry: CommandRegistry = registry
|
|||
|
|
) -> Callable:
|
|||
|
|
"""
|
|||
|
|
函数/方法注册装饰器。
|
|||
|
|
|
|||
|
|
支持以下所有场景:
|
|||
|
|
- 普通函数
|
|||
|
|
- 实例方法(自动跳过 self)
|
|||
|
|
- 类方法 @classmethod(自动跳过 cls)
|
|||
|
|
- 静态方法 @staticmethod
|
|||
|
|
|
|||
|
|
注册名默认为函数名,类方法会额外记录所属类名。
|
|||
|
|
对于类方法,需将 @register 放在 @classmethod / @staticmethod 内侧。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
name: 自定义注册名,默认使用函数名
|
|||
|
|
override: 是否允许覆盖已注册的同名函数
|
|||
|
|
registry:
|
|||
|
|
|
|||
|
|
Usage:
|
|||
|
|
# 普通函数
|
|||
|
|
@register
|
|||
|
|
def foo(x: int) -> bool: ...
|
|||
|
|
|
|||
|
|
# 实例方法
|
|||
|
|
class MyClass:
|
|||
|
|
@register
|
|||
|
|
def bar(self, x: int) -> None: ...
|
|||
|
|
|
|||
|
|
# 类方法(@register 在内侧)
|
|||
|
|
class MyClass:
|
|||
|
|
@classmethod
|
|||
|
|
@register
|
|||
|
|
def baz(cls, x: int) -> None: ...
|
|||
|
|
|
|||
|
|
# 静态方法(@register 在内侧)
|
|||
|
|
class MyClass:
|
|||
|
|
@staticmethod
|
|||
|
|
@register
|
|||
|
|
def qux(x: int) -> None: ...
|
|||
|
|
|
|||
|
|
# 自定义名称
|
|||
|
|
@register(name="my_func")
|
|||
|
|
def some_func(x: float) -> None: ...
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def decorator(func: Callable) -> Callable:
|
|||
|
|
# ── 处理 classmethod / staticmethod 描述符 ──────
|
|||
|
|
# 当 @register 在 @classmethod 内侧时,func 是原始函数
|
|||
|
|
# 当 @register 在 @classmethod 外侧时,func 是 classmethod 描述符
|
|||
|
|
method_type = "method"
|
|||
|
|
raw_func = func
|
|||
|
|
|
|||
|
|
if isinstance(func, classmethod):
|
|||
|
|
method_type = "classmethod"
|
|||
|
|
raw_func = func.__func__
|
|||
|
|
elif isinstance(func, staticmethod):
|
|||
|
|
method_type = "staticmethod"
|
|||
|
|
raw_func = func.__func__
|
|||
|
|
|
|||
|
|
# 确定注册名
|
|||
|
|
reg_name = name or raw_func.__name__
|
|||
|
|
|
|||
|
|
# 重复注册检查
|
|||
|
|
if not override and reg_name in registry.all():
|
|||
|
|
raise ValueError(
|
|||
|
|
f"[Registry] '{reg_name}' 已注册。"
|
|||
|
|
f"如需覆盖请使用 @register(override=True)"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 确定需要跳过的隐式参数
|
|||
|
|
skip_params: set[str] = set()
|
|||
|
|
if method_type == "method":
|
|||
|
|
skip_params = {"self"}
|
|||
|
|
elif method_type == "classmethod":
|
|||
|
|
skip_params = {"cls"}
|
|||
|
|
|
|||
|
|
# 解析并注册
|
|||
|
|
info = _parse_func_info(raw_func, reg_name, skip_params, method_type)
|
|||
|
|
registry.register(info)
|
|||
|
|
|
|||
|
|
# 将注册信息挂载到原始函数
|
|||
|
|
raw_func.__registry_info__ = info
|
|||
|
|
|
|||
|
|
# ── 根据方法类型返回正确的包装器 ────────────────
|
|||
|
|
if isinstance(func, classmethod):
|
|||
|
|
# 重新包装为 classmethod
|
|||
|
|
@functools.wraps(raw_func)
|
|||
|
|
def cm_wrapper(cls_arg, *args, **kwargs):
|
|||
|
|
return raw_func(cls_arg, *args, **kwargs)
|
|||
|
|
cm_wrapper.__registry_info__ = info
|
|||
|
|
return classmethod(cm_wrapper)
|
|||
|
|
|
|||
|
|
elif isinstance(func, staticmethod):
|
|||
|
|
# 重新包装为 staticmethod
|
|||
|
|
@functools.wraps(raw_func)
|
|||
|
|
def sm_wrapper(*args, **kwargs):
|
|||
|
|
return raw_func(*args, **kwargs)
|
|||
|
|
sm_wrapper.__registry_info__ = info
|
|||
|
|
return staticmethod(sm_wrapper)
|
|||
|
|
|
|||
|
|
else:
|
|||
|
|
# 普通函数 / 实例方法
|
|||
|
|
@functools.wraps(raw_func)
|
|||
|
|
def wrapper(*args, **kwargs):
|
|||
|
|
return raw_func(*args, **kwargs)
|
|||
|
|
wrapper.__registry_info__ = info
|
|||
|
|
return wrapper
|
|||
|
|
|
|||
|
|
# ── 支持 @register 和 @register() 两种写法 ──────────
|
|||
|
|
if callable(name):
|
|||
|
|
func, name = name, None
|
|||
|
|
return decorator(func)
|
|||
|
|
|
|||
|
|
return decorator
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
# 使用示例
|
|||
|
|
# ─────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
|
|||
|
|
# ── 普通函数 ────────────────────────────────────────
|
|||
|
|
@register
|
|||
|
|
def emergency_stop() -> None:
|
|||
|
|
"""全局紧急停止"""
|
|||
|
|
print("紧急停止!")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── 类:实例方法 + 类方法 + 静态方法 ────────────────
|
|||
|
|
class DroneController:
|
|||
|
|
|
|||
|
|
@register
|
|||
|
|
def takeoff(self, altitude: float, speed: float = 2.0) -> bool:
|
|||
|
|
"""控制无人机起飞"""
|
|||
|
|
print(f"起飞至 {altitude}m,速度 {speed}m/s")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
@register
|
|||
|
|
def goto(self, lat: float, lon: float, alt: float = 50.0) -> None:
|
|||
|
|
"""飞往指定 GPS 坐标"""
|
|||
|
|
print(f"飞往 ({lat}, {lon}),高度 {alt}m")
|
|||
|
|
|
|||
|
|
@register
|
|||
|
|
def land(self) -> bool:
|
|||
|
|
"""降落"""
|
|||
|
|
print("降落中...")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
@register # @register 在 @classmethod 内侧
|
|||
|
|
def create(cls, device_id: str) -> "DroneController":
|
|||
|
|
"""工厂方法:创建控制器实例"""
|
|||
|
|
return cls()
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
@register # @register 在 @staticmethod 内侧
|
|||
|
|
def ping(host: str, timeout: int = 3) -> bool:
|
|||
|
|
"""检测设备连通性"""
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── 另一个类 ────────────────────────────────────────
|
|||
|
|
class DogRobot:
|
|||
|
|
|
|||
|
|
@register
|
|||
|
|
def walk(self, speed: float = 1.0, direction: float = 0.0) -> None:
|
|||
|
|
"""四足机器人行走"""
|
|||
|
|
print(f"行走:速度={speed}, 方向={direction}°")
|
|||
|
|
|
|||
|
|
@register(name="dog_trot") # 自定义注册名
|
|||
|
|
def trot(self, speed: float = 2.5) -> None:
|
|||
|
|
"""四足机器人小跑"""
|
|||
|
|
print(f"小跑:速度={speed}")
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
@register
|
|||
|
|
def get_supported_gaits() -> list:
|
|||
|
|
"""获取支持的步态列表"""
|
|||
|
|
return ["walk", "trot", "bound"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── 打印注册表 ───────────────────────────────────────
|
|||
|
|
print("=" * 65)
|
|||
|
|
print(registry)
|
|||
|
|
print()
|
|||
|
|
|
|||
|
|
# ── 按类查询 ─────────────────────────────────────────
|
|||
|
|
print("=" * 65)
|
|||
|
|
print("DroneController 注册方法:")
|
|||
|
|
for info in registry.by_class("DroneController"):
|
|||
|
|
print(f" [{info.method_type:11s}] {info.signature()}")
|
|||
|
|
print()
|
|||
|
|
|
|||
|
|
# ── 查询单个方法详情 ──────────────────────────────────
|
|||
|
|
print("=" * 65)
|
|||
|
|
info = registry.get("takeoff")
|
|||
|
|
print(f"函数名: {info.name}")
|
|||
|
|
print(f"限定名: {info.qualname}")
|
|||
|
|
print(f"所属类: {info.class_name}")
|
|||
|
|
print(f"方法类型: {info.method_type}")
|
|||
|
|
print(f"文档: {info.doc}")
|
|||
|
|
print(f"签名: {info.signature()}")
|
|||
|
|
print("参数详情:")
|
|||
|
|
for p in info.params:
|
|||
|
|
print(f" {repr(p)} [kind={p.kind}]")
|
|||
|
|
print()
|
|||
|
|
|
|||
|
|
# ── JSON 导出 ─────────────────────────────────────────
|
|||
|
|
import json
|
|||
|
|
print("=" * 65)
|
|||
|
|
print("JSON 导出(takeoff):")
|
|||
|
|
print(json.dumps(info.to_dict(), ensure_ascii=False, indent=2))
|