I'm currently working on removing all the type errors from my Python project in VS Code.
Assume you have a Python class that has an asymmetric property. It takes any kind of iterable and converts it into a custom list subclass with additional methods.
class ObservableList(list):
"""list with events for change, insert, remove, ..."""
# ...
class MyFrame:
@property
def my_list(self) -> ObservableList:
return self._my_list
@my_list.setter
def my_list(self, val: typing.Iterable):
self._my_list = ObservableList(val)
# ...
my_frame = MyFrame()
VS Code (i.e. Pyright) will correctly deduce that:
my_frame.my_list
using any iterable, andmy_frame.my_list
will always be an ObservableList
.Now, let's assume that there is no actual @property
code. Instead, the property is implemented dynamically using __setattr__
and __getattr__
. (Context: We're talking about a GUI generator which provides automatic bindings.)
I want to use a declaration on class level to tell the typechecker that this property exists, without actually spelling it out:
class MyFrame(AutoFrame):
my_list: ???
# ...
(AutoFrame
provides the __getattr__
/ __setattr__
implementation.) What can I put in place of the ???
to make this work?
ObservableList
, Pyright complains when I assign a plain list to the property.Iterable
or list
, Pyright complains when I access ObservableList
-specific methods.list | ObservableList
: Pyright assumes that the property could return both, and list
misses the additional methods.Re: close vote: the linked question's answer basically boils down to going back to square one (implementing the property explicitly). The point of using AutoFrame
is specifically to get rid of that repetitive boilerplate code. Just imagine doing this for a GUI frame with a dozen bound controls. I can live with a single added declaration line but not much more.
You can use the Any-Trick, see my answer on how the four different cases work, but in short, if you type it as:
from typing import Any, reveal_type
class ObservableList(list):
"""list with events for change, insert, remove, ..."""
def foo(self) -> str:
...
class MyFrame:
my_list: ObservableList | Any
MyFrame().my_list = [2, 3] # OK
value: str = MyFrame().my_list.foo() # OK, add :str to avoid value being str | Any
Of course that will have the downside that any assignment, will be okay as well:
MyFrame().my_list = "no error"
so you need to be aware of that.
Alternatively, you can implement the property
behind a if TYPE_CHECKING
block OR in a .pyi
file: no-runtime influence, correct-typing, but boilerplate:
class MyFrame:
if TYPE_CHECKING:
@property
def my_list(self) -> ObservableList: ...
@my_list.setter
def my_list(self, val: typing.Iterable): ...