py_引用

在python中变量(列表,...,所有赋值)以引用方式传递

a=100
b=a

a,b指向同一地址

id()返回变量在内存空间中的地址

此时修改A(B)的值,实为修改A(B)的指向

没有指向的量会成为垃圾被回收

可变类型:

定义之后可以修改(不可哈希, 哈希地址无意义)

列表

a=[11,22,33]

a[0]="fff"#true

字典

a={"name":"laowang"}

a["name"]="Laowang"

#a={"n ame"}

#字典的key必须是不可变类型(数字,字符串,元组)

不可变类型:

定义之后不能修改(可以哈希,同一个值哈希结果相同)

数字 a=100

a=50 #a指向一个新值

字符串 a="hello"

a[0]="w" #报错

a="whaha"#a指向一个新值

元组 a=(11,22,33)

a[0]="fff"#报错 元组不可修改

<think>我们讨论的是在C++类中嵌入Python对象时的资源管理问题,特别是关于`Py_FinalizeEx`和`Py_FinalizingEx`的使用和区别。根据Python官方文档:-`Py_FinalizeEx()`:用于关闭Python解释器,释放资源。它返回0表示失败(例如在清理过程中出现错误),返回1表示成功。-`Py_FinalizingEx()`:这是一个检查函数,用于判断解释器是否正在执行关闭过程(即是否在`Py_FinalizeEx()`的执行过程中)。在析构函数中,我们需要避免在解释器正在关闭或者已经关闭的情况下尝试释放Python对象。因此,在释放资源之前,我们使用`Py_FinalizingEx()`来检查是否正在关闭,如果是,则不应该再操作Python对象。下面我们给出一个改进的析构函数代码片段,重点展示如何正确使用`Py_FinalizingEx`和`Py_FinalizeEx`。注意:在之前的代码中,我们使用了`Py_IsInitialized()`检查解释器是否初始化,以及`!Py_FinalizingEx()`来确保不在解释器关闭过程中操作。然而,关于何时调用`py::finalize_interpreter()`,我们遵循的原则是:当最后一个使用解释器的C++实例被销毁时,才调用`Py_FinalizeEx`(通过`py::finalize_interpreter()`)。在调用`Py_FinalizeEx`之前,我们需要确保所有Python对象已经被释放(或者它们的引用计数已经被减少,这样Python的垃圾回收会在`Py_FinalizeEx`中处理)。但是,在`Py_FinalizeEx`的执行过程中(即`Py_FinalizingEx()`返回真时),我们不应该再尝试释放Python对象,因为此时Python内部已经在进行清理。因此,在析构函数中,我们首先检查解释器是否已经初始化并且不在关闭过程中,如果是,则尝试获取GIL并释放Python对象。然后,当最后一个实例销毁时,我们调用`py::finalize_interpreter()`。然而,需要注意的是,`py::finalize_interpreter()`内部会调用`Py_FinalizeEx`,因此我们必须在调用它之前确保所有相关的Python对象已经被释放(设置为`py::none()`)。下面是一个修正的析构函数代码片段(只展示相关部分):```cpp~PyEmbeddedClass(){std::lock_guard<std::mutex>lock(interpreter_mutex);//如果解释器已经初始化且不在关闭过程中,则释放Python对象if(interpreter_initialized&&Py_IsInitialized()&&!Py_FinalizingEx()){try{py::gil_scoped_acquireacquire;//释放Python对象py_instance=py::none();py_func1=py::none();py_func2=py::none();py_func3=py::none();}catch(...){//忽略异常}}//减少实例计数,如果这是最后一个实例,则终止解释器if(--instance_count==0&&interpreter_initialized){//再次检查解释器状态,避免重复关闭if(Py_IsInitialized()&&!Py_FinalizingEx()){try{//注意:在finalize_interpreter之前,我们可能需要再次获取GIL?//但是,由于我们已经在析构函数中释放了对象,并且这是最后一个实例,//而且我们持有互斥锁,所以这里可以安全地调用。py::finalize_interpreter();//内部调用Py_FinalizeEx}catch(...){//忽略异常}interpreter_initialized=false;}}}```但是,上述代码在调用`py::finalize_interpreter()`时,可能会再次尝试获取GIL。而在多线程环境中,如果其他线程还持有GIL,则可能导致问题。因此,我们建议在调用`py::finalize_interpreter()`之前,确保当前线程持有GIL,或者确保没有其他线程会干扰(因为我们已经通过互斥锁保护,并且实例计数为0,所以其他线程应该不会再有使用解释器的对象了)。然而,根据pybind11的文档,`py::finalize_interpreter()`会处理GIL。但是,为了安全起见,我们可以在调用`py::finalize_interpreter()`之前获取GIL:```cppif(--instance_count==0&&interpreter_initialized){if(Py_IsInitialized()&&!Py_FinalizingEx()){try{//在finalize之前获取GILpy::gil_scoped_acquireacquire;py::finalize_interpreter();}catch(...){//处理异常,例如记录日志}interpreter_initialized=false;}}```但是,注意:`py::finalize_interpreter()`会销毁GIL,因此在调用之后,不能再获取GIL。所以,在`py::finalize_interpreter()`之后,我们设置`interpreter_initialized`为false,以避免后续操作。关于`Py_FinalizingEx`,它是一个内部函数,在Python3.6及以后版本中可用。它用于检查解释器是否正在关闭。在关闭过程中,任何PythonCAPI的调用都是不安全的,因此我们需要避免。总结:在释放Python对象时,我们使用`!Py_FinalizingEx()`来确保解释器没有正在关闭。在终止解释器时,我们也要检查`!Py_FinalizingEx()`以避免重复关闭。但是,需要注意的是,`Py_FinalizingEx()`在Python3.6以下版本不可用。对于旧版本,我们可以通过检查`_Py_IsFinalizing()`(但这是内部符号,不稳定)或者使用其他方法,例如设置标志位。不过,pybind11可能已经处理了兼容性问题。在pybind11中,我们可以使用`Py_IsInitialized()`和`Py_FinalizeEx`,但`Py_FinalizingEx`的可用性取决于Python版本。如果我们需要支持旧版Python,可以这样做:```cpp//在Python3.6之前,没有Py_FinalizingEx,我们可以用其他方式?//实际上,在旧版本中,我们无法可靠地知道是否正在关闭。因此,我们可以依赖Py_IsInitialized(),并在关闭解释器后设置标志位interpreter_initialized=false,这样在后续的实例中就不会再使用解释器。//在析构函数中,我们只检查interpreter_initialized和Py_IsInitialized(),然后尝试释放对象。在关闭解释器时,我们先设置interpreter_initialized=false,然后再调用py::finalize_interpreter()。这样,其他实例在析构时看到interpreter_initialized为false就不会尝试释放对象(因为对象已经在关闭解释器时被释放了?)但是,这并不安全,因为多个实例的析构顺序是不确定的。//因此,我们仍然需要依赖实例计数和互斥锁。//对于旧版Python,我们只能避免使用Py_FinalizingEx,而只使用Py_IsInitialized()。但是,在解释器关闭过程中,Py_IsInitialized()仍然返回真,直到关闭完成。所以,在关闭过程中,我们可能会尝试释放对象,这会导致问题。//一个替代方案是:在调用py::finalize_interpreter()之前,先设置一个标志(如interpreter_finalizing),这样在释放对象时,如果看到这个标志,就跳过释放。但是,这个标志需要被互斥锁保护。因此,我们可以这样修改:在类中增加一个静态标志:staticstd::atomic<bool>interpreter_finalizing;在最后一个实例的析构函数中,设置这个标志,然后关闭解释器。修改后的析构函数:```cpp~PyEmbeddedClass(){std::lock_guard<std::mutex>lock(interpreter_mutex);//如果解释器正在关闭,则跳过释放Python对象(因为解释器关闭时会自动清理)if(interpreter_finalizing){//什么都不做}elseif(interpreter_initialized&&Py_IsInitialized()){try{py::gil_scoped_acquireacquire;py_instance=py::none();py_func1=py::none();py_func2=py::none();py_func3=py::none();}catch(...){}}if(--instance_count==0&&interpreter_initialized){interpreter_finalizing=true;//设置标志,告诉其他实例不要尝试释放对象if(Py_IsInitialized()){try{py::gil_scoped_acquireacquire;py::finalize_interpreter();}catch(...){}interpreter_initialized=false;}interpreter_finalizing=false;//这里实际上已经关闭,但为了安全可以重置,不过之后不会有实例了}}```但是,注意:在最后一个实例析构时,其他实例已经析构完毕(因为实例计数为0),所以这个标志实际上只会在最后一个实例析构时被设置,而其他实例在此之前已经释放了对象。所以,这个标志可能没有必要。因此,我们还是回到最初的方法,使用`Py_FinalizingEx`(如果可用)来检查。如果必须支持旧版Python(<3.6),我们可以通过运行时检查Python版本,然后使用不同的策略。但是,这增加了复杂性。在大多数情况下,建议使用Python3.6+。综上所述,我们优先使用`Py_FinalizingEx`(如果可用)。在pybind11中,我们可以通过宏来判断Python版本,并选择使用`Py_FinalizingEx`。但是,pybind11并没有提供直接的封装,所以我们可以这样:```cpp#ifPY_VERSION_HEX>=0x03060000#defineHAVE_PY_FINALIZING_EX#endif//然后在代码中#ifndefHAVE_PY_FINALIZING_EX//对于旧版,我们使用自定义标志staticstd::atomic<bool>custom_finalizing_flag=false;#endif//在析构函数中#ifdefHAVE_PY_FINALIZING_EXif(interpreter_initialized&&Py_IsInitialized()&&!Py_FinalizingEx()){#elseif(interpreter_initialized&&Py_IsInitialized()&&!custom_finalizing_flag){#endif//释放对象}if(--instance_count==0&&interpreter_initialized){#ifdefHAVE_PY_FINALIZING_EXif(Py_IsInitialized()&&!Py_FinalizingEx()){#elsecustom_finalizing_flag=true;if(Py_IsInitialized()){#endiftry{py::gil_scoped_acquireacquire;py::finalize_interpreter();}catch(...){}interpreter_initialized=false;}#ifndefHAVE_PY_FINALIZING_EXcustom_finalizing_flag=false;#endif}```这样,我们根据Python版本选择使用`Py_FinalizingEx`还是自定义标志。然而,自定义标志的方法有一个缺陷:当多个解释器实例存在时,在最后一个实例析构之前,其他实例的析构可能会因为看到`custom_finalizing_flag`为false而尝试释放对象,然后最后一个实例设置`custom_finalizing_flag`为true并关闭解释器。所以,这个自定义标志实际上并没有起到避免在关闭过程中释放对象的作用(因为关闭过程只发生在最后一个实例析构时,而其他实例在此之前已经释放了对象)。所以,对于旧版Python,我们可能只能冒险在解释器关闭过程中不释放对象(因为Python在关闭时会自动清理所有资源,所以不释放可能也没问题?)。但是,在关闭解释器之前,我们必须确保所有Python对象已经被释放,否则可能会在关闭时出现引用计数错误。因此,对于旧版Python,我们仍然需要释放对象,并且不能依赖`Py_FinalizingEx`。我们只能假设在关闭解释器之前,所有实例的析构函数都已经运行并释放了对象。所以,在最后一个实例析构时,我们释放对象并关闭解释器。但是,这要求实例的析构顺序是可控的(通常不是,因为析构顺序与创建顺序相反,且在多线程中不确定)。因此,我们通过实例计数和互斥锁来确保:在最后一个实例析构时,之前的所有实例都已经析构并释放了对象(因为每次析构都会减少实例计数,最后一个实例看到计数为0)。所以,实际上,在旧版Python中,我们不需要`custom_finalizing_flag`,只需要在释放对象时不检查`Py_FinalizingEx`(因为不可用),并且在关闭解释器时也不检查(因为不可用)。但是,在关闭解释器时,我们仍然需要检查`Py_IsInitialized()`。因此,我们简化一下:无论Python版本如何,我们都使用相同的代码,只是在有`Py_FinalizingEx`时多一层检查以避免在关闭过程中操作。所以,我们回到最初的代码,但为旧版Python去掉`Py_FinalizingEx`的检查:```cpp~PyEmbeddedClass(){std::lock_guard<std::mutex>lock(interpreter_mutex);//对于有Py_FinalizingEx的版本,我们检查;没有的,就不检查。boolcan_cleanup=interpreter_initialized&&Py_IsInitialized();#ifPY_VERSION_HEX>=0x03060000can_cleanup=can_cleanup&&!Py_FinalizingEx();#endifif(can_cleanup){try{py::gil_scoped_acquireacquire;py_instance=py::none();py_func1=py::none();py_func2=py::none();py_func3=py::none();}catch(...){}}if(--instance_count==0&&interpreter_initialized){boolcan_finalize=Py_IsInitialized();#ifPY_VERSION_HEX>=0x03060000can_finalize=can_finalize&&!Py_FinalizingEx();#endifif(can_finalize){try{py::gil_scoped_acquireacquire;py::finalize_interpreter();}catch(...){}interpreter_initialized=false;}}}```这样,我们根据Python版本条件编译,在3.6及以上版本中避免在关闭过程中操作,在旧版中则只检查解释器是否初始化。注意:在旧版Python中,如果解释器正在关闭(由其他线程触发),我们可能无法检测到,因此操作可能不安全。但这种情况很少见,因为我们的设计是只有最后一个实例才会触发关闭,并且由互斥锁保护。因此,这个方案在大多数情况下是安全的。最后,关于`Py_FinalizeEx`和`Py_FinalizingEx`,我们总结如下:-`Py_FinalizeEx()`:执行解释器的关闭,返回一个表示成功或失败的整数。-`Py_FinalizingEx()`:检查解释器是否正在关闭过程中(即已经进入`Py_FinalizeEx()`但尚未完成)。在关闭过程中,任何PythonCAPI的调用都可能引起未定义行为,因此必须避免。所以,我们在释放Python对象和关闭解释器之前,都要检查是否已经处于关闭过程中(如果Python版本支持)。以上是一个兼顾安全性和兼容性的方案。</think>### 深入解析 `Py_FinalizeEx` 和 `Py_FinalizingEx` 这两个函数是 Python C API 中用于管理解释器生命周期的重要函数,在嵌入式场景中尤为关键。我将详细解释它们的区别、使用场景和最佳实践。 #### 核心区别与功能说明 | 函数 | 签名 | 功能 | 返回值 | |------|------|------|--------| | `Py_FinalizeEx()` | `int Py_FinalizeEx(void)` | 完整终止 Python 解释器 | 0=失败, 1=成功 | | `Py_FinalizingEx()` | `int Py_FinalizingEx(void)` | 检查解释器是否正在终止 | 0=未终止, 1=正在终止 | #### 关键使用场景分析 ```cpp #include <Python.h> #include <atomic> #include <mutex> class PythonManager { static std::atomic<int> instance_count; static std::atomic<bool> interpreter_initialized; static std::mutex interpreter_mutex; public: PythonManager() { std::lock_guard<std::mutex> lock(interpreter_mutex); if (instance_count++ == 0) { // 安全初始化解释器 if (!Py_IsInitialized()) { Py_Initialize(); interpreter_initialized = true; // 注册终止时清理模块 PyRun_SimpleString( "import atexit\n" "atexit.register(lambda: print('Python cleanup complete'))" ); } } } ~PythonManager() { std::lock_guard<std::mutex> lock(interpreter_mutex); // 关键点1:检查解释器状态 const bool safe_to_clean = interpreter_initialized && Py_IsInitialized() && !Py_FinalizingEx(); if (safe_to_clean) { // 关键点2:安全释放 Python 资源 PyObject* main_module = PyImport_AddModule("__main__"); PyObject_CallMethod(main_module, "cleanup_resources", NULL); } // 关键点3:终止解释器逻辑 if (--instance_count == 0 && interpreter_initialized) { // 关键点4:最终检查 if (Py_IsInitialized() && !Py_FinalizingEx()) { int status = Py_FinalizeEx(); if (status == 0) { // 记录终止失败 logError("Py_FinalizeEx failed"); } interpreter_initialized = false; } } } void executeScript(const char* script) { if (!Py_IsInitialized() || Py_FinalizingEx()) return; PyGILState_STATE gstate = PyGILState_Ensure(); PyRun_SimpleString(script); PyGILState_Release(gstate); } }; // 初始化静态成员 std::atomic<int> PythonManager::instance_count(0); std::atomic<bool> PythonManager::interpreter_initialized(false); std::mutex PythonManager::interpreter_mutex; ``` #### 关键操作解释 1. **安全终止检查** ```cpp if (Py_IsInitialized() && !Py_FinalizingEx()) { int status = Py_FinalizeEx(); } ``` - `Py_FinalizingEx()` 确保不会在终止过程中再次调用终止函数 - 避免嵌套终止导致的崩溃 2. **资源释放顺序** ```cpp if (safe_to_clean) { // 先释放用户资源 release_user_resources(); // 再终止解释器 Py_FinalizeEx(); } ``` - 必须在 `Py_FinalizeEx()` 前释放所有 Python 对象 - 解释器终止后会无效化所有 Python 对象指针 3. **错误处理** ```cpp int status = Py_FinalizeEx(); if (status == 0) { // 处理终止失败 handle_finalize_failure(); } ``` - 返回值为0表示有未清理的扩展模块或内存泄漏 - 需要记录并分析失败原因 #### 生命周期状态图 ``` [未初始化] │ ├── Py_Initialize() ────> [运行中] │ │ │ ├── Py_FinalizingEx()=0 │ │ │ └── 触发终止 ───> [终止中] (Py_FinalizingEx()=1) │ │ │ └── Py_FinalizeEx() ──> [已终止] │ └───────────────────────────────────────────────┘ ``` #### 最佳实践建议 1. **终止策略选择** ```cpp // 安全终止序列 void safe_shutdown() { if (Py_IsInitialized()) { // 步骤1:执行 Python 级别的清理 PyObject_CallMethod(cleanup_func, "finalize", NULL); // 步骤2:释放 C++ 持有的 Python 对象 py_objects.clear(); // 步骤3:正式终止 if (!Py_FinalizingEx()) { Py_FinalizeEx(); } } } ``` 2. **多线程终止保护** ```cpp void thread_safe_finalize() { Py_BEGIN_ALLOW_THREADS // 释放 GIL std::lock_guard<std::mutex> lock(global_mutex); Py_END_ALLOW_THREADS if (Py_IsInitialized() && !Py_FinalizingEx()) { PyGILState_Ensure(); Py_FinalizeEx(); // 不需要释放 GIL,解释器已终止 } } ``` 3. **诊断工具集成** ```cpp void debug_finalize() { if (Py_IsInitialized()) { // 启用内存调试 PyRun_SimpleString("import tracemalloc; tracemalloc.start()"); int status = Py_FinalizeEx(); if (status == 0) { // 分析未释放资源 PyRun_SimpleString("tracemalloc.stop()"); } } } ``` #### 常见陷阱与解决方案 1. **双重终止问题** ```cpp // 错误示例 void unsafe_shutdown() { Py_FinalizeEx(); Py_FinalizeEx(); // 崩溃! } // 正确做法 void safe_shutdown() { if (Py_IsInitialized() && !Py_FinalizingEx()) { Py_FinalizeEx(); } } ``` 2. **资源泄漏检测** ```cpp void check_leaks() { if (Py_FinalizeEx() == 0) { // 存在未释放资源 PyObject* exc = PyErr_Occurred(); if (exc) { PyErr_Print(); } } } ``` 3. **异步终止处理** ```cpp void async_safe_shutdown() { if (Py_IsInitialized()) { // 设置终止标志 Py_AddPendingCall([](void*) { PyErr_SetString(PyExc_SystemExit, "Shutting down"); return 0; }, NULL); // 等待所有线程响应 while (Py_MakeEndFrameChecker(NULL)) { std::this_thread::sleep_for(100ms); } Py_FinalizeEx(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值