Python-单例模式-只初始化一次

本文介绍了两种实现单例模式的方法:一种是通过私有类属性控制实例的唯一性和初始化过程;另一种是使用装饰器来实现。文章详细展示了如何确保类实例只被创建一次,并且初始化也仅执行一次。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

##单例模式 只初始化一次

单例模式创建的原理就是借用了私有类属性的性质,我们知道私有类属性不能直接在类外被修改,所以用私有类属性的状态当做条件来控制类内方法的调用

方法一:

下面代码中的__species负责控制__new__方法,保证实例对象只能被创建一次,创建多个对象,均指向同一个数据内存位置

__first_init负责控制__init__方法,保证再次创建的对象不能更改初始化的内容

class Animal(object):

    __species = None
    __first_init = True
    
    def __new__(cls, *args, **kwargs):
        if cls.__species == None:
            cls.__species = object.__new__(cls)
        return cls.__species

    def __init__(self,name):
        print("-----init----")
        if self.__first_init:
            self.name = name
            self.__class__.__first_init = False
            # 相当于Animal.__first_init = False

    def __str__(self):
        return "-------str---%s" %self.name


if __name__ == '__main__':
    dog = Animal('二哈')
    print(dog)
    print(id(dog))  
    dog2 = Animal('金毛')
    print(dog)
    print(id(dog2))

执行结果:
-----init----
-------str---二哈
139764904013104
-----init----
-------str---二哈
139764904013104


调用new方法必须要有return, 返回创建的实例对象或者是父类的__new__方法创建实例对象

由执行结果可知,两次创建的实例对象指向了同一个id,即 创建单例模式
第二次创建的实例对象dog2“金毛”,并未创建成功,仍然是第一次创建出来的实例对象dog"二哈",即 初始化一次

方法二:

import functools

def singleton(cls):
    _instances = {}
    @functools.wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in _instances:
            _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]
	 return get_instance


@singleton
class Animal:

    def __init__(self, name):
        print("-----init----")
        self.name = name

    def __str__(self):
        return "-------str---%s" % self.name


if __name__ == '__main__':
    dog = Animal('二哈')
    print(dog)
    print(id(dog))
    dog2 = Animal('金毛')
    print(dog)
    print(id(dog2))

执行结果:
-----init----
-------str—二哈
4327353816
-------str—二哈
4327353816

<think>我们将在C++中实现一个单例模式来管理Python解释器的初始化和销毁。根据pybind11的要求,解释器应该在整个程序生命周期中只初始化一次,并在程序退出时销毁。我们使用引用计数来确保当没有任何对象使用解释器时才销毁,但注意:pybind11不支持多次初始化和销毁,因此我们通常在整个程序运行期间保持解释器存在。然而,为了安全地管理,我们可以设计一个引用计数的单例,但不在引用计数归零时销毁解释器,而是在程序退出时(单例析构时)销毁。这样确保整个程序运行期间解释器只初始化一次,销毁一次。以下是一个示例实现:```cpp#include<pybind11/embed.h>#include<mutex>#include<atomic>namespacepy=pybind11;classPythonInterpreterManager{public:staticPythonInterpreterManager&getInstance(){staticPythonInterpreterManagerinstance;returninstance;}//增加引用计数,并在需要时初始化解释器voidacquire(){std::lock_guard<std::mutex>lock(m_mutex);if(m_refCount++==0){if(!m_initialized){py::initialize_interpreter();//可以在这里进行一些初始化设置,比如添加路径autosys=py::module_::import("sys");py::listpath=sys.attr("path");path.append("./");//添加当前路径,或者自定义路径m_initialized=true;}}}//减少引用计数,但不销毁解释器。销毁解释器将在单例析构时进行。voidrelease(){std::lock_guard<std::mutex>lock(m_mutex);--m_refCount;//注意:不在release中销毁解释器,因为pybind11不支持多次初始化和销毁}//禁止拷贝和赋值PythonInterpreterManager(constPythonInterpreterManager&)=delete;voidoperator=(constPythonInterpreterManager&)=delete;private:PythonInterpreterManager():m_refCount(0),m_initialized(false){}~PythonInterpreterManager(){std::lock_guard<std::mutex>lock(m_mutex);if(m_initialized){py::finalize_interpreter();}}std::mutexm_mutex;std::atomic<int>m_refCount{0};boolm_initialized;};//使用示例:在需要使用Python的类中,在构造函数中acquire,析构函数中releaseclassMyPythonUsingClass{public:MyPythonUsingClass(){PythonInterpreterManager::getInstance().acquire();//其他初始化,比如导入模块等}~MyPythonUsingClass(){PythonInterpreterManager::getInstance().release();//注意:这里release只是减少引用计数,不会销毁解释器}//其他成员函数...};```###解释1.**单例模式**:使用静态局部变量确保线程安全的单例(C++11保证)。2.**引用计数**:使用`m_refCount`记录当前使用解释器的对象数量。当第一个对象使用时初始化解释器,但注意后续对象会复用已初始化的解释器。3.**初始化**:在`acquire`中,当引用计数从0变为1时,初始化解释器(如果尚未初始化)。4.**避免多次销毁**:不在`release`中销毁解释器,因为pybind11不允许多次销毁和重新初始化。解释器将在单例析构时(程序退出时)被销毁。5.**线程安全**:使用互斥锁保护引用计数和初始化/销毁操作。###注意事项-这个设计假设程序运行期间可能会有多个使用Python的对象创建和销毁,但解释器只初始化一次-在程序退出时,单例的析构函数会调用`py::finalize_interpreter()`,此时应确保没有其他线程或对象在使用Python-在`MyPythonUsingClass`中,可以安全地执行Python操作,因为解释器在第一个对象创建时初始化,并在程序退出时销毁。###使用场景在需要嵌入Python的C++应用程序中,尤其是多线程环境下,通过这种方式可以安全地管理Python解释器的生命周期。§§相关问题§§:1.如何在多线程环境中安全地调用Python函数(考虑到GIL)?2.pybind11中如何将C++对象暴露给Python,并允许Python扩展C++代码?3.当需要在C++中重新加载Python模块时,应该采取什么策略?4.如何通过pybind11在C++和Python之间传递异常?</think>### C++实现单例模式管理Python解释器初始化与销毁 以下是使用单例模式安全管理Python解释器初始化和销毁的完整实现示例: ```cpp #pragma once #include <pybind11/embed.h> #include <mutex> #include <atomic> namespace py = pybind11; class PythonInterpreterManager { public: // 获取单例实例 static PythonInterpreterManager& getInstance() { static PythonInterpreterManager instance; return instance; } // 增加引用计数并初始化解释器 void acquire() { std::lock_guard<std::mutex> lock(m_mutex); if (m_refCount++ == 0 && !m_initialized) { initializeInterpreter(); } } // 减少引用计数 void release() { std::lock_guard<std::mutex> lock(m_mutex); --m_refCount; } // 禁止拷贝和赋值 PythonInterpreterManager(const PythonInterpreterManager&) = delete; void operator=(const PythonInterpreterManager&) = delete; private: PythonInterpreterManager() = default; // 私有构造函数 ~PythonInterpreterManager() { if (m_initialized && !m_finalized) { finalizeInterpreter(); } } void initializeInterpreter() { // 初始化Python解释器 py::initialize_interpreter(); // 添加自定义路径 auto sys = py::module_::import("sys"); py::list path = sys.attr("path"); path.append("/path/to/your/python/modules"); m_initialized = true; m_finalized = false; } void finalizeInterpreter() { py::finalize_interpreter(); m_finalized = true; } std::mutex m_mutex; std::atomic<int> m_refCount{0}; bool m_initialized = false; bool m_finalized = false; }; // 使用Python的类示例 class PythonUser { public: PythonUser() { PythonInterpreterManager::getInstance().acquire(); initializePythonObjects(); } ~PythonUser() { cleanupPythonObjects(); PythonInterpreterManager::getInstance().release(); } // Python功能封装 void callPythonFunction() { py::gil_scoped_acquire acquire; // 获取GIL try { if (pyFunc) { pyFunc(); } } catch (const py::error_already_set& e) { // 处理Python异常 } } private: void initializePythonObjects() { py::gil_scoped_acquire acquire; try { auto module = py::module_::import("example_module"); pyClass = module.attr("ExampleClass")(); pyFunc = pyClass.attr("example_method"); } catch (const py::error_already_set& e) { // 初始化失败处理 } } void cleanupPythonObjects() { py::gil_scoped_acquire acquire; pyFunc = py::none(); pyClass = py::none(); } py::object pyClass; py::object pyFunc; }; ``` ### 关键实现解释 1. **单例模式实现**: ```cpp static PythonInterpreterManager& getInstance() { static PythonInterpreterManager instance; return instance; } ``` - 使用Meyer's单例实现,线程安全且延迟初始化 - 静态局部变量保证单例唯一性 2. **引用计数管理**: ```cpp void acquire() { std::lock_guard<std::mutex> lock(m_mutex); if (m_refCount++ == 0 && !m_initialized) { initializeInterpreter(); } } ``` - 首次使用时初始化解释器 - 引用计数跟踪使用情况 3. **安全的解释器销毁**: ```cpp ~PythonInterpreterManager() { if (m_initialized && !m_finalized) { finalizeInterpreter(); } } ``` - 在单例析构时销毁解释器 - 确保整个程序生命周期只销毁一次 4. **Python对象生命周期管理**: ```cpp void cleanupPythonObjects() { py::gil_scoped_acquire acquire; pyFunc = py::none(); pyClass = py::none(); } ``` - 在析构函数中显式释放Python对象 - 使用`py::none()`安全释放引用 5. **GIL管理**: ```cpp void callPythonFunction() { py::gil_scoped_acquire acquire; // Python操作... } ``` - 所有Python操作都需要在GIL保护下进行 - `gil_scoped_acquire`自动管理GIL ### 使用示例 ```cpp int main() { // 示例1: 单个Python用户 { PythonUser user1; user1.callPythonFunction(); } // 此处user1析构,但解释器保持活动 // 示例2: 多个Python用户 std::vector<std::unique_ptr<PythonUser>> users; for (int i = 0; i < 5; i++) { users.push_back(std::make_unique<PythonUser>()); users.back()->callPythonFunction(); } // 所有用户销毁后,程序退出时解释器自动销毁 return 0; } ``` ### 最佳实践 1. **初始化路径**: ```cpp void initializeInterpreter() { py::initialize_interpreter(); auto sys = py::module_::import("sys"); py::list path = sys.attr("path"); path.append("/custom/path"); // 添加更多路径... } ``` 2. **异常处理**: ```cpp try { // Python操作... } catch (const py::error_already_set& e) { std::cerr << "Python error: " << e.what() << std::endl; e.restore(); // 恢复Python错误状态 // 其他错误处理... } ``` 3. **线程安全**: - 所有公共方法使用`std::mutex`保护 - 引用计数使用`std::atomic`保证原子性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值