Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

isinstance check with nested Protocol. #1257

Open
nstarman opened this issue Sep 17, 2022 · 3 comments
Open

isinstance check with nested Protocol. #1257

nstarman opened this issue Sep 17, 2022 · 3 comments
Labels
topic: feature Discussions about new features for Python's type annotations

Comments

@nstarman
Copy link

isinstance(obj, ProtocolSubclass) only checks the the existence of ProtocolSubclass's methods on obj and not the type signature. To provide deeper checks, maybe isinstance could check attributes/methods on ProtocolSubclass that are themselves a Protocol.

A small example showing the change in behavior:

@runtime_checkable
class FooLike(Protocol):
    attr: str

@runtime_checkable
class BarLike(Protocol):
    attr: FooLike

@dataclass
class Foo:
    attr: str = "test"

@dataclass
class Bar:
    attr: Foo

foo = Foo()
bar = Bar(foo)

# No change in current behavior
assert isinstance(foo, FooLike)  # passes
assert isinstance(bar, BarLike)  # passes
assert isinstance(bar, FooLike)  # passes

# Change in behavior
assert not isinstance(foo, BarLike)  # NOT because `BarLike.attr` should be `FooLike` and `foo.attr` is not
@nstarman nstarman added the topic: feature Discussions about new features for Python's type annotations label Sep 17, 2022
@nstarman nstarman changed the title isinstance check with Protocol with isinstance check with nested Protocol. Sep 19, 2022
@JelleZijlstra
Copy link
Member

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.

@nstarman
Copy link
Author

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.

@erictraut
Copy link
Collaborator

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

3 participants