如何向动态创建的类添加类型注释?

问题描述 投票:0回答:2

在一个应用程序中,我有生成动态类的代码,这大大减少了重复代码的数量。但是为 mypy 检查添加类型提示会导致错误。考虑以下示例代码(简化以关注相关位):

class Mapper:

    @staticmethod
    def action() -> None:
        raise NotImplementedError('Not yet implemnented')


def magic(new_name: str) -> type:

    cls = type('%sMapper' % new_name.capitalize(), (Mapper,), {})

    def action() -> None:
        print('Hello')

    cls.action = staticmethod(action)
    return cls


MyCls = magic('My')
MyCls.action()

用mypy检查这个会导致以下错误:

dynamic_type.py:15: error: "type" has no attribute "action"
dynamic_type.py:21: error: "type" has no attribute "action"

mypy 显然无法判断

type
调用的返回值是
Mapper
的子类,因此当我分配给它时,它会抱怨“type”没有属性“action”。

请注意,代码功能完美并且执行了预期的操作,但 mypy 仍然抱怨。

有没有办法将

cls
标记为
Mapper
的一种类型?我尝试简单地将
# type: Mapper
附加到创建类的行:

cls = type('%sMapper' % new_name.capitalize(), (Mapper,), {})  # type: Mapper

但是我收到以下错误:

dynamic_type.py:10: error: Incompatible types in assignment (expression has type "type", variable has type "Mapper")
dynamic_type.py:15: error: Cannot assign to a method
dynamic_type.py:15: error: Incompatible types in assignment (expression has type "staticmethod", variable has type "Callable[[], None]")
dynamic_type.py:16: error: Incompatible return value type (got "Mapper", expected "type")
dynamic_type.py:21: error: "type" has no attribute "action"
python mypy
2个回答
9
投票

一种可能的解决方案基本上是:

  1. 使用预期的输入和输出类型键入您的
    magic
    函数
  2. 通过明智地使用
    magic
    Any
     使 
    # type: ignore
  3. 函数的内容动态键入

例如,这样的事情会起作用:

class Mapper:
    @staticmethod
    def action() -> None:
        raise NotImplementedError('Not yet implemnented')


def magic(new_name: str) -> Mapper:

    cls = type('%sMapper' % new_name.capitalize(), (Mapper,), {})

    def action() -> None:
        print('Hello')

    cls.action = staticmethod(action)  # type: ignore
    return cls  # type: ignore


MyCls = magic('My')
MyCls.action()

将代码库的一部分保留为动态类型可能看起来有点令人反感,但在这种情况下,我认为没有什么可以避免的:mypy(和 PEP 484 类型生态系统)故意不尝试处理超动态代码像这样。

相反,您能做的最好的事情就是干净地记录“静态”接口,添加单元测试,并将代码的动态部分限制在尽可能小的区域内。


0
投票

供将来参考,这是解决 Python 3.9 起的 mypy 问题的正确方法:

class Mapper:

    @staticmethod
    def action() -> None:
        raise NotImplementedError('Not yet implemnented')


def magic(new_name: str) -> type[Mapper]:

    cls: type[Mapper] = type('%sMapper' % new_name.capitalize(), (Mapper,), {})

    def action() -> None:
        print('Hello')

    setattr(cls, "action", staticmethod(action))
    return cls


MyCls = magic('My')
MyCls.action()

在 3.9 版本之前,除了注释中不使用

type
之外,其他都是相同的,您可以使用
typing.Type

© www.soinside.com 2019 - 2024. All rights reserved.