Skip to content

How to receive a return value from python correctly? #259

Description

@ChenxiLiu-code

OS: Windows11 24H2
Python Version: 3.13
Qt Version: 6.8
PythonQt Version: 3.6
Compiler: msvc

I do two tests about receiving return value from python.
The first one is running well:

/// Test 1: Python Class
    qDebug() << "Test 1: Python Class";
    {
        QVariant pyobj = context.evalScript("TestPyObj()", Py_eval_input); /// The TestPyObj Class is defined by Python Code
        qDebug() << pyobj;
        if(pyobj.isValid()) {
            PythonQtObjectPtr pyobj_ptr(pyobj);
            qDebug() << "PyObj RefCount:" << pyobj_ptr->ob_refcnt; ///2 pyobj & pyobj_ptr
            pyobj.clear();
            qDebug() << "PyObj RefCount:" << pyobj_ptr->ob_refcnt; ///1 pyobj
            pyobj_ptr.call("print");
        }
        /// destroyed
    }

But if I change the TestPyObj Class to TestCppObj which is defined in Cpp code and registered to Python by PythonQt, the return value will be destroyed directly before the cpp code receive it. The Test Code is below

///... init code

PythonQt::self()->registerCPPClass("TestCppObj", "", "TestUtils", PythonQtCreateObject<TestCppObjWrapper>, PythonQtSetInstanceWrapperOnShell<TestCppObjShell>);

///... other code

qDebug() << "Test 2: C++ Class";
    {
        QVariant pyobj = context.evalScript("pqt.TestUtils.TestCppObj()", Py_eval_input);
        qDebug() << pyobj;
        if(pyobj.isValid()) {
            
        }
    }

The printed messages are:

Test 2: C++ Class
TestCPPObj created 2258935037504
TestCppObjShell created 2258935037504
TestCppObjShell destroyed 2258935037504
TestCPPObj destroyed 2258935037504
QVariant(void*, 0x20df3053e40) ///  0x20df3053e40 ==2258935037504

The class decleration is below:

/// Class 
class TestCppObj {
    public:
        TestCppObj() {
            std::cout << "TestCPPObj created " << size_t(this) << std::endl;
        }
        virtual ~TestCppObj() {
            std::cout << "TestCPPObj destroyed " << size_t(this) << std::endl;
        }
    
        virtual void print() {
            std::cout << "Hello from C++" << std::endl;
        }
};

/// Shell Class
class TestCppObjShell : public TestCppObj {
public:
    TestCppObjShell(): _wrapper(nullptr) {
        std::cout << "TestCppObjShell created " << size_t(this) << std::endl;
    }
    ~TestCppObjShell() {
        std::cout << "TestCppObjShell destroyed " << size_t(this) << std::endl;
        PythonQtPrivate* priv = PythonQt::priv();
        if (priv) { priv->shellClassDeleted(this); }
    }

    void print() override {
        std::cout << "Shell Print" << std::endl;
        if (_wrapper) {
            PYTHONQT_GIL_SCOPE;
            if(((PyObject*)_wrapper)->ob_refcnt > 0) {
                static PyObject* name = PyString_FromString("print");
                PyObject* obj = PyBaseObject_Type.tp_getattro((PyObject*)_wrapper, name);
                if (obj) {
                    static const char* argumentList[] ={""};
                    static const PythonQtMethodInfo* methodInfo = PythonQtMethodInfo::getCachedMethodInfoFromArgumentList(1, argumentList);
                    void* args[1] = {nullptr};
                    PyObject* result = PythonQtSignalTarget::call(obj, methodInfo, args, true);
                    if (result) { Py_DECREF(result); }
                    Py_DECREF(obj);
                    return;
                } else {
                    PyErr_Clear();
                }
            }
        }
        TestCppObj::print();
    }
    PythonQtInstanceWrapper* _wrapper { nullptr };
};

/// Wrapper Class
class TestCppObjWrapper : public QObject {
    Q_OBJECT
public slots:
    TestCppObj* new_TestCppObj() {
        return new TestCppObjShell ();
    }

    void delete_TestCppObj(TestCppObj* obj) {
        delete obj;
    }

    void print(TestCppObj* obj) {
        obj->print();
    }
};

Thanks for any help.

Activity

  1. usiems commented on Apr 30, 2025

    @usiems
    Contributor

    This is somewhat expected. Objects in Python are reference-counted. If the reference count goes down to zero, the object is deleted.

    In the case of Python objects, the reference count is increased by the PythonQtObjectPtr stored in the QVariant.

    For the C++ object, there is no extra reference count. After the C++ object is returned from the evalScript method, the scope of the Python code is deleted, the reference count of the wrapper goes back zero, and the wrapper and its wrapped object are deleted.

    There are two things you can do:

    • Store a reference to the object in Python. Create a new Python module or "re-use" an existing one, and store your object in it. This way there will be a remaining reference to the wrapper, and the object will live on.
    • Customize the reference counting of your class. You can retrieve the PythonQtClassInfo of the class your registered with PythonQt::self()->getClassInfo(name), and call setReferenceCounting on it to set callbacks for increasing and decreasing the reference count of the wrapped object. Then you can to decide how to handle it when your wrapped object isn't referenced in Python anymore.

    I guess the first option is easier though.

  2. locked and limited conversation to collaborators on Sep 30, 2025
  3. converted this issue into a discussion #318 on Sep 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions