Как ввести метод подсказки python magic __get__

Предположим, у нас есть следующие классы:

class Foo:
   def __init__(self, method):
       self.method = method

   def __get__(self, instance, owner):
       if instance is None:
          return self
       return self.method(instance)


class Bar:
    @Foo
    def do_something(self) -> int:
        return 1


Bar().do_something  # is 1
Bar.do_something    # is Foo object

Как правильно ввести подсказку __get__ и method, чтобы Pylance понял, что Bar().do_something имеет возвращаемый тип do_something? (как стандарт property)

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Вам нужно перегрузить метод __get__.

Я сам не использую VSCode, но я протестировал приведенный ниже код с MyPy, и я ожидаю, что Pyright также правильно выведет типы.

Питон >=3.9

Чтобы сделать это максимально гибким, я бы предложил сделать Foo общим с точки зрения

  1. класс, использующий дескриптор/декоратор,
  2. спецификация параметра украшенного метода, и
  3. возвращаемый тип декорированного метода.
from collections.abc import Callable
from typing import Generic, TypeVar, Union, overload
from typing_extensions import Concatenate, ParamSpec, Self

T = TypeVar("T")    # class using the descriptor
P = ParamSpec("P")  # parameter specs of the decorated method
R = TypeVar("R")    # return value of the decorated method


class Foo(Generic[T, P, R]):
    method: Callable[Concatenate[T, P], R]

    def __init__(self, method: Callable[Concatenate[T, P], R]) -> None:
        self.method = method

    @overload
    def __get__(self, instance: T, owner: object) -> R: ...

    @overload
    def __get__(self, instance: None, owner: object) -> Self: ...

    def __get__(self, instance: Union[T, None], owner: object) -> Union[Self, R]:
        if instance is None:
            return self
        return self.method(instance)

Демо:

from typing import TYPE_CHECKING


class Bar:
    @Foo
    def do_something(self) -> int:
        return 1


a = Bar().do_something
b = Bar.do_something

print(type(a), type(b))  # <class 'int'> <class '__main__.Foo'>
if TYPE_CHECKING:
    reveal_locals()

Запуск MyPy дает желаемый результат:

note: Revealed local types are:
note:     a: builtins.int
note:     b: Foo[Bar, [], builtins.int]

ПРИМЕЧАНИЕ: (спасибо @SUTerliakov за указание на это)

  • Если вы используете Python >=3.10, вы можете импортировать Concatenate и ParamSpec напрямую из typing и использовать |-нотацию вместо typing.Union.
  • Если вы используете Python >=3.11, вы также можете импортировать Self напрямую из typing, то есть вам вообще не понадобится typing_extensions.

Питон <3.9

Без Concatenate , ParamSpec и Self мы все еще можем сделать Foo универсальным с точки зрения возвращаемого значения декорированного метода:

from __future__ import annotations
from collections.abc import Callable
from typing import Generic, TypeVar, Union, overload

R = TypeVar("R")    # return value of the decorated method


class Foo(Generic[R]):
    method: Callable[..., R]

    def __init__(self, method: Callable[..., R]) -> None:
        self.method = method

    @overload
    def __get__(self, instance: None, owner: object) -> Foo[R]: ...

    @overload
    def __get__(self, instance: object, owner: object) -> R: ...

    def __get__(self, instance: object, owner: object) -> Union[Foo[R], R]:
        if instance is None:
            return self
        return self.method(instance)

Вывод MyPy для того же демо-скрипта, что и выше:

note: Revealed local types are:
note:     a: builtins.int
note:     b: Foo[builtins.int]

Не могли бы вы добавить ссылку на typing_extensions, чтобы прояснить, что требования к Python связаны только с возможностями модуля stl typing? Ваше первое решение будет отлично работать на py3.9 с typing_extensions импортом Self, Concatenate и ParamSpec.

SUTerliakov 01.04.2023 02:00

@SUTerliakov Спасибо. Я отредактировал свой ответ. Ты прав. Достаточно различать <3.9 и >=3.9. (Я также неправильно использовал обозначение объединения | в старой версии.)

Daniil Fajnberg 01.04.2023 13:29

На самом деле нотация объединения труб также была в порядке, потому что у вас есть аннотации для импорта в будущее и использование трубы только в аннотациях:)

SUTerliakov 01.04.2023 16:04

спасибо за развернутый ответ Даниил

feiyang472 03.04.2023 09:17

@feiyang472 Рад, что смог помочь. Если это решило вашу проблему и дало удовлетворительный ответ на ваш вопрос, отметьте его как принятое, чтобы сообщить об этом другим пользователям.

Daniil Fajnberg 03.04.2023 09:30

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