Skip to content
New issue

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

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

Already on GitHub? # to your account

Subclasses in typed arguments #42

Open
isaaclok opened this issue Dec 9, 2020 · 2 comments
Open

Subclasses in typed arguments #42

isaaclok opened this issue Dec 9, 2020 · 2 comments

Comments

@isaaclok
Copy link

isaaclok commented Dec 9, 2020

Hi, first of all thanks for this amazing library!
I have a problem though, when using interfaces with typed arguments. As I understand it, if a function accept argument of class A, if B inherits from A, it should also be accepted.

But when I try this code

from interface import Interface, implements
class A:
    pass

class B(A):
    pass

class I(Interface):
    def my_func(self, a: A):
        pass

class C(implements(I)):
    def my_func(self, a: B):
        pass

it throws this error

interface.interface.InvalidImplementation: 
class C failed to implement interface I:
The following methods of I were implemented with invalid signatures:
  - my_func(self, a: __main__.B) != my_func(self, a: __main__.A)
@isaaclok isaaclok changed the title Subclasses in type arguments Subclasses in typed arguments Dec 9, 2020
@ssanderson
Copy link
Owner

Hi @isaaclok. Thanks for the feedback.

I haven't used type annotations in a real project myself, so the current support for type annotations is a bit rudimentary. We currently just check that annotations for implementations exactly match the annotations on interfaces, which is more strict than necessary. I think a reasonable improvement would be for us to do type checking using issubclass, though it's a bit tricky to do this properly because annotations are no longer evaluated eagerly as of https://www.python.org/dev/peps/pep-0563/.

One thing to note is that if we taught interface to reason about subtyping relationships in annotations, we would probably still end up rejecting the code snippet in your example, because function parameters are generally modelled as contravariant, rather than covariant. That is to say, if your interface method takes an A as a parameter, then your implementation would need to take an A or a supertype of A, rather than a subtype.

The way I remember covariance vs. contravariance is as follows: if an impl satisfies your interface, then it must be possible to call your implementation with any set of arguments that could be passed to to the interface signature. Your interface signature promises that it can be called with any A (i.e., an A or any subtype of A), but your impl signature only accepts B, which is a more specific subtype of A. If there were a third type, C, which was a subtype of A but not B, then it would be valid to pass a C to the interface method, but it would not be valid to pass a C to your impl method. On the other hand, if your impl requires a supertype of A, then you know that an A (or any subtype of A) will still be accepted, so that's ok.

@hasii2011
Copy link

Is this project still active?

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

3 participants