类型工具

melobot.typ 提供了框架公开接口中反复出现的泛型、可调用对象协议、运行时类型工具、抽象基类和枚举。它们的主要目标是让类型注解、依赖注入和运行时校验使用同一套表达方式;多数情况下只在编写扩展组件或较通用的工具代码时需要直接使用。

通用泛型与可调用对象

TUV 是无约束的类型变量,T_co 是协变的无约束类型变量,P 是无约束的 ParamSpec。它们都以 Any 为默认值,方便在未显式提供泛型参数的公开接口中保持兼容。

from melobot.typ import AsyncCallable, P, T

def trace(func: AsyncCallable[P, T]) -> AsyncCallable[P, T]:
    async def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
        print(f"calling {func}")
        return await func(*args, **kwargs)
    return wrapped

这里 P 保留被包装函数的位置参数和关键字参数,T 保留协程的返回值类型。对“同步或异步均可”的回调,则使用 SyncOrAsyncCallable

类型

等价含义

AsyncCallable[P, T]

Callable[P, Awaitable[T]]

SyncOrAsyncCallable[P, T]

Callable[P, T | Awaitable[T]]

这两个是 Protocol,仅描述可调用对象的形状,并不负责把同步函数转换成协程。需要实际转换时,使用实用组件中的 to_async()to_coro()

运行时类型校验

Python 的 isinstance 只能处理运行时类,不能直接检查 list[str]、联合类型或更复杂的类型提示。is_type() 接收一个对象和任意类型提示,并按类型提示进行运行时校验:

from melobot.typ import is_type

assert is_type(["a", "b"], list[str])
assert not is_type(1, str)
assert is_type("hello", str | bytes)

返回值带有 TypeIs 注解,因此静态类型检查器可以在判断通过的分支中收窄对象类型。它适合处理外部输入、插件扩展点或依赖注入内部的兼容性判断;对于已知的普通类继承关系,isinstance 通常更简单也更快。运行时泛型校验并不等价于逐项、完整的数据验证,尤其不应只凭一次容器类型判断就信任不受控的嵌套输入;需要严格验证时请使用 Pydantic 等专用校验工具。

is_subhint() 则比较两个类型提示之间的包含关系:

from melobot.typ import is_subhint

assert is_subhint(int, int | str)
assert not is_subhint(str, int)

它回答的是“前者能否作为后者的子类型”,并不检查任何具体对象。泛型可变性、Any、类型变量和第三方注解都会影响结果;将它用于接口兼容判断时,应针对自己的类型提示编写测试,而不要把它当成字符串比较工具。

抽象基类与单例元类

BetterABCBetterABCMeta 兼容标准库抽象类机制,并额外支持 abstractattr()。抽象属性不限制子类用类属性、实例属性还是 property 实现:

from melobot.typ import BetterABC, abstractattr

class Storage(BetterABC):
    name: str = abstractattr()

class MemoryStorage(Storage):
    name = "memory"

class FileStorage(Storage):
    def __init__(self, path: str) -> None:
        self.name = path

如果子类实例化后仍缺少声明为抽象属性的字段,BetterABC 会拒绝实例化。它特别适合“实现形式可以不同,但最终必须提供某个数据属性”的扩展接口。

SingletonMeta 会让同一类的所有构造调用返回同一个实例;SingletonBetterABCMeta 则将单例语义与 BetterABCMeta 结合。它们适合框架级上下文管理器、进程内注册表等确实应只有一份状态的对象:

from melobot.typ import SingletonMeta

class Registry(metaclass=SingletonMeta):
    pass

assert Registry() is Registry()

单例只在当前 Python 进程内成立,不会解决多线程同步、多进程共享或测试间状态污染。业务对象默认应显式传递依赖,而不是为了方便随意设计成单例。

常用枚举与颜色

类型

作用

LogLevel

与标准库 logging 数值兼容的 DEBUGINFOWARNINGERRORCRITICAL

LogicMode

ANDORNOTXOR;检查器和匹配器组合时使用

Color

用于表示常用色、十六进制色或 RGB 色的值对象

Color 的常用色以类属性提供,其他颜色可通过十六进制或 RGB 创建:

from melobot.typ import Color

warning = Color.yellow
brand = Color("#7c3aed")
sky = Color(56, 189, 248)

assert brand.hex == "#7c3aed"
hue, saturation, lightness = sky.hsl

常用色没有唯一的 RGB、十六进制或 HSL 值,因此读取这些属性会抛出 AttributeError。大多数业务代码无需直接使用 Color;它主要服务于可组合的检查/匹配规则和渲染扩展。

总结

类型工具的重点不是为每段业务代码增加泛型,而是在编写可复用接口时准确表达“参数如何传递、返回值是否可等待、值是否满足某个类型提示”。运行时输入校验使用 is_type,接口提示兼容性判断使用 is_subhint;需要定义扩展基类或进程内协调器时,再考虑 BetterABC 与单例元类。

下一篇将介绍:导入与惰性加载