I can’t seem to figure out how to express this in the Python type system. Perhaps it’s fundamentally impossible? I’ve tried searching, but I can’t turn up any results that deal with my precise question.
Suppose I have some library code where I define a BaseType
that is meant to be subclassed by users. Next, suppose I want to create an object that is both an instance of a user-defined type while also inheriting from a different library class. Is that possible to express?
Here’s a minimal example.
Library code:
class BaseType:
base: str
class ExtensionType:
ext: str
def add_extension[T: BaseType](user_type: type[T]):
class CombinedType(user_type, ExtensionType):
pass
return CombinedType()
User code:
class UserType(BaseType):
user: str
user_type = UserType()
user_type.base # ok
user_type.user # ok
library_extended = add_extension(UserType)
library_extended.base # ok
library_extended.ext # ok
library_extended.user # error
This first approach doesn’t work. The value returned by add_extension
is known to subclass BaseType
but not UserType
.
However, if the user creates the class manually, it works.
class ManuallyExtended(UserType, ExtensionType):
pass
manually_extended = ManuallyExtended()
manually_extended.base # ok
manually_extended.ext # ok
manually_extended.user # ok
That solution would normally be fine, but in my situation, users will define lots of types, and I don’t want them to have to write a bunch of redundant declarations.
It seems like there are two possible solutions. One would be a way to tell the type checker that the return value is a subclass of both T
and ExtensionType
. Stated differently, I want it to return an object that gives True
for both isinstance(value, UserType)
and isinstance(value, ExtensionType)
.
def add_extension[T: BaseType](user_type: type[T]) -> T + ExtensionType: # Made up syntax
...
But I don’t know if it’s possible or how I would express that.
Alternatively, you could inherit from a generic type, i.e.
class CombinedType[T: BaseType](T, ExtensionType): ...
But that gives an error.
Is this possible?