1 year ago

#378624

test-img

Marcel Wilson

Can type hinting infer type of class attribute from another?

There are subclasses that have the class attribute matcher_function set to a function. During instantiation that function is called and sets another attribute matcher. In all cases the return object of the matcher_function is what matcher gets set to.

Is it possible to create a type hint in the base class BaseResolution that would allow both mypy and pycharm to properly infer matcher is the return value of matcher_function?

# contains_the_text.py
from hamcrest import contains_string
from hamcrest.library.text.stringcontains import StringContains
from .base_resolution import BaseResolution


class ContainsTheText(BaseResolution):
    matcher: StringContains  # <-- this is what I'm curious can be inferred
    matcher_function = contains_string  # <-- this function returns an instance 
                                        # of `StringContains`
    # it would be wonderful if 
    # a. mypy could detect that the matcher type hint is correct based on 'matcher_function' 
    # b. infer what the type is when the hint is not present in the subclasses.
# base_resolution.py
from typing import Any, Callable, TypeVar

from hamcrest.core.base_matcher import BaseMatcher, Matcher
from hamcrest.core.description import Description

T = TypeVar("T")

class BaseResolution(BaseMatcher[T]):
    matcher: Matcher
    matcher_function: Callable
    expected: Any

    def __init__(self, *args: object, **kwargs: object) -> None:
        cls = self.__class__
        if args and kwargs:
            self.expected = (args, kwargs)
            self.matcher = cls.matcher_function(*args, **kwargs)
        elif args:
            self.expected = args if len(args) > 1 else args[0]
            self.matcher = cls.matcher_function(*args)
        elif kwargs:
            self.expected = kwargs
            self.matcher = cls.matcher_function(**kwargs)
        else:
            self.expected = True
            self.matcher = cls.matcher_function()

    def _matches(self, item: T) -> bool:
        """passthrough to the matcher's method."""
        return self.matcher.matches(item)

    # truncated a whole bunch of other methods...

While these are likely better typehints, they didn't seem to do the trick.

class BaseResolution(BaseMatcher[T]):
    matcher: Matcher[T]
    matcher_function: Callable[..., Matcher[T]]

I know you can do something sorta similar using TypeVar(bound=) which will infer function return types based on arguments passed in. But I can't seem to figure out how (if even possible) to apply that at a class attribute level.

from typing import Type, TypeVar, Generic
T = TypeVar("T")


class Foo(Generic[T]):
    ...

class FooBar(Foo[T]):
    ...

F = TypeVar("F", bound=Foo)  # any instance subclass of Foo


class MyClass(FooBar):
    ...

def bar(f: Type[F]) -> F:
    ...

def baz(f: Type[Foo]) -> Foo:
    ...


objx = bar(MyClass)
objy = baz(MyClass)

reveal_type(objx)  # -> MyClass*
reveal_type(objy)  # -> Foo[Any]

Given the above example I tried the following but that clearly isn't right.

F = TypeVar("F", bound=Matcher)

class BaseResolution(BaseMatcher[T]):
    matcher: F
    matcher_function: Callable[..., F]

# mypy returns 
# Type variable "base_resolution.F" is unbound

python

python-3.x

type-hinting

mypy

0 Answers

Your Answer

Accepted video resources