I've got the following class defined:
class BackgroundThread(threading.Thread):
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
super().__init__(*args, **kwargs)
self._quit = threading.Event()
def run(self) -> None:
while not self._quit.wait(timeout=0.0):
self._target(*self._args, **self._kwargs)
def __enter__(self) -> None:
self.start()
return None
def __exit__(self, *args: typing.Any) -> None:
self._quit.set()
self.join()
The code works exactly as expected. However, mypy complains that
"BackgroundThread" has no attribute "_target"
"BackgroundThread" has no attribute "_args"
"BackgroundThread" has no attribute "_kwargs"
I can get around this by inserting
class BackgroundThread(threading.Thread):
_target: typing.Callable
_args: typing.Tuple
_kwargs: typing.Dict[str, typing.Any]
However, this seems hacky. Why doesn't mypy recognize these variables which come from the parent class? Is it because they begin with underscores?
This is the definition of Thread in standard stub. Underscore attributes are not defined, only dunder are. You can ask/contribute to typeshed on Github to add missing underscore attributes. It is not forbidden to add them, but usually them are not listed in typeshed stubs.
class Thread:
name: str
ident: Optional[int]
daemon: bool
if sys.version_info >= (3,):
def __init__(
self,
group: None = ...,
target: Optional[Callable[..., Any]] = ...,
name: Optional[str] = ...,
args: Iterable[Any] = ...,
kwargs: Optional[Mapping[str, Any]] = ...,
*,
daemon: Optional[bool] = ...,
) -> None: ...
else:
def __init__(
self,
group: None = ...,
target: Optional[Callable[..., Any]] = ...,
name: Optional[Text] = ...,
args: Iterable[Any] = ...,
kwargs: Optional[Mapping[Text, Any]] = ...,
) -> None: ...
def start(self) -> None: ...
def run(self) -> None: ...
def join(self, timeout: Optional[float] = ...) -> None: ...
def getName(self) -> str: ...
def setName(self, name: Text) -> None: ...
if sys.version_info >= (3, 8):
@property
def native_id(self) -> Optional[int]: ... # only available on some platforms
def is_alive(self) -> bool: ...
if sys.version_info < (3, 9):
def isAlive(self) -> bool: ...
def isDaemon(self) -> bool: ...
def setDaemon(self, daemonic: bool) -> None: ...