Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type hinting with descriptors

In this pull request it looks like type hinting support for descriptors was added.

However it looks like no finalized "correct" usage example was ever posted, nor does it looks like any documentation was ever added to the typing module or to Mypy.

It looks like the correct usage is something like this:

from typing import TypeVar

T = TypeVar('T')
V = TypeVar('V')


class classproperty():
    def __init__(self, getter: Callable[[Type[T]], V]) -> None:
        self.getter = getter

    def __get__(self, instance: Optional[T], owner: Type[T]) -> V:
        return self.getter(owner)


def forty_two(cls: Type) -> int:
    return 42


class C:
    forty_two: int = classproperty(forty_two)

This seems logical, but I have no idea if that's actually the right way to do things.

Is there any documentation on this? Or complete examples that actually works on the version that was merged?

like image 210
shadowtalker Avatar asked Jan 29 '19 03:01

shadowtalker


2 Answers

The method described in the question seems to work for both Mypy and the PyCharm type checker.

Edit: Apparently my example code type checks, but MyPy cannot actually infer the type as of version 0.800.

"""Defines the `classproperty` decorator."""

from typing import Any, Callable, Optional, Type, TypeVar


T = TypeVar("T")
V = TypeVar("V")


class classproperty(property):
    """Class property decorator."""

    def __init__(self, getter: Callable[[Any], V]) -> None:
        """Override getter."""
        self.getter = getter  # type: ignore

    def __get__(self, instance: Optional[T], owner: Type[T]) -> V:  # type: ignore
        return self.getter(owner)  # type: ignore

    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)

    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))


class Thing:
    @classproperty
    def value1(cls) -> int:
        return 44

    value2 = classproperty(lambda cls: 55)

    @property
    def value3(self) -> int:
        return 66


thing = Thing()
reveal_type(thing.value1)
reveal_type(thing.value2)
reveal_type(thing.value3)
main.py:40: note: Revealed type is '<nothing>'
main.py:41: note: Revealed type is '<nothing>'
main.py:42: note: Revealed type is 'builtins.int'

https://mypy-play.net/?mypy=0.800&python=3.9&gist=79f6fab466ecc4c4b45b75f1c7b6a6a8.

like image 174
shadowtalker Avatar answered Oct 10 '22 22:10

shadowtalker


I could not get the example to work with MyPy. However, the derived definition below worked for me:

"""Defines the `classproperty` decorator."""

from typing import Any, Callable, Optional, Type, TypeVar

T = TypeVar("T")
V = TypeVar("V")


class classproperty(property):
    """Class property decorator."""

    def __init__(self, getter: Callable[[Any], V]) -> None:
        """Override getter."""
        self.getter = getter  # type: ignore

    def __get__(self, instance: Optional[T], owner: Type[T]) -> V:  # type: ignore
        return self.getter(owner)  # type: ignore

    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)

    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

like image 39
aaronsteers Avatar answered Oct 10 '22 22:10

aaronsteers