python-interface icon indicating copy to clipboard operation
python-interface copied to clipboard

Subclasses in typed arguments

Open isaaclok opened this issue 5 years ago • 2 comments

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 avatar Dec 09 '20 13:12 isaaclok

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.

ssanderson avatar Dec 09 '20 19:12 ssanderson

Is this project still active?

hasii2011 avatar Mar 16 '22 03:03 hasii2011