pythonqtpython-typingmypy

How to typehint return of an object's method to follow a function it receives during init?


this is a Python type-hinting question but I think a little bit of context will help before we get to the crux of the issue.

I have a class which accepts two parameters:

  1. Signal - This is something that is "emitted" by clients outside.
  2. Slot - This is connected with the signal to execute on emit.

You connect the two by doing Signal.connect(Slot). This connection is persistent until you explicitly disconnect again and the slot will be executed on every emit of the signal. (For those familiar, this is the Qt PySide2 library). I am building an object that ensures that the slot will only be executed once on the first emit and then disconnected from the signal but I am struggling to type-hint properly with two mypy errors:

import typing as _t

from Qt import QtCore as _QtCore

_R = _t.TypeVar("_R")


class SingleShotConnect:
    _INSTANCES: _t.ClassVar[_t.Set["SingleShotConnect"]] = set()

    def __init__(
        self,
        signal: "_QtCore.SignalInstance",
        slot: _t.Callable[..., _R],
    ) -> None:
        self._signal = signal
        self._slot = slot
        self._signal.connect(self._single_shot_wrapper)
        SingleShotConnect._INSTANCES.add(self)

    # error: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
    def _single_shot_wrapper(self, *args, **kwargs) -> _R:
        self._signal.disconnect(self._single_shot_wrapper)
        SingleShotConnect._INSTANCES.remove(self)

        # error: Incompatible return value type (got "_R@__init__", expected "_R@_single_shot_wrapper")
        return self._slot(*args, **kwargs)

As you can see, there are two errors related to _single_shot_wrapper() which I am not sure how to fix? I do not have access to ParamSpec if that is a possible solution due to python version limitation.


Solution

  • The class needs to be generic in _R in order for the type bound by __init__ to be available to _single_shot_wrapper.

    import typing as _t
    
    from Qt import QtCore as _QtCore
    
    _R = _t.TypeVar("_R")
    
    
    class SingleShotConnect(_t.Generic[_R]):
        # Might want SingleShotConnect[_R] here; did not test if
        # necessary
        _INSTANCES: _t.ClassVar[_t.Set["SingleShotConnect"]] = set()
    
        def __init__(
            self,
            signal: "_QtCore.SignalInstance",
            slot: _t.Callable[..., _R],
        ) -> None:
            self._signal = signal
            self._slot = slot
            self._signal.connect(self._single_shot_wrapper)
            SingleShotConnect._INSTANCES.add(self)
    
        # error: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
        def _single_shot_wrapper(self, *args, **kwargs) -> _R:
            self._signal.disconnect(self._single_shot_wrapper)
            SingleShotConnect._INSTANCES.remove(self)
    
            # error: Incompatible return value type (got "_R@__init__", expected "_R@_single_shot_wrapper")
            return self._slot(*args, **kwargs)