Search code examples
pythonreferencepysidepyside6qbuffer

Why do I need to assign QBuffer to a variable in order not to crash QImageReader?


That's such a weird bug, if it's even a bug.

This works:

from PySide6.QtCore import QBuffer, QByteArray, QFile, QIODevice

from PySide6.QtGui import QImageReader


image_path = Path('/home/p10/testIAMAGAGADS.png')
file = QFile(image_path)
file.open(QIODevice.ReadOnly)
blob = file.readAll()
buffer = QBuffer(blob)
image_reader = QImageReader(buffer)

This crashes PySide6:

from pathlib import Path
from PySide6.QtCore import QBuffer, QByteArray, QFile, QIODevice

from PySide6.QtGui import QImageReader


image_path = Path('/home/p10/testIAMAGAGADS.png')
file = QFile(image_path)
file.open(QIODevice.ReadOnly)
blob = file.readAll()
image_reader = QImageReader(QBuffer(blob))

I would expect an object created in a specific scope (albeit passed as an argument) to stay alive at least till the end of that scope. PS: The same thing happens when I read the image from a file to a bytes object and pass it to QBuffer without binding it to a variable beforehand.


Solution

  • There's no bug or weirdness here. The second example does not keep a reference to the QBuffer object, so it gets garbage-collected (by Python). This is no different than doing e.g. len(dict(a=1)): the dict will be deleted immediately after len returns. The image reader takes a pointer to a QIODEvice. If it tries to read from that device (via the pointer) after it's been deleted, a fatal error will occur.

    The Qt docs will usually explicitly state whether ownership passes to the receiver, and there's no indication that QImageReader will do this. In fact, the docs include the following:

    Note: QImageReader assumes exclusive control over the file or device that is assigned. Any attempts to modify the assigned file or device during the lifetime of the QImageReader object will yield undefined results.

    One way to fix the second example would be to make the QBuffer a child of the QFile (since they both inherit QObject):

    from pathlib import Path
    from PySide6.QtCore import QBuffer, QByteArray, QFile, QIODevice
    from PySide6.QtGui import QImageReader
    
    image_path = Path('/home/p10/testIAMAGAGADS.png')
    file = QFile(image_path)
    file.open(QIODevice.OpenModeFlag.ReadOnly)
    blob = file.readAll()
    image_reader = QImageReader(QBuffer(blob, file))
    image = image_reader.read()
    print(image)
    

    (NB: when the QFile is deleted, Qt will automatically delete all its children as well).