Let’s imagine that we have 2 protocols: HasSize
and HasLength
. For domain reasons these may mean different things, but in some cases you want to adapt one to the other:
For the sake of example let’s forget about len()
.
class HasSize(Protocol):
def size(self) -> int: pass
class HasLength(Protocol):
def length(self) -> int: pass
As mentioned, it’s common (but not the only) case that both these mean the same. Since not every HasSize
is HasLength
, not vice versa, I don’t want one to extend the other.
I could provide mixin classes:
class LengthAsSize:
def length(self) -> int:
return self.size()
#and vice versa
But the typing system will complain about missing self.size()
method.
I could make them extend both protocols:
class LengthAsSize(HasLength, HasSize):
def length(self) -> int:
return self.size()
#and vice versa
but that would remove the possibility of non-Protocol
metaclasses, so I won’t be able to do:
class Impl(NamedTuple, LengthAsSize): # will complain about metaclasses
x: list
def size(self) -> int:
return len(self.x)
So, I’d like to provide a type decorator that will provide the other impl:
def length_as_size[T: HasLength](t: type[T]) -> T and HasSize: #THIS IS THE ISSUE
def size(self) -> int:
return self.length()
t.size = size
return t
#this won't raise any issues, but static checker won't recognize this as HasLength; it will still recognize it as HasSize, though
@length_as_size
class Impl(NamedTuple):
x: list
def size(self) -> int:
return len(self.x)
Now, the part that I’ve commented is problematic – X and Y
doesn’t resolve to an intersection of X
and Y
but is considered Any
instead. Using &
doesn’t work neither. I didn’t find anything related to that in the docs (though they are lengthy and it’s possible that I’ve simply missed it).
How do I phrase the type that extends both other types like this?
3