2025, Dec 11 00:02

typing.Self и обобщённые классы в Python 3.13: баг PyCharm и обход

Как работает typing.Self в обобщённых классах Python 3.13, почему PyCharm выдаёт ложное предупреждение и как подавить его через type: ignore или noqa.

Работа с typing.Self в Python 3.13 должна быть предельно простой: если класс-метод на обобщённой (generic) базе объявлен с возвратом Self, то при вызове через подкласс он должен давать экземпляр именно этого подкласса. Однако в такой конфигурации PyCharm иногда показывает сбивающее с толку предупреждение: будто бы объявленный тип возврата не совпадает с фактическим. Разберёмся, что происходит, и как на практике это обойти.

Воспроизводимый пример

Сценарий: обобщённый базовый класс с класс-методом, возвращающим Self, и подкласс, фиксирующий параметр типа. Вызов этого класса-метода у подкласса в PyCharm вызывает предупреждение.

from typing import Self
class Root[T]:
    @classmethod
    def build(cls) -> Self:
        return cls()
class Leaf(Root[int]):
    pass
def make_leaf() -> Leaf:
    return Leaf.build()  # Предупреждение в PyCharm: «Ожидался тип 'Leaf', вместо этого получен 'Root[int]'»

Что на самом деле происходит

Код корректный. Класс-метод build возвращает Self, поэтому при вызове на подклассе тип результата — сам подкласс. Предупреждение исходит от типизатора PyCharm, а не из‑за ошибки в коде или модели типов.

С вашим кодом всё в порядке. Это баг в PyCharm.

Проблема отслеживается здесь: youtrack.jetbrains.com/issue/PY-75679/typing.Self-is-broken-for-Generic-base-classes. По имеющимся наблюдениям, mypy этой ошибки не показывает, и также отмечалось, что типизатор PyCharm пока не полностью соответствует спецификации. Для дополнительной проверки можно прогнать код в mypy и Pyright; у обоих есть онлайн‑песочницы: mypy-play.net и pyright-play.net.

Практическое решение

Так как предупреждение — ложноположительное, разумнее всего локально подавить его и продолжать работу. Есть два неброских варианта: добавить строчную директиву, которую другие инструменты либо поддерживают, либо спокойно проигнорируют.

from typing import Self
class Root[T]:
    @classmethod
    def build(cls) -> Self:
        return cls()
class Leaf(Root[int]):
    pass
def make_leaf() -> Leaf:
    return Leaf.build()  # type: ignore

В качестве альтернативы ту же строку можно заглушить с помощью # noqa. Также обращают внимание, что # noinspection PyTypeChecker — это директива, специфичная для PyCharm; многие избегают её, если нужна пометка, которую PyCharm учтёт, а остальные типизаторы проигнорируют.

Почему это важно

Ложные срабатывания статического анализа отнимают время и подрывают доверие к инструментам. Понимание, что это предупреждение — известная проблема, позволяет не тормозить и не сомневаться в корректных приёмах типизации. При сомнениях сверяйтесь с несколькими типизаторами: здесь mypy ошибок не видит, а Pyright можно использовать как второе мнение.

Итоги

Если PyCharm утверждает, что класс‑метод, возвращающий Self в подклассе обобщённого класса, отдаёт базовый generic вместо подкласса, считайте это багом PyCharm (см. трекер по ссылке). Реализацию оставляйте как есть, подавляйте предупреждение через # type: ignore или # noqa и, при желании, проверьте тот же фрагмент в mypy или Pyright в их онлайн‑песочницах. Так вы сохраните корректную типизацию и избавите редактор от лишнего шума.