首页 > 其他分享 >单例模式

单例模式

时间:2024-05-09 19:22:29浏览次数:22  
标签:__ singleton 模式 实例 单例 print x00 pickle

设计模式之单例模式

【一】什么是单例模式

  • 单例设计模式(Singleton Design Pattern): 一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
  • 当一个类的功能比较单一,只需要一个实例对象就可以完成需求时,就可以使用单例模式来节省内存资源。

【通常】单例模式创建的对象是进程唯一的, 单例类中对象的唯一性的作用范围是进程内的,在进程间是不唯一的。

【二】如何实现一个单例

  • 要实现一个单例,我们需要知道要重点关注的点是哪些?

    • 考虑对象创建时的线程安全问题
    • 考虑是否支持延迟加载
    • 考虑获取实例的性能是否高(是否加锁)
  • 在python中,我们可以使用多种方法来实现单例模式

    • 使用模块
    • 使用装饰器
    • 使用类(方法)
    • 基于__new__方法实现
    • 基于元类metaclass实现

【三】不支持延迟加载

在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载。

  • 一种常见的方式是利用python模块化实现单例。
# test.py

import threading

class IdGenerator:
    def __init__(self):
        self._lock = threading.Lock()
        self.id = 0

    def get_id(self):
        with self._lock:
            self.id += 1
            return self.id

# 创建单例实例
id_generator_instance = IdGenerator()
  • 在另一个文件(例如 test_user.py)中导入模块并使用单例模式
# test_user.py


import test
if __name__ == '__main__':
    singleton1 = test.id_generator_instance
    singleton2 = test.id_generator_instance

    print(singleton1 is singleton2)  # 输出 True,表示是同一个实例
  • Python中可以使用模块的方式非常简单地实现单例模式,因为Python模块在程序中只会被导入一次,所以它天生就是单例的。

【四】支持延迟加载

    • 使用装饰器
    • 使用类(方法)
    • 基于__new__方法实现
    • 基于元类metaclass实现

【五】类属性

【1】类产生对象

  • 当我们有一个类,类里面有很多方法,我们想使用这些方法的时候,我们的第一想法是实例化得到一个对象,再用对象去调用相应的方法
  • 但是不同的人,都想用这一个方法的时候,就需要每一个人都去实例化对象,用对象调用方法,但是我们不去使用对象中的其他属性,只用到其中一种属性,于是这对于系统来说是一种开销
# 创建一个普通的类
class MysqlControl(object):
    pass


# 实例化类得到对象 --- 类只要加 () 实例化就会产生一个全新的对象
obj_one = MysqlControl()
obj_two = MysqlControl()

# 查看对象  --- 发现虽然是同一个类实例化得到的对象,但是对象的地址不一样
print(obj_one)
# <__main__.MysqlControl object at 0x0000023E6330C880>
print(obj_two)
# <__main__.MysqlControl object at 0x0000023E6330CBE0>
print(obj_one is obj_two)
# False

【2】类属性包装成方法

  • 使用类属性保存实例,通过类方法获取实例。
  • 在第一次调用get_instance方法时创建实例,并在后续调用中直接返回该实例。
class Quantong(object):
    _instance = None

    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = Quantong('741.0.0.1', 9527)
        return cls._instance


obj_one = Quantong.get_instance()
obj_two = Quantong.get_instance()

print(obj_one)
# <__main__.Quantong object at 0x000001D4DCFC9D80>
print(obj_two)
# <__main__.Quantong object at 0x000001D4DCFC9D80>
print(obj_one is obj_two)
# True
# 输出 True,表示是同一个实例

【六】装饰器

  • 使用装饰器将原来的类包装成一个新的类,通过闭包和字典保存实例。
  • 在每次实例化时,先检查字典中是否已经存在该类的实例,如果不存在才创建实例并返回。
def singleton(cls):
    instances = {}

    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return wrapper


@singleton
class Singleton:
    pass


singleton_one = Singleton()
singleton_two = Singleton()

print(singleton_one)
# <__main__.Singleton object at 0x00000213FD69A800>
print(singleton_two)
# <__main__.Singleton object at 0x00000213FD69A800>
print(singleton_one is singleton_two)
# True

# 输出 True,表示是同一个实例

# # 使用 __call__ 方法获取单例
singleton_three = Singleton()
print(singleton_one is singleton_three)
# True

【七】元类(metaclass)

  • 定义一个元类,在元类的__call__方法中判断实例是否已存在,如果不存在则调用父类的__call__方法来创建并返回实例。
class SingletonType(type):
    def __init__(cls, name, bases, attrs):
        super(SingletonType, cls).__init__(name, bases, attrs)
        cls.instance = None

    def __call__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls.instance


class Singleton(metaclass=SingletonType):
    pass


singleton_one = Singleton()
singleton_two = Singleton()

print(singleton_one)
# <__main__.Singleton object at 0x0000018BA9217E80>
print(singleton_two)
# <__main__.Singleton object at 0x0000018BA9217E80>
print(singleton_one is singleton_two)
# True

# 输出 True,表示是同一个实例

# # 使用 __call__ 方法获取单例
singleton_three = Singleton()
print(singleton_one is singleton_three)
# True

【八】基于__new__方法

【1】推理思路

  • 在Python中,对象的实例化过程通常遵循以下步骤:

    • 首先,执行类的__new__方法,如果未定义此方法,将默认调用父类的__new__方法来创建一个实例化对象。
    • 接着,再执行__init__方法来对这个新创建的对象进行初始化。
  • 我们可以充分利用这个实例化过程来实现单例模式。

    • 具体做法是在类的__new__方法中判断是否已经存在实例,如果存在,则直接返回现有的实例,否则创建一个新的实例。
    • 这样就能够确保只有一个实例存在,从而实现了单例模式的效果。

【2】实现

  • 重写__new__方法,在实例化对象时判断类中是否已有实例,如果没有则调用父类的__new__方法来创建并返回。

优势:

  • 这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls)
        return cls._instance


singleton_one = Singleton()
singleton_two = Singleton()

print(singleton_one)
# <__main__.Singleton object at 0x00000209D2D3CBE0>
print(singleton_two)
# <__main__.Singleton object at 0x00000209D2D3CBE0>
print(singleton_one is singleton_two)
# True

# 输出 True,表示是同一个实例

# # 使用 __call__ 方法获取单例
singleton_three = Singleton()
print(singleton_one is singleton_three)
# True

【九】基于模块

  • 将实例化操作放在模块级别,通过导入该模块来获取实例。
  • 由于Python模块在运行时只会被导入一次,因此保证了实例的单一性。
# singleton.py
class Singleton:
    pass

singleton_instance = Singleton()

【十】pickle模块介绍

【1】什么是pickle模块

  • pickle 模块是 Python 内置的一个序列化和反序列化的模块,它可以将 Python 对象转换为字节流,也可以将字节流转换回 Python 对象。
  • 这些操作通常被称为序列化和反序列化。

【2】序列化和反序列化

(1)序列化(dumps)

  • 序列化是指将 Python 对象转换为可以存储在文件、数据库或者网络传输的数据格式的过程。
  • 序列化后得到的结果通常是字节流形式的数据,这种数据可以方便地进行存储和传输。
import pickle

dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)
# b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x02k1\x94\x8c\x02v1\x94\x8c\x02k2\x94\x8c\x02v2\x94\x8c\x02k3\x94\x8c\x02v3\x94u.'
print(type(str_dic))
# <class 'bytes'>

(2)反序列化(loads)

  • 反序列化则是指将字节流形式的数据恢复为原来的 Python 对象的过程。
  • 在反序列化过程中,Python 解释器会按照一定的规则解析字节流,并根据字节流的内容创建出与之对应的 Python 对象。
import pickle

dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)
# b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x02k1\x94\x8c\x02v1\x94\x8c\x02k2\x94\x8c\x02v2\x94\x8c\x02k3\x94\x8c\x02v3\x94u.'
print(type(str_dic))
# <class 'bytes'>

dic2 = pickle.loads(str_dic)
print(dic2) 
# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
print(type(dic2))
# <class 'dict'>

【3】常用的方法

(1)Python对象和pickle对象互转

  • pickle 模块提供了一系列的方法和类来实现序列化和反序列化操作。
  • 其中最常用的方法是 pickle.dumps() 和 pickle.loads()。
  • pickle.dumps() 方法接受一个 Python 对象作为参数,返回一个字节流,表示该对象的序列化结果。
  • pickle.loads() 方法接受一个字节流作为参数,返回一个 Python 对象,表示该字节流的反序列化结果。
import pickle

# 【一】将Python中的字典类型序列化成pickle的字节流

# 【1】字典类型
user_dict = pickle.dumps({'name': 'dream'})
print(user_dict, type(user_dict))
# b'\x80\x04\x95\x13\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x04name\x94\x8c\x05dream\x94s.' <class 'bytes'>

# 【2】字符串类型
user_str = pickle.dumps('hello world')
print(user_str, type(user_str))
# b'\x80\x04\x95\x0f\x00\x00\x00\x00\x00\x00\x00\x8c\x0bhello world\x94.' <class 'bytes'>

# 【3】整数类型
user_int = pickle.dumps(123)
print(user_int, type(user_int))
# b'\x80\x04K{.' <class 'bytes'>

# 【4】浮点数类型
user_float = pickle.dumps(3.14)
print(user_float, type(user_float))
# b'\x80\x04\x95\n\x00\x00\x00\x00\x00\x00\x00G@\t\x1e\xb8Q\xeb\x85\x1f.' <class 'bytes'>

# 【5】布尔类型
user_bool = pickle.dumps(True)
print(user_bool, type(user_bool))
# b'\x80\x04\x88.' <class 'bytes'>

# 【6】元组类型
user_tuple = pickle.dumps((1, 2, 3))
print(user_tuple, type(user_tuple))
# b'\x80\x04\x95\t\x00\x00\x00\x00\x00\x00\x00K\x01K\x02K\x03\x87\x94.' <class 'bytes'>

# 【7】列表类型
user_list = pickle.dumps([1, 2, 3])
print(user_list, type(user_list))
# b'\x80\x04\x95\x0b\x00\x00\x00\x00\x00\x00\x00]\x94(K\x01K\x02K\x03e.' <class 'bytes'>

# 【8】集合类型
user_set = pickle.dumps({1, 2, 3})
print(user_set, type(user_set))
# b'\x80\x04\x95\x0b\x00\x00\x00\x00\x00\x00\x00\x8f\x94(K\x01K\x02K\x03\x90.' <class 'bytes'>

(2)Python对象和pickle对象读写

import pickle


# 【二】数据的读写
def save_data(path, mode='wb', data=None):
    with open(path, mode) as fp:
        pickle.dump(data, fp)
    print(f'当前已保存!保存路径为:{path}!')


def load_data(path, mode='rb'):
    with open(path, mode) as fp:
        data = pickle.load(fp)
        return data


# 【1】序列化写入 Python独有的类
class Student(object):
    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade


student = Student('张三', 18, '高三')
# 将实例化好的对象写入到指定文件
# save_data(path='Student', data=student)
# 将保存好的数据读出来,可以直接使用
student_data = load_data(path='Student')
print(student_data)  # <__main__.Student object at 0x109277eb0>
print(student_data.name)  # 张三
print(student_data.age)  # 18

【4】pickle的弊端

  • 需要注意的是,pickle 模块并不安全,因为它可以序列化任何 Python 对象,包括内置类型、自定义类的对象等。
  • 这使得 pickle 模块有可能成为攻击者攻击的目标。
  • 例如,攻击者可以通过构造恶意的 pickle 数据包,将其发送给受害者,导致受害者执行任意代码。
  • 因此,如果可能的话,应该尽量避免使用 pickle 模块,或者至少要确保输入的数据来自可信的来源。

标签:__,singleton,模式,实例,单例,print,x00,pickle
From: https://www.cnblogs.com/zyb123/p/18182947

相关文章

  • 设计模式详解
    本文结构图:除了类本身,设计模式更强调多个类/对象之间的关系和交互过程——比接口/类复用的粒度更大创建型模式(Creationalpatterns)工厂方法模式(FactoryMethodpattern)工厂方法也被称作虚拟构造器(VirtualConstructor)即定义一个用于创建对象的接口,让子类类决定实例化......
  • 设计模式03----构造者模式
    构造者模式:是一种创建型设计模式,是将一个对象拆分成多个部件分别进行构造然后组合成为一个整体的设计模式产品(Product):被构建的复杂对象,通常包含多个组成部件,例如一个需要配置的汽车对象。抽象建造者(Builder):一个接口,定义了构建产品各个部件的方法。具体建造者(ConcreteBuilde......
  • Java学设计模式之桥接模式
    一、桥接模式概念1.1什么是桥接模式桥接模式是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立地变化。桥接模式通过将抽象部分和实现部分分离来解决多维度变化的问题,从而提高了系统的灵活性和可扩展性。结构桥接模式通常由以下几个部分组成:Abstraction(抽象类......
  • VMWare Workstation安装CentOS7使用桥接模式无法ping通问题
    无线网络下VMWare+CentOS7使用桥接模式无法联通网络问题_vmwarecentos桥接模式连不上网-CSDN博客编辑->虚拟网络编辑器VMnet0自动模式不行,要选择对应的网卡;可在网络连接里等方式查看网卡名称;不是管理员身份运行VMWareWorkstation默认不显示VMnet0,右下角有个啥按钮,点击一下......
  • 设计模式的定义
    创建型模式简单工厂模式提供一个工厂类,根据传入参数来决定创建具体的产品类的实例。抽象工厂模式提供一个创建一系列或相关依赖对象的接口,而无需指定它们具体的类;用于解决多个类型产品的创建问题。建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建......
  • Java学设计模式之装饰器模式
    一、模式概念1.1什么是模式装饰模式是一种结构型设计模式,它允许向现有对象动态添加新功能,同时又不改变其结构。装饰模式通过将对象放置在包装器类中,然后在运行时动态地向对象添加新的行为或责任,从而实现这一目的。结构装饰模式通常由以下几个部分组成:Component(组件):定义一......
  • Java学设计模式之适配器模式
    一、适配器模式概念1.1什么是适配器模式适配器模式是一种结构型设计模式,它提供了一种将不兼容的接口转化为兼容的接口的方式,从而使原本无法一起工作的类可以协同工作。适配器模式可以分为两种:对象适配器和类适配器。1.2对象适配器对象适配器通过组合的方式,将不兼容的接口适......
  • Java学设计模式之原型模式
    一、原型模式概念原型模式是一种创建型设计模式,其核心思想是通过复制现有对象来创建新对象,而不是通过实例化类来创建。这种方式可以提高创建对象的效率,特别是当对象的创建过程比较昂贵或复杂时。在原型模式中,原型对象是一个已经存在的对象,它作为新对象的模板。新对象通过复制原......
  • Java学设计模式之建造者模式
    一、建造者模式概念1.1什么是建造者模式建造者模式是一种创建型设计模式,用于将一个复杂对象的构建过程与其表示分离,以便同样的构建过程可以创建不同的表示。它允许客户端通过相同的构建过程来构建不同的产品。建造者模式通常涉及以下几个角色:产品(Product):表示被构建的复杂对......
  • 懒汉模式和饿汉模式
    懒汉模式(LazyInitialization)和饿汉模式(EagerInitialization)是两种常见的单例模式实现方式,它们的区别主要在于对象的初始化时机。1.**懒汉模式**:-懒汉模式是指在第一次使用对象时才进行初始化。-在懒汉模式中,单例对象在第一次被使用时创建,因此也被称为延迟加载。-......