SuperShoot
SuperShoot

Reputation: 10872

Python typing: function argument interdependence

Take this function from Django Rest Framework source:


def method_overridden(method_name, klass, instance):
    """
    Determine if a method has been overridden.
    """
    method = getattr(klass, method_name)
    default_method = getattr(method, '__func__', method)  # Python 3 compat
    return default_method is not getattr(instance, method_name).__func__

The function is intended to determine if a method has been overridden by a subclass and accepts a type klass and an object instance. In context, instance is meant to be a subclass of klass, however, the function only really cares that they both have a method method_name.

So that example has led me to try to solve a more general case, how can you express an interdependence between arguments of a function such that one argument should be an instance of another argument that is a type, without actually caring what that type is? I tried this:

from typing import TypeVar

T = TypeVar("T")

def fn(t: type[T], i: T) -> None:
    pass

fn(dict, "str")  # no mypy error

Using a class, you can bind the type to the class:

from typing import Generic, TypeVar

class K(Generic[T]):
    def meth(self, t: type[T], i: T) -> None:
        pass

K[dict]().meth(dict, "str")  # Argument 2 to "meth" of "K" has incompatible type "str"; expected "Dict[Any, Any]"

Thanks!

Upvotes: 1

Views: 282

Answers (1)

Wombatz
Wombatz

Reputation: 5458

The problem is that mypy infers T in such a way that both arguments can match. We can check that by redefining fn slightly:

def fn(t: type[T], i: T) -> T:
    return i

reveal_type(fn(dict, "str"))  # -> Collection[Any]
reveal_type(fn(int, "str"))  # -> object

To solve this you can either force T to be a specific type (like you did) or separate the arguments and force mypy to first infer T for one of the arguments.

def fn2(t: type[T]) -> Callable[[T], None]:
    def inner(i: T) -> None:
        pass
    return inner

fn2(dict)("str")  # error: Argument 1 has incompatible type "str"; expected "Dict[Any, Any]"
fn2(dict)({})     # no error
fn2(int)(True)    # also no error, because `type` is covariant

Upvotes: 1

Related Questions