Укажите типизацию класса с помощью пользовательского метакласса - PullRequest
1 голос
/ 23 марта 2019

Следуя замечательной системе для использования замены, подобной перечислению, для выбора Django (http://musings.tinbrain.net/blog/2017/may/15/alternative-enum-choices/) У меня есть проект, который использует класс с пользовательским метаклассом, который позволяет мне делать list(MyChoices) (в самом классе), чтобы получить список всех вариантов перечисления. Соответствующая часть кода выглядит примерно так:

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 не помечается как итеративный, несмотря на то, что он получен из класса, метакласс которого определяет cls как имеющий метод __iter__. Кто-нибудь знаетрешения, в котором я могу показать, что сам класс Choices сам по себе является итеративным?

1 Ответ

1 голос
/ 29 марта 2019

Я исправил код, чтобы он был корректным для MyPy (это проще проверить с помощью Pytype, который сначала добавляет файлы аннотаций * .pyi).

Проблема с печатью заключалась в методе __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')]

Я сообщил об еще одной незначительной ошибке в исходной статье.Эта ошибка не связана с этой проблемой.

...