Generic types and isinstance
results in Any
type parameter
#1842
-
I have two classes - a import abc
from typing import Any, Generic, TypeVar
class BasePlugin:
def get_token(self) -> str:
return 'foo'
class BaseLoader(metaclass=abc.ABCMeta):
@property
def plugin_class(self) -> type[BasePlugin]:
raise NotImplementedError()
@abc.abstractmethod
def load(self, **kwargs: Any) -> BasePlugin:
return self.plugin_class(**kwargs) There are multiple Plugin classes and there is a distinct class BasePlugin:
def get_token(self) -> str:
return 'foo'
T = TypeVar('T', bound='BasePlugin')
class BaseLoader(Generic[T], metaclass=abc.ABCMeta):
@property
def plugin_class(self) -> type[T]:
raise NotImplementedError()
@abc.abstractmethod
def load(self, **kwargs: Any) -> T:
return self.plugin_class(**kwargs)
class PasswordPlugin(BasePlugin):
def __init__(self, auth_url: str, username: str, password: str):
super().__init__()
self.auth_url = auth_url
self.username = username
self.password = password
class PasswordLoader(BaseLoader[PasswordPlugin]):
@property
def plugin_class(self) -> type[PasswordPlugin]:
return PasswordPlugin
def load(self, **kwargs: Any) -> PasswordPlugin:
if 'username' not in kwargs:
raise Exception('need username')
if 'password' not in kwargs:
raise Exception('need password')
return super().load(**kwargs) And this is mostly happy. However, the loaders are configured and sourced from opaque objects (in this case, an import argparse
def load_from_parser(
namespace: argparse.Namespace, **kwargs: Any
) -> BasePlugin | None:
if not hasattr(namespace, 'os_auth_type'):
return None
# narrow type
if isinstance(namespace.os_auth_type, BaseAuthLoader):
plugin = namespace.os_auth_type
else:
return None
return plugin.load(**kwargs) I check this with
Likewise, pyright with strict mode enabled complains:
However, I'm guessing my mental model isn't quite right here, so could someone advise me as to what I'm missing and how I can achieve the coupling between two different classes? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
If you don't specify the type of a type parameter it's always considered to be The The other way to do it would be to add an annotation to import argparse
def load_from_parser(
namespace: argparse.Namespace, **kwargs: Any
) -> BasePlugin | None:
if not hasattr(namespace, 'os_auth_type'):
return None
# narrow type
if isinstance(namespace.os_auth_type, BaseAuthLoader):
plugin: BaseLoader[BasePlugin] = namespace.os_auth_type
else:
return None
return plugin.load(**kwargs) It's also worth noting that, at least based on your example, that you might want to change You may also want to change the name to |
Beta Was this translation helpful? Give feedback.
If you don't specify the type of a type parameter it's always considered to be
Any
, that's part of the gradual guarantee of Python's type system, setting an upper bound does not change that. If you want the default to be something else you need to use PEP-696 and set a default type for theTypeVar
.The
default
parameter is new however and only available starting with Python 3.13, in earlier versions you will need to usetyping_extensions.TypeVar
.The other way to do it would be to add an annotation to
plugin
i.e.