mypy не любит псевдонимы типа Cython - PullRequest
1 голос
/ 15 апреля 2020

Я пытаюсь ускорить выполнение сценария PEP 484 python, используя Cython. Я хочу сохранить некоторую семантику и удобочитаемость.

Раньше у меня был

Flags = int

def difference(f1: Flags, f2: Flags):
    return bin(f1 ^ f2).count("1")

Теперь эту функцию вызывают довольно часто, и это естественный кандидат для небольшого рефакторинга и компиляции в C используя Cython, но я не хотел бы потерять информацию о том, что f1 и f2 являются коллекциями флагов. Итак, я явно пытался

import cython

Flags = cython.int

def difference(f1: Flags, f2: Flags):
    return bin(f1 ^ f2).count("1")

Теперь, mypy терпит неудачу с этим, жалуясь

flags.py:5: error: Variable "flags.Flags" is not valid as a type
flags.py:5: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases
flags.py:6: error: Unsupported left operand type for ^ (Flags?)

, тогда как без этого псевдонима типа

import cython

def difference(f1: cython.int, f2: cython.int):
    return bin(f1 ^ f2).count("1")

модуль проверяет только отлично (кроме отсутствующей заглушки библиотеки для cython).

Что здесь происходит? Разве смысл псевдонима типа не в том, что не должно быть различий в поведении в дальнейшем?

1 Ответ

1 голос
/ 16 апреля 2020

Проблема, с которой вы здесь сталкиваетесь, заключается в том, что, поскольку с cython нет подсказок типов, к сожалению, неоднозначно, что именно должно означать выражение cython.int, и, следовательно, неоднозначно, что должно означать Flags = cython.int. .

В частности, это может быть случай, когда cython.int должно быть значением , а не типом. В этом случае Flags = cython.int будет обычным присваиванием переменной, а не псевдонимом типа.

Хотя mypy теоретически может попытаться проанализировать остальную часть вашей программы, чтобы устранить эту неоднозначность, это будет несколько дорого сделать , Поэтому вместо этого он несколько произвольно решает, что cython.int должно быть значением (например, константой), что, в свою очередь, приводит к тому, что ваша функция difference не может выполнить проверку типа.

Однако, если вы используете тип cython.int непосредственно в сигнатуре типа, мы не получаем такой неоднозначности: в этом контексте это выражение, скорее всего, должно быть каким-то типом, поэтому mypy решает: интерпретировать выражение по-другому.


Итак, как вы обходите это? Ну, есть несколько вещей, которые вы можете попробовать, и я перечислю их в порядке убывания усилий (и в порядке увеличения хакерства).

  1. Отправить запрос на получение поддержки в Mypy для поддержки PEP 613 . Этот PEP предназначен для того, чтобы дать пользователям возможность напрямую разрешить эту неоднозначность, позволяя им напрямую указывать, должен ли что-то быть псевдонимом типа.

    Этот PEP принят; единственная причина, по которой mypy не поддерживает его, заключается в том, что пока никто не удосужился его реализовать.

  2. Спросите у сопровождающих Cython, согласны ли они с отправкой заглушек для cython превращение их пакета в PEP 561 совместимый пакет - пакет, который поставляется в комплекте с подсказками типов.

    Кажется, что Cython уже связывает некоторые подсказки типов в ограниченным образом и сделать их доступными для внешнего использования теоретически может быть так же просто, как проверить их актуальность и добавить файл py.typed в пакет Cython.

    Дополнительные сведения о типе подсказки в Cython можно найти здесь и здесь .

    Mypy также планирует капитальный ремонт обработки импорта , так что вы можете по желанию использовать любой Связанные типы подсказок, даже если пакет не соответствует PEP 561 в течение следующих нескольких месяцев - вы также можете подождать, пока это произойдет.

  3. Создайте свой собственный пакет-заглушку для Cython. Этот пакет может быть неполным и определять только int и несколько других необходимых вам вещей. Например, вы можете создать файл «stubs / cython.pyi», который выглядит следующим образом:

    from typing import Any
    
    # Defining these two functions will tell mypy that this stub file
    # is incomplete and to not complain if you try importing things other
    # than 'int'. 
    def __getattr__(name: str) -> Any: ...
    def __setattr__(name: str, value: Any) -> None: ...
    
    class _int:
        # Define relevant method stubs here
    

    Затем укажите mypy на этот файл-заглушку в дополнение к вашему обычному коду. Затем Mypy поймет, что должен использовать этот файл-заглушку в качестве подсказки типа для модуля cython. Это означает, что когда вы делаете cython.int, mypy увидит, что это класс, который вы определили выше, и поэтому будет иметь достаточно информации, чтобы знать, что Flags = cython.int, скорее всего, псевдоним типа.

  4. Переопределить, что Flags назначается при выполнении проверки только типа. Вы можете сделать это с помощью переменной typing.TYPE_CHECKING:

    from typing import TYPE_CHECKING
    import cython
    
    # The TYPE_CHECKING variable is always False at runtime, but is treated
    # as being always True for the purposes of type checking
    if TYPE_CHECKING:
        # Hopefully this is a good enough approximation of cython.int?
        Flags = int
    else:
        Flags = cython.int
    
    def difference(f1: Flags, f2: Flags):
        return bin(f1 ^ f2).count("1")
    

    Одно предостережение в отношении этого подхода заключается в том, что я не уверен, в какой степени Cython поддерживает эти виды трюков PEP 484 и распознает ли он, что Flags предназначен для псевдонима типа, если он заключен в оператор if, подобный этому.

  5. Вместо того, чтобы Flags псевдоним типа для cython.int, сделайте его подклассом :

    import cython
    
    class Flags(cython.int): pass
    
    def foo(a: Flags, b: Flags) -> Flags:
        return a ^ b
    

    Теперь вы используете cython.int в контексте, в котором разумно предположить, что это тип, а mypy в итоге не сообщает об ошибке.

    Конечно, это меняет семантику вашей программы и может также сделать Cython несчастным - я не очень знаком с тем, как работает Cython, но я подозреваю, что вы не должны создавать подкласс cython.int.

...