pythonpython-typingpyright

Python type-hinting: a Tkinter Event


I'm learning how to create GUIs with TKinter in Python. I'm using VSCode as my editor and I have this piece of code:

[...]
    # create a button and bind an action to the button press event
    button:tk.Button = tk.Button(master=gridFrame, text="Button")
    button.bind("<ButtonPress-1>", self.play)

[...]
    def play(self, event: tk.Event)->None:
        """Hanlde a player's move"""
        clickedBtn: tk.Button = event.widget

Pylance complains on the type hint for event being of type Event: Expected type arguments for generic class "Event". I've tried with this other hint:

def play(self, event: tk.Event[tk.Button])->None:

Which silences the warning, but when I try to run the code with this, I get a TypeError exception:

    def play(self, event: Event[tk.Button])->None:
                          ~~~~~^^^
TypeError: type 'Event' is not subscriptable

Is there a way to hint this or should I settle for Any or silence the warning and admit defeat?

Thanks!


Solution

  • STerliakov already provided you with a sufficient solution in the comments.

    Since type-hints existed in python they are are always evaluated as well (*). It is possible that there are stubs (.pyi) files defining generic types that are not generic at runtime. This is the situation you encounter: the type-checker expects a substriction, however at runtime it is not valid.

    Solution 1

    Immediate evaluation has many problems: NameErrors, circular-imports, efficiency, ...; to circumvent this PEP 563 allowed stringified annotations:

    def play(self, event: 'tk.Event[tk.Button]') -> None: ...

    Solution 2

    In parallel from __future__ import annotations was introduced that treats all annotations in the file as strings from a runtime perspective stored. They are stored in the __annotations__ attribute.

    from __future__ import annotations
    
    def play(self, event: tk.Event[tk.Button]) -> None: ...
    
    print(play.__annotations__)
    {'event': 'tk.Event[tk.Button]', 'return': 'None'}
    

    In Python 3.14

    The long deferred PEP 649 – Deferred Evaluation Of Annotations Using Descriptors is implemented. Annotations are not evaluated immediately. Runtime invalid annotations are valid. They only raise an error when accessed, depending on the method and provided globals. For example, accessing __annotations__ for the first time (but not before) will try to evaluate the annotation, i.e. only then will tk.Event be subcripted.
    If you do not want access to the actual annotations this behavior can be ignored. For a type-checker it is the same as Solution 1 and 2.


    Footnote:

    (*) the only exceptions are annotations inside a functions body, which have zero influence on the runtime.