使用自定义 Metaclass 指定 class 的类型

Specify Typing of class with custom Metaclass

在一个伟大的系统上使用类似枚举的 Django 选择替换 (http://musings.tinbrain.net/blog/2017/may/15/alternative-enum-choices/) 我有一个项目使用 class 和自定义元 class 允许我要做 list(MyChoices)(在 Class 本身上)以获取所有枚举选项的列表。代码的相关部分如下所示:

class MetaChoices(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        return OrderedDict()

    def __new__(mcs, name, bases, attrs):
        _choices = OrderedDict()
        for attr_name, value in list(attrs.items()):
            ...do things...
        return type.__new__(mcs, name, bases, dict(attrs))

    def __iter__(cls):
        return iter(cls._choices.items())


class Choices(metaclass=MetaChoices):
    pass

class IceCreamFlavor(Choices):
    STRAWBERRY = ('strawberry', 'Fruity')
    CHOCOLATE = 'chocolate'

list(IceCreamFlavor)
# [('strawberry', 'Fruity'), ('chocolate', Chocolate')

代码已经运行了一段时间,但现在我打开了输入(在本例中使用 PyCharm 的类型检查器,但也在寻找通用解决方案),并且 IceCreamFlavor 没有被标记为可迭代的,尽管它是从 class 派生的,其 metaclass 将 cls 定义为具有 __iter__ 方法。有谁知道我可以证明 Choices class 本身就是一个可迭代的解决方案?

我修复了代码以使其适用于 MyPy(通过首先添加注释文件 *.pyi 的 Pytype 检查更容易)。

方法 __iter__() 中存在打字问题,属性 _choices 对于检查器而言似乎未定义,因为它不是透明分配的,仅由 attrs['_choices'] = ....

可以加一行注释:

class MetaChoices(type):
    _choices = None  # type: dict   # written as comment for Python >= 3.5
    # _choices: dict                # this line can be uncommented if Python >= 3.6

它对 Pytype 完全有效,并且它的注释当然也被 MyPY 检查有效。

也许 __iter__() 中的输入问题可能导致元类方法在检查器中被忽略。


如果修复没有帮助,则可以使用以下简化示例报告问题:

class MetaChoices(type):
    _choices = {0: 'a'}

    def __iter__(cls):
        return iter(cls._choices.items())


class Choices(metaclass=MetaChoices):
    pass


assert list(Choices) == [(0, 'a')]

我向原始文章报告了另一个小错误。该错误与此问题无关。