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 butobject” 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
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.
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.