You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++嵌入Python循环调用脚本时Py_Finalize()导致崩溃的原因及相关问题咨询

多次调用Python脚本时Py_Finalize()引发崩溃的问题解答

咱们来一步步拆解你遇到的问题,先从最核心的崩溃原因说起:

核心问题:Py_Finalize()为啥会让第二次循环的result变成nullptr?

Py_Finalize()的作用是彻底销毁整个Python解释器的所有运行状态——包括已加载的模块、所有对象实例、内存管理结构,相当于把Python运行时完全关掉了。

你第一次循环调用完Py_Finalize()后,整个Python环境已经被清掉了。第二次循环再调用Py_Initialize()重启解释器时,问题出在你用的pafy和youtube_dl这类扩展模块上:很多C扩展模块在Py_Finalize()之后无法被正确重新初始化,导致第二次执行PyImport_Import(py_module_name)时模块导入失败。

你代码里只检查了py_module是否为nullptr,但没做后续的错误处理——如果模块导入失败,后面获取的function就是nullptr,调用PyObject_CallObject自然会返回nullptr,这就是result变成空指针的原因。

另外要澄清一点:你在函数里声明的result是局部变量,第二次循环时是全新创建的,不是之前的result被影响了,而是当前这次函数调用的流程在模块导入/函数获取环节就失败了,导致result被赋值为nullptr。

子问题1:循环里只调用Py_Initialize()不调用Py_Finalize()会有问题吗?

首先,Python官方推荐的最佳实践是:在整个程序生命周期内只初始化一次解释器——也就是把Py_Initialize()放在main函数开头,Py_Finalize()放在程序结束前,而不是每次循环都初始化/销毁。

如果每次循环都初始化却不销毁,会带来两个主要问题:

  • 内存泄漏:每次初始化都会创建新的解释器状态、模块对象等资源,这些资源不会被自动释放,程序跑的时间越长,内存占用越高。
  • 状态污染:模块里的全局变量会保留上次运行的状态,可能导致后续调用出现莫名其妙的行为(比如pafy缓存的旧数据影响新请求)。

所以把初始化和销毁移到循环外面是更稳妥的做法。

子问题2:为啥有些只声明一次的对象引用计数大于1?能用Py_SET_REFCNT()吗?

对象引用计数大于1是很正常的,常见原因有这些:

  • Python解释器内部会缓存一些常用对象(比如短字符串、小整数),这些对象会被解释器本身持有引用,所以你创建的对象会多一个内部引用。
  • 导入的模块会被Python的sys.modules注册表持有引用,函数对象也会被模块对象持有引用,所以你拿到的py_module和function的引用计数自然会大于1。

至于Py_SET_REFCNT()——**绝对不要手动调用这个函数!**它是Python底层用于初始化对象引用计数的,手动修改会彻底破坏引用计数的正确性,要么导致内存泄漏,要么导致野指针崩溃。正确的做法是始终用Py_XDECREF()/Py_DECREF()来释放你自己持有的引用,解释器会自动处理内部的引用。

额外疑问:PyUnicode_AsWideCharString()的内存释放方式对吗?

你用delete [] play_mrl_wchar是错误的!因为PyUnicode_AsWideCharString()返回的内存是用Python自己的内存分配器分配的,不是C++的new,正确的释放方式是调用PyMem_Free(play_mrl_wchar),否则会导致内存分配器不匹配,引发未定义行为(比如崩溃、内存泄漏)。

修复后的代码示例

我给你调整了代码,解决了上述所有问题:

修改后的C++代码

#include <vector>
#include <string>
#include <iostream>
#include <Python.h>

std::string get_play_mrl(const std::string& mrl) {
    PyObject* py_module_name = PyUnicode_FromString("get_str");
    PyObject* py_module = PyImport_Import(py_module_name);
    if (!py_module) {
        PyErr_Print(); // 打印具体错误信息,方便调试
        std::cout << "Error importing module.\n";
        Py_XDECREF(py_module_name);
        return "Error";
    }

    PyObject* function = PyObject_GetAttrString(py_module, "get_play_url");
    if (!function || !PyCallable_Check(function)) {
        PyErr_Print();
        std::cout << "Error getting callable function.\n";
        Py_XDECREF(function);
        Py_XDECREF(py_module);
        Py_XDECREF(py_module_name);
        return "Error";
    }

    const char* mrl_c = mrl.c_str();
    PyObject* args = PyTuple_Pack(1, PyUnicode_FromString(mrl_c));
    PyObject* result = PyObject_CallObject(function, args);
    if (!result) {
        PyErr_Print();
        std::cout << "Error calling function.\n";
        Py_XDECREF(args);
        Py_XDECREF(function);
        Py_XDECREF(py_module);
        Py_XDECREF(py_module_name);
        return "Error";
    }

    Py_ssize_t size = 0;
    wchar_t* play_mrl_wchar = PyUnicode_AsWideCharString(result, &size);
    std::wstring ws(play_mrl_wchar, size);
    std::string play_mrl_str(ws.begin(), ws.end());
    PyMem_Free(play_mrl_wchar); // 用Python的内存释放函数

    // 释放所有PyObject引用
    Py_XDECREF(py_module_name);
    Py_XDECREF(py_module);
    Py_XDECREF(function);
    Py_XDECREF(args);
    Py_XDECREF(result);

    return play_mrl_str;
}

int main() {
    // 只初始化一次Python解释器
    if (!Py_Initialize()) {
        std::cout << "Failed to initialize Python interpreter.\n";
        return 1;
    }

    std::vector<std::string> url_vec = {/* 你的YouTube链接 */};
    for (const auto& a : url_vec) {
        std::string mrl = get_play_mrl(a);
        // 对mrl执行相关操作...
    }

    // 程序结束时销毁解释器
    Py_Finalize();
    return 0;
}

关键修改点

  1. 将Py_Initialize()和Py_Finalize()移到main函数首尾,只执行一次,避免重复初始化/销毁带来的问题。
  2. 增加了完整的错误检查(比如验证function是否可调用、result是否为空),并添加PyErr_Print()打印具体错误,方便调试。
  3. 修复了PyUnicode_AsWideCharString()返回内存的释放方式,改用Python官方的PyMem_Free()。
  4. 确保在任何错误路径下都能正确释放已创建的PyObject引用,避免内存泄漏。

内容的提问来源于stack exchange,提问作者trofchik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 17:42:37