You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为MicroPython类装饰器添加类型注解以通过mypy检查?

解决MicroPython可序列化类的mypy类型检查问题

问题根源

现有代码的类型问题主要集中在这几点:

  • 错误将元类ABCMeta作为实例类型使用,导致类型注解逻辑混乱
  • 装饰器缺少泛型类型注解,mypy无法推断返回类的具体类型
  • 动态添加的实例属性(如Test.a、Test.b)无法被mypy识别
  • deserialize方法的返回类型标注过于宽泛,不符合实际返回的具体类类型

修正方案

以下是调整后的代码,完全适配mypy严格类型检查:

from abc import ABC, abstractmethod
from typing import TypeVar, Type, Any, List, Tuple

# 定义泛型类型变量,绑定被装饰的具体类
T = TypeVar('T', bound='SerializableBase')

class SerializableBase(ABC):
    # 定义实例层的抽象方法,用ABC作为基类而非元类
    @abstractmethod
    def serialize(self) -> str:
        pass

    @staticmethod
    @abstractmethod
    def deserialize(data: str) -> 'SerializableBase':
        pass

def serializable(cls: Type[T]) -> Type[T]:
    # 校验fields存在并添加类型约束
    if not hasattr(cls, 'fields'):
        raise ValueError('fields is required')
    fields: List[Tuple[str, type]] = cls.fields

    # 重写__init__并添加类型注解,同时给实例注入属性注解
    def __init__(self: T, **kwargs: Any) -> None:
        for fname, _ in fields:
            if fname not in kwargs:
                raise ValueError(f'{fname} is required')
            setattr(self, fname, kwargs.get(fname))
        # 给实例添加属性注解,让mypy识别动态生成的属性
        self.__annotations__ = {fname: ftype for fname, ftype in fields}
    cls.__init__ = __init__

    # 修正serialize的self类型为具体泛型类T
    def serialize(self: T) -> str:
        return f"{cls.__name__} {' '.join(str(getattr(self, fname)) for fname, _ in fields)}"
    cls.serialize = serialize

    # 修正deserialize的返回类型为具体类T
    @staticmethod
    def deserialize(data: str) -> T:
        parts = data.split(' ')
        if parts[0] != cls.__name__:
            raise ValueError(f'invalid data not a {cls.__name__}')
        if len(parts) - 1 != len(fields):
            raise ValueError('invalid data not enough fields')
        return cls(**{fname: ftype(parts[i+1]) for i, (fname, ftype) in enumerate(fields)})
    cls.deserialize = deserialize

    # 确保类继承自SerializableBase(若未显式继承)
    if not issubclass(cls, SerializableBase):
        cls.__bases__ = (SerializableBase,) + cls.__bases__

    return cls

使用示例(适配mypy)

@serializable
class Test:
    # 给fields添加明确的类型标注
    fields: List[Tuple[str, type]] = [
        ('a', int),
        ('b', str)
    ]

x = Test(a=1, b='hello')
print(x.a)  # mypy可识别x.a类型为int
print(x.b)  # mypy可识别x.b类型为str

# 反序列化的类型可被正确推断
y = Test.deserialize('Test 1 world')
reveal_type(y)  # mypy会输出: Revealed type is "__main__.Test"

关键修改说明

  1. 重构抽象基类:将原元类改为实例层的抽象基类SerializableBase,用ABC作为基类,明确区分元类与实例类型的职责。
  2. 添加泛型支持:通过TypeVar绑定具体被装饰类,让装饰器能准确推断返回类的类型,包括deserialize的返回值类型。
  3. 显式类型标注:给fields、装饰器参数、方法参数添加明确类型注解,让mypy能解析结构逻辑。
  4. 动态属性识别:在__init__中给实例注入__annotations__,让mypy能识别动态生成的实例属性。
  5. 修正方法类型:将serialize的self类型从宽泛的基类改为具体泛型类T,确保类型推断精准。

内容的提问来源于stack exchange,提问作者sqrtroot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 00:52:51