Протокол Python с использованием аргументов, доступных только по имени, требует, чтобы реализация имела другую подпись.

Вопрос или проблема

Я на 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, вы можете сделать следующее:

  1. Игнорирование предупреждений: Временно добавьте # type: ignore в конце строки, чтобы игнорировать предупреждение о несоответствии типов. Это не является идеальным решением, но оно позволяет вам продолжить разработку без остановок.

    a: OnSubscribeFunc = A().subscribe  # type: ignore
  2. Проверка на наличие обновлений: Периодически проверяйте обновления для PyCharm. Возможно, в следующих версиях будет исправлен данный аспект проверки типов.

  3. Использование альтернативных инструментов: Рассмотрите возможность использования Mypy или Pyright для проверки типов, чтобы избежать конфликта с инструментом.

  4. Обратитесь в поддержку: Если вы считаете это багом, отправьте отчет в поддержку PyCharm, чтобы они могли изучить и исправить проблему.

Заключение

Работа с протоколами и типизацией в Python может быть достаточно запутанной, особенно с использованием только ключевых аргументов. Правильное понимание этого вопроса, а также использование подходящих инструментов поможет вам избежать ошибок и поддерживать высокое качество кода. Напоминаем, что всегда полезно следить за документацией Python и обновлениями ваших инструментов разработки, чтобы избежать подобных трудностей в будущем.

Оцените материал
Добавить комментарий

Капча загружается...