Python/PyQt/PySide - How to add an argument to derived class's constructor



  • This may be as much a Python question as a PyQt/PySide one. I need some Python expert who knows about sub-classing, and also typing annotations, including overload on functions.

    I come from a C++ background. I do not understand the syntax/code I need in a class I am deriving from a PyQt class to allow a new parameter to be passed to the constructor.

    I see that I asked this question a long time ago at https://stackoverflow.com/questions/45999732/python3-typing-overload-and-parameters but never got an answer.

    I now want to sub-class from QListWidgetItem. That starts with these constructors in the (C++) docs:

    QListWidgetItem(QListWidget *parent = nullptr, int type = Type)
    QListWidgetItem(const QString &text, QListWidget *parent = nullptr, int type = Type)
    QListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent = nullptr, int type = Type)
    QListWidgetItem(const QListWidgetItem &other)
    

    My sub-class should still support these constructors. In addition to the existing text, I want my sub-class to be able to store a new optional value. At minimum/sufficient I want a new possible constructor like one of the following:

    MyListWidgetItem(const QString &text, const QVariant &value, QListWidget *parent = nullptr, int type = Type)
    # or
    MyListWidgetItem(const QString &text, QVariant value = QVariant(), QListWidget *parent = nullptr, int type = Type)
    

    See that I'm putting the new value parameter to come after text.

    So for Python I know I start with a typing overload definition (for my editor) like

    class MyListWidgetItem(QListWidgetItem)
        @typing.overload
        def __init__(self, text: str, value: typing.Any, parent: QListWidget=None, type: int=Type)
            pass
    

    Then I get to the definition bit. To cater for everything am I supposed to do:

        def __init__(self, *__args)
            # Now what??
            super().__init__(__args)
    

    Is that how we do it? Is it then my responsibility to look at __args[1] to see if it's my value argument? And remove it from __args before passing it onto super().__init__(__args)?

    Or, am I not supposed to deal with __args, and instead have some definition with all possible parameters explicitly and deal with them like that?

    Or what? This is pretty fundamental to sub-classing to add parameters where you don't own the code of what you're deriving from. It's easy in C-type languages; I'm finding it real to hard to understand what I can/can't/am supposed to do for this, I'd be really gratefully for a couple of lines to show me, please...! :)


  • Lifetime Qt Champion

    Hi,

    To the best of my knowledge, you should pass on the parameters as they are expected and then continue with your own code.



  • @SGaist
    Unfortunately I have no idea to approach that. Hence my question.

    If I type help(QtWidgets.QListWidgetItem) I get told:

    Help on class QListWidgetItem in module PyQt5.QtWidgets:
    
    class QListWidgetItem(sip.wrapper)
     |  QListWidgetItem(parent: QListWidget = None, type: int = QListWidgetItem.Type)
     |  QListWidgetItem(str, parent: QListWidget = None, type: int = QListWidgetItem.Type)
     |  QListWidgetItem(QIcon, str, parent: QListWidget = None, type: int = QListWidgetItem.Type)
     |  QListWidgetItem(QListWidgetItem)
    

    But Python, unlike C++, does not really have any such thing as "multiple overloaded definitions" of a function/method./constructor.

    You can "annotate" (via typing.overload) your various "overloads" to make it show up like this to the user/editor, which doubtless is what PyQt has done for this. Think of this as the equivalent of the multiple overloads you would export in your C++ .h file.

    But when it comes to the definition (rather than declaration) of the method, equivalent of what you'd write in your .cpp file, quite unlike C++ there can only be one, single def method(arg1, arg2, ...): ... into which you code your actual implementation. In some shape or form, that must allow for the varying number of parameters and/or types which all your overloads put together allow for. You have to write runtime code in that one definition which recognises how many parameters and of which type they are in order to figure how to correctly call the base method. And that is what I don't know how to do, especially when I wish to insert an extra argument to some new overload I am creating, so clearly I must correctly recognise and remove it when my method is called in that case because I must not pass on that parameter to the existing base class which does not accept such a parameter....

    Hence, here I would like to add, say, the following one overload:

    MyListWidgetItem(text: str, value: QVariant, parent: QListWidget = None, type: int = QListWidgetItem.Type)
    

    to those already defined for QListWidgetItem whilst maintaining all the existing ones unchanged. I don't know what code I am supposed to write in the definition to achieve this correctly.


  • Lifetime Qt Champion

    Well, AFAIK, these are all init method "overloads" so I would write it as is and in the implementation call: super(MyListWidgetItem, self).__init__("original_arguments_list") and then go on with your code.



  • @SGaist
    I know you're trying to help! I can't recall whether you know any Python or not. I don't quite know how I'm supposed to do exactly what you say....

    Briefly, let's restate the problem. The existing QListWidgetItem class apparently implements/caters for the following:

    class QListWidgetItem(sip.wrapper)
     |  QListWidgetItem(parent: QListWidget = None, type: int = QListWidgetItem.Type)
     |  QListWidgetItem(str, parent: QListWidget = None, type: int = QListWidgetItem.Type)
     |  QListWidgetItem(QIcon, str, parent: QListWidget = None, type: int = QListWidgetItem.Type)
     |  QListWidgetItem(QListWidgetItem)
    

    Note that already not only is the number of arguments variable but also so are the types. For example, looking through all the overloads parameter #1 might be a QListWidget, a str, a QIcon or a QListWidgetItem, or not even supplied, and depending on that influences what the second argument can be, etc.

    I wish to add an extra one:

    MyListWidgetItem(text: str, value: QVariant, parent: QListWidget = None, type: int = QListWidgetItem.Type)
    

    So I need to recognise this new one when it's called; I need to pull out my new value: QVariant to set my variable, I also need to remove it before calling the base class constructor . Two questions:

    1. I can only guess: is it my job in order to recognise my case to write like:
    if len(arguments) >= 2:
        if isinstance(arguments[0], str) and isinstance(arguments[1], QVariant):
            self.value = arguments[1]
            arguments.removeAt(1)
    
    1. Am I supposed to write the single __init__() definition (not overload declarations) for my new sub-class along the lines of:
    def __init__(self, *__args)
        ...
        super().__init__(*__args)
    

    or along the lines of:

    def __init__(self, arg1: typing.Union[QListWidget, str, icon, QListWidgetItem, None], arg2: typing..., arg3: typing..., arg4)
        ...
        super().__init__(arg1, arg2, arg3, arg4)
    


  • Just to close this up. Although I posted this question here, to the the PyQt mailing list and on stackoverflow, since I never got an actual answer as to how to write the derived class's constructor to accept the new positional argument while retaining all the existing overloads, in the end I have actually gone for a named argument:

    class JListWidgetItem(QtWidgets.QListWidgetItem):
        def __init__(self, *args, value: typing.Any=None, **kwargs):
            super().__init__(*args, **kwargs)
            # optional `value` member, passed in as `value=...`, stored in data(Qt.UserRole)
            # this is a common thing to want, cf. `QComboBox.itemData()`
            if value is not None:
                self.setValue(value)
    

    and caller goes e.g. JListWidgetItem("text", value=1). Some people have said this is the more "pythonic" way, who knows, but at least it works! Thanks to all.


 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.