"""极简注册表:按名字注册 / 构建模型、数据集等组件。""" from __future__ import annotations import inspect from collections.abc import Callable, Iterator from typing import Any, TypeVar T = TypeVar("T") class Registry: def __init__(self, name: str) -> None: self.name = name self._entries: dict[str, Callable[..., Any]] = {} def register(self, name: str | None = None) -> Callable[[T], T]: """装饰器;`name` 缺省时使用被装饰对象的 __name__。""" def wrapper(obj: T) -> T: key = name or getattr(obj, "__name__", None) if key is None: raise ValueError(f"无法推断注册名: {obj!r}") if key in self._entries: raise KeyError(f"{self.name} 中已存在同名条目: {key}") self._entries[key] = obj # type: ignore[assignment] return obj return wrapper def get(self, name: str) -> Callable[..., Any]: if name not in self._entries: raise KeyError(f"{self.name} 中找不到 '{name}',可用: {sorted(self._entries)}") return self._entries[name] def build(self, cfg: dict, **kwargs: Any) -> Any: """按 `{"name": ..., 其余作为构造参数}` 构建对象。""" cfg = dict(cfg) name = cfg.pop("name", None) if name is None: raise KeyError(f"构建 {self.name} 需要 'name' 字段: {cfg}") target = self.get(name) params = {**cfg, **kwargs} self._check_params(name, target, params) return target(**params) @staticmethod def _check_params(name: str, target: Callable[..., Any], params: dict) -> None: """提前挡下不认识的字段,给出比裸 TypeError 更可操作的提示。 最常见的触发方式:用 `--set xxx.name=其他骨架` 只改了名字, yaml 里原骨架的专有字段(如 tiny_conv 的 widths/depths)仍然留在配置里。 """ signature = inspect.signature(target) if any(p.kind is p.VAR_KEYWORD for p in signature.parameters.values()): return unknown = sorted(set(params) - set(signature.parameters)) if not unknown: return accepted = sorted(p for p in signature.parameters if p != "self") raise TypeError( f"构建 '{name}' 时收到它不接受的字段 {unknown}。它接受的是 {accepted}。" "如果你是用 --set 改了 name,注意 yaml 里原来那个组件的专有字段仍会保留——" "换组件请直接编辑配置文件、整块替换。" ) def __contains__(self, name: object) -> bool: return name in self._entries def __iter__(self) -> Iterator[str]: return iter(self._entries) def __len__(self) -> int: return len(self._entries)