I'm working with inheritance from other modules and classes made by others. And so whenever I make a subclass that inherits from an existing class like tkinter.Frame
, pandas.Dataframe
or sqlite3.Connection
I get all the arguments with their type-hints (I'm using VS Code and by pressing Tab it autocompletes def __init__
and generates them). But the type hints are always broken, especially the Ellipsis ...
.
I know it can be solved by importing the type hints if there exists TypeVar
, or TypeAlias
for it, or remove them altogether, or even remove all the extra arguments that I don't really need and use *args, **kwargs
instead, but I don't want that.
I want my subclass to function the same way as the superclass did, and only extend its functionality. I found that explicate arguments with their type hints makes it better to work with class and what it can do.
Example:
import tkinter
from tkinter import ttk
class MyFrame(ttk.Frame):
def __init__(self, master: tkinter.Misc | None = ..., *, border: tkinter._ScreenUnits = ...,
borderwidth: tkinter._ScreenUnits = ..., class_: str = ..., cursor: tkinter._Cursor = ...,
height: tkinter._ScreenUnits = ..., name: str = ..., padding: _Padding = ...,
relief: tkinter._Relief = ..., style: str = ..., takefocus: tkinter._TakeFocusValue = ...,
width: tkinter._ScreenUnits = ...) -> None:
super().__init__(master, border, borderwidth, class_, cursor, height, name,
padding, relief, style, takefocus, width)
In this example all ellipsis cause problems and some type hints don't show up, _Padding
for example.
And running the code with this class raises this error:
AttributeError: module 'tkinter' has no attribute '_ScreenUnits'
or
AttributeError: module 'tkinter' has no attribute '_Cursor'
And if I decided to remove all type hints, I would get this error because of the ellipsis:
TypeError: can only concatenate str (not "ellipsis") to str
meaning that ...
is getting passed as str
value.
Is there an easy way to inherit the type hints from the superclass, without breaking my code?
Or am I doing something wrong?
This should do the trick for you:
from typing import ParamSpec, TypeVar, Callable
from functools import update_wrapper
from tkinter import ttk
P = ParamSpec("P")
T = TypeVar("T")
def inherit_signature_from(
original: Callable[P, T]
) -> Callable[[Callable], Callable[P, T]]:
"""Set the signature of one function to the signature of another."""
def wrapper(f: Callable) -> Callable[P, T]:
return update_wrapper(f, original)
return wrapper
class MyFrame(ttk.Frame):
@inherit_signature_from(ttk.Frame.__init__)
def __init__(self, *args, **kwargs):
# do stuff?
super().__init__(*args, **kwargs)
VSCode/Pycharm/mypy will now think that MyFrame.__init__
has the same signature as ttk.Frame.__init__
and will check them accordingly.
If you're on python3.12 and your type-checker of choice has adopted PEP 695 already, you can also use the new parameter syntax, which avoids importing TypeVar
and ParamSpec
by defining them on the fly within the scope of the function:
from typing import Callable
from functools import update_wrapper
def inherit_signature_from[T, **P](
original: Callable[P, T]
) -> Callable[[Callable], Callable[P, T]]:
"""Set the signature of one function to the signature of another."""
def wrapper(f: Callable) -> Callable[P, T]:
return update_wrapper(f, original)
return wrapper
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With