So something like this?
from __future__ import annotations
from abc import abstractmethod
from typing import Protocol, runtime_checkable
@runtime_checkable
class FloatT(Protocol):
@abstractmethod
def __add__(self, other: "FloatT" | "IntegerT") -> "FloatT": ...
@abstractmethod
def __radd__(self, other: "FloatT" | "IntegerT") -> "FloatT": ...
@abstractmethod
def __float__(self) -> float: ...
FloatT.register(float)
@runtime_checkable
class IntegerT(Protocol):
@abstractmethod
def __init__(self, value: "IntegerT"): ...
@abstractmethod
def __add__(self, other: "IntegerT") -> "IntegerT": ...
@abstractmethod
def __radd__(self, other: "IntegerT") -> "IntegerT": ...
@abstractmethod
def __float__(self) -> float: ...
@abstractmethod
def __int__(self) -> int: ...
IntegerT.register(int)
class MyFloat(FloatT):
def __init__(self, value: FloatT | IntegerT): ...
def __add__(self, other: FloatT | IntegerT) -> "MyFloat": ...
def __radd__(self, other: FloatT | IntegerT) -> "MyFloat": ...
def __float__(self) -> float: ...
class MyInteger(IntegerT):
def __init__(self, value: IntegerT): ...
def __add__(self, other: FloatT | IntegerT) -> "MyInteger": ...
def __radd__(self, other: FloatT | IntegerT) -> "MyInteger": ...
def __float__(self) -> float: ...
def __int__(self) -> int: ...
integer_t_val: IntegerT = 0 # should validate, but doesn't
assert isinstance(MyInteger(0), IntegerT) # works because of registration
assert isinstance(0, IntegerT) # works because of registration
assert isinstance(0.0, FloatT) # works because of registration
assert isinstance(MyFloat(0), FloatT) # works because of inheritance? not sure
reveal_type(MyFloat(0) + MyInteger(0)) # should yield MyFloat
reveal_type(MyInteger(0) + 0) # should yield MyInteger
reveal_type(0.0 + MyFloat(0)) # should yield MyFloat
I think instance checks would have to be revisited if Protocol
s are to be used as ancestor classes. As of now, that check is not very efficient and imposes some odd behaviors that probably aren’t appropriate for direct inheritance.
I don’t know if something like this would suffice?
class _ProtocolMeta(ABCMeta):
# …
def __instancecheck__(cls, instance):
if super().__instancecheck__(instance):
# Short circuit for direct inheritors
return True
else:
# … existing implementation that checks method names, etc. …
return False
I’m not aware enough of all of the edge cases to know what problems the above would cause.