Как указать тип функции, добавленной в класс декоратором класса в Python

У меня есть декоратор класса, который добавляет несколько функций и полей в украшенный класс.

@mydecorator
@dataclass
class A:
    a: str = ""

Добавлена ​​(через setattr()) функция .save() и набор информации для полей класса данных в виде отдельного словаря.

Я бы хотел, чтобы VScode и mypy правильно распознали это, чтобы при использовании:

a=A()
a.save()

или a.my_fields_dict эти 2 правильно распознаны.

Есть ли способ сделать это? Может быть, изменить аннотации типа класса A во время выполнения?

Подумайте о том, что вы говорите. Вы хотите изменить аннотации во время выполнения, но статический контролер типов должен знать о них? Статические проверки типов не выполняют ваш код, они просто читают его.

Daniil Fajnberg 12.01.2023 20:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

TL;DR

То, что вы пытаетесь сделать, невозможно с текущей системой типов.


1. Типы перекрестков

Если атрибуты и методы, которые вы добавляете в класс через декоратор, являются статическими (в том смысле, что они известны не только во время выполнения), то то, что вы описываете, фактически является расширением любого данного класса T путем смешивания протоколP. Этот протокол определяет метод save и так далее.

Чтобы аннотировать это, вам понадобится пересечение T & P. Это будет выглядеть примерно так:

from typing import Protocol, TypeVar


T = TypeVar("T")


class P(Protocol):
    @staticmethod
    def bar() -> str: ...


def dec(cls: type[T]) -> type[Intersection[T, P]]:
    setattr(cls, "bar", lambda: "x")
    return cls  # type: ignore[return-value]


@dec
class A:
    @staticmethod
    def foo() -> int:
        return 1

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


2. Проблемы с декоратором класса

Единственный обходной путь на данный момент — это пользовательская реализация вместе с соответствующим плагином для проверки типов по вашему выбору. Я только что наткнулся на пакет typing-protocol-intersection, который делает именно это для mypy.

Если вы установите это и добавите plugins = typing_protocol_intersection.mypy_plugin в свою конфигурацию mypy, вы можете написать свой код следующим образом:

from typing import Protocol, TypeVar

from typing_protocol_intersection import ProtocolIntersection


T = TypeVar("T")


class P(Protocol):
    @staticmethod
    def bar() -> str: ...


def dec(cls: type[T]) -> type[ProtocolIntersection[T, P]]:
    setattr(cls, "bar", lambda: "x")
    return cls  # type: ignore[return-value]


@dec
class A:
    @staticmethod
    def foo() -> int:
        return 1

Но тут мы сталкиваемся со следующей проблемой. Проверка этого с помощью reveal_type(A.bar()) через mypy даст следующее:

error: "Type[A]" has no attribute "bar"  [attr-defined]
note: Revealed type is "Any"

Но если мы сделаем это вместо этого:

class A:
    @staticmethod
    def foo() -> int:
        return 1


B = dec(A)

reveal_type(B.bar())

мы не получаем жалоб от mypy и note: Revealed type is "builtins.str". Хотя то, что мы делали раньше, было эквивалентно!

Это не баг плагина, а mypy внутренностей. Это еще одна давняя проблема, которая mypy неправильно обрабатывает декораторы классов.

Человек в этой ветке проблемы даже упомянул ваш вариант использования в сочетании с желаемым типом пересечения.


сделай сам

Другими словами, вам просто нужно подождать, пока эти две дыры не будут залатаны. Или вы можете надеяться, что, по крайней мере, проблема с декоратором от mypy будет исправлена ​​в ближайшее время, и тем временем напишите свой собственный плагин VSCode для типов пересечений. Может быть, вы сможете встретиться с человеком, стоящим за этим mypy плагином, о котором я упоминал выше.

Это не ошибка плагина, а внутренностей mypy. Вы уверены? На первый взгляд, я полагаю, что добавления get_class_decorator_hook к ProtocolIntersectionPlugin должно быть достаточно. Я расследую это и, если возможно, PR, потому что это выглядит очень здорово.

SUTerliakov 12.01.2023 22:19

@SUTerliakov Согласен, выглядит многообещающе. Но проверьте проблему mypy, которую я связал. И попробуйте простой декоратор классов без плагина. Он все еще сломан.

Daniil Fajnberg 12.01.2023 23:15

Да, недавно я пытался заставить mypy работать с простым декоратором классов, но это все еще ошибка. Может быть, крюк сможет это вылечить... После расследования я оставлю здесь записку.

SUTerliakov 12.01.2023 23:24

@DaniilFajnberg хм, интересно, спасибо. У меня уже есть протокол для моей добавленной части. Жаль пока нет перекрестка.

chersun 13.01.2023 21:07

Другие вопросы по теме