Open nstarman opened 2 years ago
This is a big can of worms and I don't think it's a good idea to add it to the standard library. For example, we'd have to get runtime checks for TypeVars, and generic types, and callable compatibility.
Just wondering, is there any clean way to do structural typing for more complex objects?
E.g.
def isbarlike(obj) -> TypeGuard[BarLike]
....
looks useful, but really isn't because all we learn is that obj
has an attribute named attr
, not also that the attribute is FooLike
. We can regress to using specific classes:
def isabar(obj) -> TypeGuard[Bar] # functionally equivalent to ``isinstance(obj, Bar)``
....
but that defeats the intent of static duck typing.
The semantics of isinstance
are well established and are unlikely to change. What you're proposing would not only be a can of worms. It would also be a backward compatibility break.
If you want to apply different semantics (e.g. perform deeper nested checks), you can write your own implementation. If you use TypeGuard
as a return type, then a static type checker will also be able to use it for type narrowing.
all we learn is that obj has an attribute named attr, not also that the attribute is FooLike
If isbarlike
is implemented correctly (i.e. it properly validates that obj
matches a BarLike
protocol), then it will need to validate that the attribute is FooLike
.
def isfoolike(obj) -> TypeGuard[FooLike]:
return hasattr(obj, 'attr') and isinstance(obj.attr, str)
def isbarlike(obj) -> TypeGuard[BarLike]:
return hasattr(obj, 'attr') and isfoolike(obj.attr)
isinstance(obj, ProtocolSubclass)
only checks the the existence ofProtocolSubclass
's methods onobj
and not the type signature. To provide deeper checks, maybeisinstance
could check attributes/methods onProtocolSubclass
that are themselves aProtocol
.A small example showing the change in behavior: