Why do I need a second type var for a method on a class? #9555
-
I have this code: @dataclass(frozen=True)
class MyThing(Generic[T]):
some_property: T
@staticmethod
def build_one(some_property: T) -> MyThing[T]:
return MyThing(some_property)
@staticmethod
def build_two(some_property: U) -> MyThing[U]:
return MyThing(some_property)
def build_my_thing(some_property: T) -> MyThing[T]:
return MyThing(some_property)
reveal_type(MyThing.build_one("hi!")) # Type of "build_one" is "(some_property: Unknown) -> MyThing[Unknown]"
reveal_type(MyThing.build_two("hi!")) # Type of "MyThing.build_two("hi!")" is "MyThing[str]"
reveal_type(build_my_thing("hi!")) # Type of "build_my_thing("hi!")" is "MyThing[str]" I don't understand why the staticmethod approach only works if I use a different typevar than the one used for Generic. Would appreciate if anyone was able to explain it! I tried to look for an existing answer but feel like I'm lacking the terminology to properly describe the problem. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
The expression Now let's see what happens if you use a separate method-scoped type variable TypeVar scoping rules in Python can be confusing, which is why we introduced a better syntax in Python 3.12. Here's what your code looks like with this new syntax. @dataclass(frozen=True)
class MyThing[T]:
some_property: T
@staticmethod
def build_one(some_property: T) -> MyThing[T]:
return MyThing(some_property)
@staticmethod
def build_two[U](some_property: U) -> MyThing[U]:
return MyThing(some_property) Also, Python 3.13 introduces default values for type variables. This allows you to specify what value a type variable should take when a type argument is not specified. @dataclass(frozen=True)
class MyThing[T = str]:
... |
Beta Was this translation helpful? Give feedback.
The expression
MyThing.build_one
bindsMyThing
to thebuild_one
method. TheMyThing
class takes one type argument, so you would normally need to do something likeMyThing[str].build_one
. Since you haven't supplied a type argument, a type checker will assumeAny
(orUnknown
, which is what pyright calls an "implicit"Any
). That means the class variableT
is replaced withUnknown
, so the type ofMyThing.build_one
is(some_property: Unknown) -> MyThing[Unknown]
.Now let's see what happens if you use a separate method-scoped type variable
U
. The expressionMyThing.build_two
once again bindsMyThing[Unknown]
to the methodbuild_two
and the class-scoped type variableT
is replaced byUnknown
. Th…