I was looking through the typeshed
source and saw that in the pathlib.pyi
it does the following:
_P = TypeVar('_P', bound=PurePath)
...
class PurePath(_PurePathBase): ...
I have a similar case with a base class that returns a subclass from __new__
(similar to Path
), so the type annotations would therefore be similar as well. However, defining the bound
keyword to the class that is defined below it resolves to an NameError since the name has not yet been resolved (as I would've expected; trying due to typeshed
source).
from abc import ABC
from typing import Type
from typing import TypeVar
from foo.interface import SomeInterface
_MT = TypeVar('_MT', bound=MyBaseClass)
class MyBaseClass(SomeInterface, ABC):
def __new__(cls: Type[_MT], var: int = 0) -> _MT:
if var == 1:
return object.__new__(FirstSubClass)
return object.__new__(cls)
class FirstSubClass(MyBaseClass): pass
How does typeshed
get away with this? It would be perfect for my typing, otherwise I must do:
_MT = TypeVar('_MT', covariant=True, bound=SomeInterface)
And all my linter warnings are satisfied ("expected type _MT, got object instead"
)...
Better matching case is the typing a factory method since I am using __new__
as a factory similar to how Path
does it and as described here. Still, it would be nice to know how typeshed
accomplishes this forward reference to bound
without using a string.
In runtime Python code, you can use string literal types in the
bound
attribute to create a forward reference:As for why a non-string can be used in
typeshed
, it's becausetypeshed
provides.pyi
stub files, which are syntactically valid Python code, but are not meant to be executed, only to be examined by type checkers. There's little I could find on specifications for stub files, but it makes sense to assume that everything is implicitly a string literal. This seems to be implied from the mypy docs: