Proper Subtyping in Python

Python doesn’t have proper subtyping as defined in Reference 1 and Reference 2.

A key point is that inherited (and not overriden) methods of a supertype invoked by an instance of the subtype should at most return an instance of the subtype. However, in Python, it is always that instance of the supertype that is returned.

My question is: Suppose the supertype’s inherited and not overridden method returns an Awaitable which eventually ‘resolve’ into an instance of the supertype, when the method is invoked by an instance of the subtype and awaited, what should it return?

I probably don’t understand what you are trying to say.
I read your post analogue this code:

class Base:
    def m(self):
        return self
                
class Child(Base):
    pass
        
o1 = Child()
print(type(o1))     # ok: <class '__main__.Child'>
o2 = o1.m()
print(type(o2))     # What do you expect here?
# It is <class '__main__.Child'>...?
# This is the *only* instance and it is the subtype.

Can you give an example which demonstrates your case?

1 Like

Hi, thank you for your prompt reply.

Here is an example:

class SuperType:

    def inherited_not_overriden_method(self):
        return SuperType()

class SubType(SuperType):
    ...

if __name__ == '__main__':

    inst_of_subtype = SubType()
    assert type(inst_of_subtype.inherited_not_overriden_method()) is SubType # this should pass but it fails

In [Reference 2], I quote the following:

the result type may be a subtype of that of the supertype. This is called covariance: the return type of the subtype method is a subtype of the return type of
the supertype method.

Hence, I am looking at how I might enable this behaviour without rewriting all the methods of the SuperType.

You can’t.

If you can access the supertype, change it to instead use type(self).

If you can’t, your best bet is to create some kind of auto-method-generator that creates wrappers for all supermethods.

There are lots of ways to provide the behavior I think you want, but explicitly writing SuperType() is not one of them. OOP isn’t going to magically change the intent of your code.

1 Like

Related:

And if you’re concerned about typing hinting the return type, see:

As the other replies say you need either change the return statement or implement function stubs in a wrapper or in the SubType.

class SuperType:

    def method1(self):
        # ...
        # many lines of code
        # ...
        return type(self)()     # either this change, if possible
        
    def method2(self):
        # ...
        # many lines of code
        # ...
        return SuperType()
        

class SubType(SuperType):
    
    # or this stub for each needed method
    def method2(self):
        o = super().method2()
        r = SubType()   # init new instance with data from o
        return r


if __name__ == '__main__':
    inst_of_subtype = SubType()
    assert type(inst_of_subtype.method1()) is SubType
    assert type(inst_of_subtype.method2()) is SubType

Edit: Sorry, reply to wrong post