Details
-
Bug
-
Resolution: Unresolved
-
P2: Important
-
None
-
6.x, 6.8.2
-
None
Description
The problem
We were never able to fully annotate Signal of our custom signals that carry data of a particular type (or particular types). The best we can do is to have a typing.ClassVar[Signal] type annotation for a custom signal object such as Signal(int).
We would like to give more expressive type annotations to our custom signal objects, so like typing.ClassVar[Signal[int]], for example. But if we do this, mypy immediately complains:
mypy: error
type-arg - "Signal" expects no type arguments, but 1 given
What can we do about it?
Well, we can make the Signal class in QtCore.pyi file be generic, and we will be able to annotate Signal(int) as typing.ClassVar[Signal[int]], without mypy complaining.
Improved type stub for Signal class
So, to make annotating the type(s) that a custom signal carries possible, I propose this type stub in QtCore.pyi file.
# QtCore.pyi # In the module-level scope, immediately after the PlaceHolderType declaration T = typing.TypeVar("T") # And then almost at the end of the QtCore.pyi file class Signal(typing.Generic[T]): def __init__(self, /, *types: type, name: str | None = ..., arguments: typing.List[str] | None = ...) -> None: ... @typing.overload def __get__(self, instance: PySide6.QtCore.QObject, owner: typing.Any | None, /) -> PySide6.QtCore.SignalInstance: ... @typing.overload def __get__(self, instance: None, owner: typing.Any | None, /) -> PySide6.QtCore.Signal: ...
What I did here is that I've added T = typing.TypeVar("T") and made the Signal class inherit from typing.Generic[T], rather than inheriting from object. That's it.
The placebo
Note that this is now just a placebo. If we give a type annotation like typing.ClassVar[Signal[str]] to a custom signal object like Signal(int), mypy does not complain when it should have, since the type annotation expresses that the data carried by the custom signal object is of the str type (i.e., the annotation is Signal[str]), where it really carries data of the int type (i.e., the actual signal object is Signal(int)).
The reasoning
But, in all fairness, type stubs in PySide6 have always been written in a way just to shut up mypy from complaining and not to provide specific typing information. In PySide6's type stubs, there are 129 uses of typing.Any, just in the QtCore.pyi file alone, which tells a lot.
So, there's no harm (since mypy can't really check whether the annotation and the signal's datatype matches) in making the Signal class to be generic, so that we can at least annotate the type(s) of data that our custom signals will carry, if we choose to have properly annotated data-carrying signals, but without having mypy breathing behind our necks.
Conclusion
Type annotations in Python (and hence in PySide6's type stubs) are not some strict thing to adhere to, else your app will break into 1000 pieces. No, they are more like hints to whoever is reading the code to be informed with what datatypes an app is dealing with.
So, let's allow annotations such as typing.ClassVar[Signal[int]]. This way everyone will know immediately that the signal carries some ints, so the slot should have a parameter of the same type and deal with the received data appropriately.
Thank you.
Attachments
Issue Links
- relates to
-
PYSIDE-2942 Type hints: Slot() annotations fails in different cases
-
- Reported
-
-
PYSIDE-2839 Can't properly type hint a custom signal in my Protocol class
-
- Open
-
Gerrit Reviews
For Gerrit Dashboard: PYSIDE-3012 | ||||||
---|---|---|---|---|---|---|
# | Subject | Branch | Project | Status | CR | V |
625791,5 | Improve type annotations | dev | pyside/pyside-setup | Status: NEW | +2 | 0 |
625809,1 | WIP: Improve type annotations | dev | pyside/pyside-setup | Status: NEW | -2 | 0 |