0

I have the following code,

class Label(QLabel):
    def __init__(self, *args, 
        width:int|None=None,   
        height:int|None=None,
        minWidth:int|None=None,
        minHeight:int|None=None,
        maxWidth:int|None=None,
        maxHeight:int|None=None,
        stretchFactor:int=0,
        align:str|None=None,
        color:str|None=None,
        bgColor:str|None=None, 
        border:str|None=None,
        fontSize:int|None=None,
        fontWeight:str|None=None,
        fontFamily:str|None=None,
        padding:str|None=None,
        hExpanding=False,       
        vExpanding=False,    
        name:str|None=None,    
        styleSheet:str='',
        labelImg:str|None=None,       
        **kwargs):
        
        super().__init__(*args, **kwargs)

        ss(self, width=width, height=height, minWidth=minWidth, minHeight=minHeight, 
           maxWidth=maxWidth, maxHeight=maxHeight, 
            stretchFactor=stretchFactor, align=align, color=color, bgColor=bgColor, 
            border=border, fontSize=fontSize, fontWeight=fontWeight, 
            fontFamily=fontFamily, padding=padding, hExpanding=hExpanding, 
            vExpanding=vExpanding, name=name, styleSheet=styleSheet)

        if labelImg is not None:
            self.setPixmap(QtGui.QPixmap(labelImg))



class Button(QPushButton):
    def __init__(self, *args, 
        width:int|None=None,   
        height:int|None=None,
        minWidth:int|None=None,
        minHeight:int|None=None,
        maxWidth:int|None=None,
        maxHeight:int|None=None,
        stretchFactor:int=0,
        align:str|None=None,
        color:str|None=None,
        bgColor:str|None=None, 
        border:str|None=None,
        fontSize:int|None=None,
        fontWeight:str|None=None,
        fontFamily:str|None=None,
        padding:str|None=None,
        hExpanding=False,       
        vExpanding=False,    
        name:str|None=None,    
        styleSheet:str='',
        onClick:Callable|None=None,     
        **kwargs):
        

        super().__init__(*args, **kwargs)

        ss(self, width=width, height=height, minWidth=minWidth, minHeight=minHeight, 
           maxWidth=maxWidth, maxHeight=maxHeight, 
            stretchFactor=stretchFactor, align=align, color=color, bgColor=bgColor, 
            border=border, fontSize=fontSize, fontWeight=fontWeight, 
            fontFamily=fontFamily, padding=padding, hExpanding=hExpanding, 
            vExpanding=vExpanding, name=name, styleSheet=styleSheet)
        
        self.clicked.connect(onClick)

, and many other similar self-defined widgets like that.

You can see, so many initial args are the same, it leads to many duplicated code. I want to just define them once.

But I also want IDEs, like vscode, could inpect the args of those widget classes, so when I input like Label(wid, it could give auto-complete suggestions.

How to do that?

2
  • 1
    Use this answer: stackoverflow.com/a/76466821 - I don't know what IDE you're using, by PyCharm certainly does recognise signatures defined this way. Commented Feb 22 at 4:22
  • @dROOOze, many thanks for you help. It works great. Commented Feb 22 at 5:41

1 Answer 1

1

Updated after dROOOze's commenting

With @dROOOze's suggestion, I rewrote the code like this

from typing import TypedDict, Unpack, Required, NotRequired


class WidgetArgs(TypedDict):
    width        :NotRequired[int]
    height       :NotRequired[int]
    minWidtht    :NotRequired[int]
    maxWidth     :NotRequired[int]
    maxHeight    :NotRequired[int]
    stretchFactor:NotRequired[int]
    color        :NotRequired[str]
    bgColor      :NotRequired[str]
    border       :NotRequired[str]
    fontSize     :NotRequired[int]
    align        :NotRequired[str]
    fontWeight   :NotRequired[str]
    fontFamily   :NotRequired[str]
    padding      :NotRequired[str]
    hExpanding   :NotRequired[bool]
    vExpanding   :NotRequired[bool]
    name         :NotRequired[str]
    styleSheet   :NotRequired[str]

def popStyleArgs(kwargs:dict):
    retDict = {}
    for name in WidgetArgs.__annotations__.keys():
        val = kwargs.pop(name,None)
        if val is not None:
            retDict[name] = val
    return retDict


class Label(QLabel):
    def __init__(self, *args, 
        labelImg:str|None=None,      
        **kwargs: Unpack[WidgetArgs]):
        
        styleArgs = popStyleArgs(kwargs)
        super().__init__(*args, **kwargs)
        ss(self, **styleArgs)

        if labelImg is not None:
            self.setPixmap(QtGui.QPixmap(labelImg))



class Button(QPushButton):
    def __init__(self, *args, 
        onClick:Callable|None=None,     
        **kwargs: Unpack[WidgetArgs]):
        
        styleArgs = popStyleArgs(kwargs)
        super().__init__(*args, **kwargs)
        ss(self, **styleArgs)

        self.clicked.connect(onClick)

It works well.


And in VSCode, I could get auto-complete suggestion like this, which is great.

enter image description here


When mouse hovers on the type, it will show definition like this, it's ok.

enter image description here


In Pycharm, even better, when hovering, it will show the referenced unpack types, like this

enter image description here


The only pitty is, I have to copy the docstrings of arguments to every widget's initial method __init__(). Any suggestions?

Sign up to request clarification or add additional context in comments.

5 Comments

Great that you got it working for autocomplete. Just bear in mind that, keyword arguments with a default value should be annotated as typing.NotRequired[<type>] in the TypedDict class body, as otherwise when someone tries to call the class constructor they'll be forced to provide all the keyword arguments. I don't quite know what you mean with the "document str of args", do you mean parameter docstrings? This is how you would do it for TypedDict annotations, but I'm not sure if it works when transferred to **kwargs on a method signature.
I thought NotRequired was necessary too, but it seems without it, it works well, see this.
It's a static typing error without NotRequired (you can run the example on mypy Playground). At runtime, of course, type annotations are completely ignored so the code won't error.
Yes, there are warnings in IDEs like Pycharm. Thanks, I will correct that.
And I did mean parameter docstrings. I've put docstrings after every attributes, and created docstring of whole class also, but Pycharm did not display them when cursor hovering on instansing class names like Label().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.