pythonc++qtpyqt6pychart

Meaning of "<<" left shift operator when assigning multiple values to QBarSet in PyQt


In the video tutorial linked below, the author assigned multiple values to a QBarSet.

    set1 = QBarSet("Bob")
    set2 = QBarSet("Tom")

    set1 << 5 << 0 << 0 << 4 << 0 << 3
    set2 << 3 << 5 << 8 << 4 << 8 << 5

I don't quite understand what was done with "<<" . In python, this is a left shift operator. In c++ example (see the link), it was just like what is seen in the python code.

Can anyone please explain what is multiple "<<" doing in this python code? Can python directly translate c++ code here?

@29:32

https://www.youtube.com/watch?v=p0xlfqBWrl8&t=822s

https://doc.qt.io/qt-6/qtcharts-barchart-example.html


Solution

  • tl;dr

    In Python, like in C++ and many other languages, operators can be and are often overridden in order to get special behavior from classes that normally wouldn't support them.

    PyQt (as PySide) just overrides the operators that are also overridden in the C++ API in order to provide a consistent syntax.

    When you use set0 << 1 you are not actually bit shifting, but using the same operation that happens in the overridden operator in C++, which is to append the value to the bar set.

    Explanation

    Python does not "directly translate c++ code". The Qt binding is just implemented in order to make its syntax consistent to the official C++ API.

    Specifically, << corresponds to the __lshift__ method, meaning that if you create a subclass and override that method, the result is that whenever an instance of that class is followed by <<, that override will be called with the following object, instead of using the default numeric behavior.

    For example:

    class Test(object):
        def __init__(self, value):
            self.value = value
    
        def __lshift__(self, other):
            # "other" is the object on the right hand of the operator
            return Test(int(str(self.value) + str(other.value)))
    
        def __repr__(self):
            return 'Test({})'.format(self.value)
    
    
    >>> a = 2
    >>> b = 3
    >>> print(a << b) # same as 2 << 3
    16
    >>> a = Test(2)
    >>> b = Test(3)
    >>> print(a << b)
    Test(23)
    >>> c = Test(9)
    >>> print(a << b << c)
    Test(239)
    

    The above example obviously doesn't make a lot of sense, but it's common to override operators in order to provide syntactic sugar that could make the syntax more succinct, while keeping it clear enough.

    Coincidentally, the most simple example is the + operator for string concatenation, which is also used in the above example: text obviously does not support mathematical addition, yet 'a' + 'b' works, because, internally, __add__ is overridden to return the concatenation of the two strings.

    Similar cases exists in the Python implementation, also involving bitwise operators, like | and |= introduced for dict since 3.9:

    dict_a |= dict_b
    

    is (almost) equivalent to:

    dict_a.update(dict_b)
    

    Normally, the usage of specific operators follows the logic behind its implementation, just like the dict case above, or similar operators used for set(): the logic of bitwise operations | or & is maintained for items in a collection.

    Some times, though, the usage is more "graphical", like in the case at hand. For example, some modules override >> (__rshift__) and other operators to create "chains of objects" that are easier to write and read than using a more verbose syntax, like to create a filter chain that processes a signal in various ways before returning the result.

    In Qt (C++), &QBarSet::operator<<(const qreal &value) appends the right hand written value to the bar set (equivalent to append(): the meaning of << is mostly like saying "put a value into that object".
    The Python bindings just follow the same concept by overriding __lshift__ for that class and getting the same behavior.

    Consider the difference between the full syntax:

    set0.append(1)
    set0.append(2)
    set0.append(3)
    set0.append(4)
    set0.append(5)
    set0.append(6)
    

    Or even a more "elegant one" such as:

    values = 1, 2, 3, 4, 5, 6
    for v in values:
        set0.append(v)
    

    And the following syntax, with the convenience operator override:

    set0 << 1 << 2 << 3 << 4 << 5 << 6
    

    Even if the underlying behavior of the class and the operator is not known, the syntax is still clear enough.

    Many other Qt classes have overridden operators. For example, QRect::operator|=(QRect) that is equivalent to united(), which returns the bounding rectangle containing two rectangles:

    full = QRect()
    for other in listOfRectangles:
        full = full.united(other)
        # or
        full |= other
    

    PyQt also implements some operators on its own (many of which are not used in PySide). For instance, it QRect.contains(), uses the __contains__ override, which makes sense, since it's called when using the syntax x in y:

    p = QPoint(2, 5)
    r = QRect(0, 0, 10, 10)
    print(r.contains(p))
    # equivalent to
    print(p in r)
    

    Read more about the Python data model and all "magic methods" (also called dunder methods, as in double underscores), and consider that not all Qt operators are always available in Python, and vice versa (check the official Qt C++ API, the PyQt/PySide docs and the help() in the interactive Python shell).