如何让IDE识别Python中静态声明、动态创建类的期望类型名?

1 投票
1 回答
58 浏览
提问于 2025-04-11 23:06

问题:

我正在开发一个库,比如支持 UInt5 类型、Int33 类型等等。这个库比这要复杂一些,但为了举例,创建一个 UInt12 类型可能会这样做:

def makeUInt(size:int) -> type[UInt]:
   class NewUInt(UInt):
       # Do stuff with size
   NewUInt.__name__ = f"UInt{size}"
   return NewUInt
   
UInt12 = makeUInt(12)
an_example_number = UInt12(508)

我的开发环境(VS Code)的智能提示功能识别 an_example_number 的类型为 UInt,而不是 UInt12。

麻烦来了:

我并不指望动态声明的类型能被类型提示识别。然而,我已经明确指定了 UInt12 作为类型别名,实际上如果我使用子类而不是类型别名,像这样:

def makeUInt(size:int) -> type[UInt]:
   class NewUInt(UInt):
       # Do stuff with size
   NewUInt.__name__ = f"UInt{size}"
   return NewUInt
   
class UInt12(makeUInt(12)): pass
an_example_number = UInt12(508)

一切就能按预期工作,所以显然在某种程度上,动态声明可以被转化为 IDE 能理解的东西。

举个例子,我可以假设让 UInt 记录已创建的类,并阻止 UInt12(makeUInt(12)) 实际上成为子类。不过,这显然不是一个理想的解决办法。

请求:

我该如何(最好是在 Python 3.8 中)保留动态创建类型的优势,同时让 IDE 理解我对这些类型实例的命名方式?

最终的使用场景是,我想明确提供某些类型,而不需要每次都重新声明 # 使用大小信息做事情,这样像 UInt16、UInt32 等常见类型可以在我的库中声明并获得提示,而像 UInt13 这样的不常见类型则由用户根据需要声明,未必能获得提示。

背景信息:

def makeUInt(size:int) -> type[UInt]:
   class NewUInt(UInt):
       # Do stuff with size
   NewUInt.__name__ = f"UInt{size}"
   return NewUInt
   
UInt12 = makeUInt(12)
an_example_number = UInt12(508)

我希望 an_example_number 在类型提示中显示为 UInt12,但它显示为 UInt。

1 个回答

2

在你的情况下,明确地创建子类可能会更有效,就像你已经看到的那样。

与其创建一个类工厂函数,不如在基类中定义所有派生类的共同行为,然后让具体的实现依赖于子类中定义的类变量。

在一个和你的例子类似的情况下,可以这样写:

class UInt:
    size: int  # to be defined by subclasses

    def __init__(self, value: int):
        if value >= 2 ** self.size:
            raise ValueError("Too big")
        self._value = value

    def __repr__(self):
        return f"{self.__class__.__name__}({self._value})"


class UInt12(UInt):
    size = 12


an_example_number = UInt12(508)
print(an_example_number)  # UInt12(508)

撰写回答