Вопрос или проблема
Я на python 3.10. Я использую стандартный проверщик типов PyCharm и MyPy. Вот протокол, который я определил:
class OnSubscribeFunc(Protocol):
def __call__(self, instrument: str, *, x: int) -> AsyncGenerator:
...
Когда я создаю метод, который реализует его вот так:
class A:
async def subscribe(self, instrument: str, *, x: int):
yield ...
a: OnSubscribeFunc = A().subscribe # здесь, похоже, он ошибается
Я получаю это предупреждение: Ожидаемый тип 'OnSubscribeFunc', вместо этого получен '(instrument: str, Any, x: int) -> AsyncGenerator'
Если я убираю *
из моей реализации, предупреждение исчезает. Я ожидал, что будет наоборот, потому что отсутствие *
позволяет реализации иметь аргументы, которые не являются только по ключевым словам, что может быть не тем, к чему я стремлюсь с моим протоколом.
Для сравнения – эта реализация не вызывает никаких предупреждений:
class A:
async def subscribe(self, instrument: str, x: int):
yield ...
Это не имеет для меня смысла, почему он так себя ведет и является ли это ожидаемым поведением или является это ошибкой в моем проверщике типов?
Это известная ошибка PyCharm. Mypy и Pyright обе принимают ваш код таким, какой он есть.
Поставьте # type: ignore
или # noqa
и двигайтесь дальше.
Ответ или решение
Проблема с реализацией протоколов в Python с использованием только ключевых аргументов
В программировании на Python часто возникает необходимость в создании протоколов для задания структуры ваших функций и классов. Протоколы служат своего рода контрактами, которые гарантируют, что определённые функции содержат необходимые параметры и возвращаемые значения. Однако иногда реализация этих протоколов может вызывать путаницу, особенно в вопросах, связанных с использованием только ключевых аргументов.
Анализ кода
Вы предоставили следующий код, который задаёт протокол:
from typing import Protocol, AsyncGenerator
class OnSubscribeFunc(Protocol):
def __call__(self, instrument: str, *, x: int) -> AsyncGenerator:
...
Здесь вы определяете протокол OnSubscribeFunc
, который ожидает, что функция будет вызываться с первым аргументом instrument
и с единственным только ключевым аргументом x
.
Затем вы реализуете функцию, которая соответствует этому протоколу, следующим образом:
class A:
async def subscribe(self, instrument: str, *, x: int):
yield ...
Однако, когда вы пытаетесь привязать метод subscribe
к переменной a
как к протоколу OnSubscribeFunc
:
a: OnSubscribeFunc = A().subscribe # это приводит к ошибке
Вы получаете предупреждение о несоответствии типов:
Expected type 'OnSubscribeFunc', got '(instrument: str, Any, x: int) -> AsyncGenerator' instead
Это предупреждение указывает на то, что сигнатуры ваших методов не совпадают. На первый взгляд, это выглядит как ошибка логики, потому что вы только добавили *
, чтобы указать, что x
является только ключевым аргументом.
Объяснение проблемы
Проблема здесь возникает из-за того, как проверка типов в Python обрабатывает функции с только ключевыми аргументами. В спецификации языка Python использование *
указывает, что все последующие аргументы должны быть переданы как ключевые. Однако, когда проверщик типов интерпретирует эту сигнатуру, он не всегда правильно справляется с адаптацией классов и протоколов, особенно в контексте совместимости с реализациями.
В вашем случае, PyCharm
приводит это к предупреждению о несоответствии типов, в то время как другие инструменты, такие как Mypy
и Pyright
, принимают код без проблем. Это указывает на то, что поведение PyCharm
может является ошибкой в инструменте.
Рекомендации
Если вы хотите решить эту проблему в PyCharm
, вы можете сделать следующее:
-
Игнорирование предупреждений: Временно добавьте
# type: ignore
в конце строки, чтобы игнорировать предупреждение о несоответствии типов. Это не является идеальным решением, но оно позволяет вам продолжить разработку без остановок.a: OnSubscribeFunc = A().subscribe # type: ignore
-
Проверка на наличие обновлений: Периодически проверяйте обновления для
PyCharm
. Возможно, в следующих версиях будет исправлен данный аспект проверки типов. -
Использование альтернативных инструментов: Рассмотрите возможность использования
Mypy
илиPyright
для проверки типов, чтобы избежать конфликта с инструментом. -
Обратитесь в поддержку: Если вы считаете это багом, отправьте отчет в поддержку
PyCharm
, чтобы они могли изучить и исправить проблему.
Заключение
Работа с протоколами и типизацией в Python может быть достаточно запутанной, особенно с использованием только ключевых аргументов. Правильное понимание этого вопроса, а также использование подходящих инструментов поможет вам избежать ошибок и поддерживать высокое качество кода. Напоминаем, что всегда полезно следить за документацией Python и обновлениями ваших инструментов разработки, чтобы избежать подобных трудностей в будущем.