Python 3 type annotations and subclasses

11,776

Solution 1

will this only accept an object of class FooBase?

No, this will accept any subclasses too. This is also stated in the Theory of Type Hinting PEP, specifically the summary of Gradual Typing section:

A type t1 is consistent with a type t2 if t1 is a subtype of t2. (But not the other way around.)

take a look at it for further pointers when dealing with type hints.

do I need to build a Union of all cases.

Even if you did, all subclasses will be eliminated from the Union and the subclasses are going to get skipped. Try creating the Union you mention:

typing.Union[Foo1, Foo2, FooBar]

and the result should be FooBar. The fact that it is an abstract class pays no difference here, Python itself uses many abstract classes in the typing module.

Take for example the Sized abc; hinting a function with Sized allows any virtual subclass (classes defining __len__) to be substituted:

def foo(obj: Sized): pass

foo([1, 2, 3, 4]) # ok
foo([2, 3, 4, 5]) # ok

Solution 2

Inheritance also applies to annotated types. Any instance of Foo which is a subtype of FooBase is also a valid object of the type FooBase. So you can pass a FooBase object but also a Foo object to the function.

If you want to limit the function to only subclasses of FooBar, you could take a look at the Type[C] construct: The type of class objects.

Solution 3

Solved using TypeVarand PyCharm checker accepts it:

from pydantic import BaseModel
class MyModel(BaseModel):
    pass


from typing import TypeVar
BaseModelType = TypeVar('BaseModelType', bound='BaseModel')

async def do_smth(value: BaseModelType):
    pass

# ...

await do_smth(MyModel())
Share:
11,776

Related videos on Youtube

Chris vCB
Author by

Chris vCB

Twentysomething former lawyer turned geek.

Updated on June 13, 2022

Comments

  • Chris vCB
    Chris vCB about 2 years

    How do I refer to 'any object that subclasses a parent class' in Python type annotations?

    Example: FooBase is an abstract base class, from which Foo1, Foo2, etc. are subclassed. I want the function to accept any descendant of FooBase. Will this do:

    def do_something(self, bar:FooBase):
        pass
    

    Or will this only accept an object of class FooBase, which of course is impossible given that FooBase is abstract? In that case, do I need to build a Union of all cases (please God I hope not!), or can I through some other way express this relationship abstractly?