Annotating "Instance of a subclass derived from specific base class"

Viewed 31

I have a Python method with the following signature:

def basic_sizer(self, ctrl):

where ctrl can be any wxPython control derived from wx.Control. Is there a specific Python stock annotation to indicate this other than either

def basic_sizer(self, ctrl: wx.Control):

or

def basic_sizer(self, ctrl: Union[wx.SpinCtrl, wx.BitmapButton, <other possible controls>]):

I have tried

def basic_sizer(self, ctrl: Type[wx.Control]):

as suggested here. This approach is also presented in the official documentation, but PyCharm does not accept it, flagging mismatched type. I do not want to use some PyCharm-specific hack, even if available. Rather, I am interested in whether the Python typing module provides a generic approach for this situation.

1 Answers

Abstraction

You have some base class SomeBase. You want to write and annotate a function foo that takes an argument arg. That argument arg can be an instance of SomeBase or of any subclass of SomeBase. This is how you write that:

def foo(arg: SomeBase):
    ...

Say now there are classes DerivedA and DerivedB that both inherit from SomeBase and you realize that arg should actually only ever be an instance of any of those subclasses and not be of the type SomeBase (directly). Here is how you write that:

def foo(arg: DerivedA | DerivedB):
    ...

Or in Python <3.10:

from typing import Union

def foo(arg: Union[DerivedA, DerivedB]):
    ...

To my knowledge, there is currently no way to annotate that arg should be an instance of any subclass of SomeBase but not of the class SomeBase itself.


Concrete

I am not familiar with wxPython, but you stated that you want the argument ctrl to

be any wxPython control derived from wx.Control.

According to the documentation, wx.Control is in fact a class. Your statement is still ambiguous in whether or not the ctrl argument should be assumed to be any instance of wx.Control. But if so, you do write:

def basic_sizer(self, ctrl: wx.Control):
    ...

If you want to restrict it to specific subclasses, you use the Union.

But this is wrong:

def basic_sizer(self, ctrl: Type[wx.Control]):
    ...

That would state that ctrl must be a class (as opposed to an instance of a class), namely wx.Control or any subclass of it. Unless of course that is in fact what you want... Again, your statement is ambiguous.


Mismatched types

Possible reasons for PyCharm complaining about "mismatched types" include:

  • You are calling the method basic_sizer providing an argument for ctrl that is not actually an instance of wx.Control.
  • wxPython messed up big time in their typing.
  • PyCharm has a bug in its static type checker.

If you provide the code that produces the PyCharm complaint and the specific message by PyCharm, we can sort this out.

PS:

If the PyCharm complaint arises in some other place because you assume that ctrl has certain attributes that it may not have, that would probably indicate that you actually need it to be an instance of specific subclasses. There are multiple ways to handle this, depending on the situation.

Related