Generic function but no `object`

Is there a way to “partially limit” a TypeVar? Let’s take this example

def fun[T](cls: type[T], d: T) -> T:
    ...

reveal_type(fun(int, 0))    # Revealed type is "int"
reveal_type(fun(str, ""))   # Revealed type is "str"
reveal_type(fun(str, 0))    # Revealed type is "object"

the goal is to write fun in such a way that cls should be the “type” of d.

but as “everything is an object”, and T is “unbounded” in the last case instead of an error I have (in my case) mypy happily accepting the invocation

And I’m not sure how to bound T , because for my purposes it can be (almost) everything.

For this reason I was wondering if there was a way to say “T can be anything but object” in a generic function

Python doesn’t have negation types, so not really.

There’s a chance that type[T] is an imprecise tool for your use case in the first place; it’s covariant by design. What are you doing with the cls parameter? For example, are you using it as a constructor? If so, you could try something like the following:

from collections.abc import Callable

def fun[T](constructor: Callable[[], T], d: T) -> T: # Or some other Callable signature
    ...

reveal_type(fun(int, 0))    # Revealed type is "int"
reveal_type(fun(str, ""))   # Revealed type is "str"
reveal_type(fun(str, 0))    # Error: incompatible type
2 Likes

There’s a trick where you use @deprecated combined with @overload to essentially negate a subset of otherwise allowed typed, but it’s finicky and I don’t know if it will work in this situation.

Every type is assignable to object, so I’m pretty sure it won’t. Mypy rejects the definition outright, per the playground.

1 Like

I wanted to do something like ages ago and found that it is currently impossible.

Negation wouldn’t help here ~object is Never (This is one of the non-controversial parts of negation, with only one interpretation under set theoretic typing)

What would help here is a way to specify that a specific type variable must resolve to a singular exact matching type rather than a set of types or a shared base type. Type variable solving is currently underspecified as it is, and this would be a new constraint on solutions on top of it.

The problem in this specific case is probably much more that Type[...] is covariant, which makes it possible to infer int | str in that case. This has always been a huge soundness hole, where you can do things like:

class X:
    def __init__(self, x: int): ...

x: type[object] = X

def f(x: type[object]):
    return x()

f(X)  # This shouldn't be possible, because the call x() will fail

I have personally never liked that type[…] behaves this way, but it’s too late to change, because a lot of code depends on it.