Uploaded image for project: 'Qt for Python'
  1. Qt for Python
  2. PYSIDE-3012

QtCore.pyi: Improving type annotations

    XMLWordPrintable

Details

    • Bug
    • Resolution: Unresolved
    • P2: Important
    • None
    • 6.x, 6.8.2
    • Type hints
    • None
    • All

    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

          For Gerrit Dashboard: PYSIDE-3012
          # Subject Branch Project Status CR V

          Activity

            People

              cinucen Ece Cinucen
              pedantichacker Boštjan Mejak
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:

                Gerrit Reviews

                  There are 2 open Gerrit changes