跳至主要內容
单例模式

单例模式

L.H.X...大约 8 分钟python设计模式python

单例模式

单例模式

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

单例模式结构

单例模式的Python实现

1. 普通单例模式

_ new _方法可以在实例化类的时候通过hasattr方法返回同一个实例

:sad:

class ExerciseSinglePattern:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, a, b):
        print('__init__')
        self.a = a
        self.b = b
if __name__ == '__main__':
    sp = ExerciseSinglePattern('a', 'b')
    sp2 = ExerciseSinglePattern('c', 'd')
    print(sp)
    print(sp2)
    # <__main__.ExerciseSinglePattern object at 0x00000243AF6E4F70>
	# <__main__.ExerciseSinglePattern object at 0x00000243AF6E4F70>

2. 使用装饰器实现单例模式

2.1 装饰器(Decorators)

装饰器是Python中很重要的内容,属于Python的进阶知识;装饰器的本质可以理解为一个高阶函数,因为它返回的是一个函数对象。装饰器可以在不修改被装饰代码的情况下对代码增加额外的功能,这种动态增加功能的方式,我们称之为“装饰器”.

1️⃣普通装饰器:

一般来说,装饰器的基本代码格式为两层嵌套函数,如下所示:


# 装饰器
def decorator(func:被装饰函数):
    def wrapper(*args, **kwargs):# *args和**kwargs主要是被装饰函数所需的参数
        # 函数执行前的操作
        print("Start:")
        func(*args, **kwargs)
        # 函数执行后的操作
        print("End")
    return wrapper

以上就是一个简单的装饰器,如果要使用装饰器,则使用Python中的语法糖@

# 基本函数
def test(text):
    print(text)
    
# 使用装饰器后的函数
@decorator
def test(text):
    print(text)
if __name__ == '__main__':
    test('装饰器')
  	"""
  	Start:
	装饰器
	End
  	"""

2️⃣带参数的装饰器:

如果装饰器本身需要传递参数,则需要构建一个可以返回装饰器的高阶函数。

# 带有参数的装饰器
def logger_decorator(log_info):
    # 装饰器
    def decorator(func):
        # 内层函数
        def wrapper(*args, **kwargs):
            print(f"{func.__name__}:{log_info}")
            func(*args, **kwargs)
        return wrapper
    return decorator

# 带有参数的装饰器使用方法如下
@logger_decorator("Start...", "...End")
def test2():
    print("正在插入数据")
    time.sleep(5)
    
if __name__ == '__main__':
    # test('装饰器')
    test2()
    
image-20231230132509562
image-20231230132509562

在我们执行被装饰器装饰的函数时,我们使用函数的方式和普通函数无异,也就是说如果我们想要对test2()函数增加打印执行时间的功能,我们只需要添加一个装饰器,由于函数调用的方式并没有发生变化,所以就无需更改任何原代码就可完成对代码额外功能的增加

我们来剖析一下上面调用test()函数时,程序的执行流程

#我们在调用test2()函数时,程序的执行顺序如下
logger_decorator(arg1,arg2)(test2)(*args,**kwargs)
# 1. 首先,执行的是logger_decorator
logger_decorator(arg1,arg2)
# 2. 由上面的函数可知,logger_decorator函数返回的是decorator
decorator = logger_decorator(arg1,arg2)
# 3. decorator接受一个参数func,这个参数就是被装饰函数的引用,接下来
wrapper = decorator(test2)
# 4. 其实,在执行test2时,实际上执行的是wrapper
wrapper(*args, **kwargs)
# 以上就是通过装饰器装饰后的函数的整体流程

3️⃣:通过签名调用的函数

上面我们说过,使用装饰器装饰过的函数,使用方法和被装饰前无异,但是有另外一种情况:我们依赖某个函数的签名,即func.name,此时我们如果装饰func的话,func.__name__就会发生改变,因为实际上func已经变成了wrapper,所以依赖函数签名的程序就会报错。针对这种情况,我们使用python内置的方法即可解决:functools.wraps函数

如果我们不使用functools.wraps:

def wraps_decorator(func):
    def wrapper(*args, **kwargs):
        print("Do Something......")
        return func(*args, **kwargs)
    return wrapper

@wraps_decorator
def wraps_test(text):
    print(text)
    time.sleep(2)
if __name__ == '__main__':
    # 我们如果不使用functools.wraps
    func_name = wraps_test.__name__
    print(f"wraps_test函数名称:{func_name}")
    
    """
    wraps_test函数名称:wrapper
    """
    
    # 我们可以看到,wraps_test签名变成了wrapper

使用functools.wraps:

# 使用方式很简单,通过装饰器的写法装饰内层函数即可
import functools


def wraps_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("Do Something......")
        return func(*args, **kwargs)
    return wrapper

@wraps_decorator
def wraps_test(text):
    print(text)
    time.sleep(2)
if __name__ == '__main__':

    # 使用functools.wraps
    func_name = wraps_test.__name__
    print(f"wraps_test函数名称:{func_name}")
    """
    wraps_test函数名称:wraps_test
    """

2.2 使用装饰器实现单例模式

上面讲解完了装饰器,我们可以了解到装饰器可以拓展函数的功能,接下来我们就使用一点小技巧通过装饰器对类进行装饰,从而实现单例的需求

# 首先,我们要实现单例模式,和普通单例模式类似,
# 我们要知道某个类A是否已经创建实例,如果已经创建实例则返回,所以我们可以通过字典来实现
# 装饰器单例模式
def singleton_decorator(cls):
    # 创建一个存储类实例的字段
    _instances = {}
    def wrapper(*args, **kwargs):
        if cls not in _instances:
            _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]
    return wrapper


@singleton_decorator
class DecoratorSingleton:
    def __init__(self, name=None):
        self.name = name

if __name__ == '__main__':
    dt_singleton = DecoratorSingleton("Decorator Singleton")
    print(dt_singleton)
    dt_singleton2 = DecoratorSingleton("Decorator Singleton2")
    print(dt_singleton2)
    """
    <__main__.DecoratorSingleton object at 0x000002AFB008FBB0>
	<__main__.DecoratorSingleton object at 0x000002AFB008FBB0>
    """

3. 单态模式(单例模式的一种)

单例模式是指,一个类只有一个对象,然而,单态模式更关心的是状态和行为,即所有对象共享相同的状态,所以基于这类思想设计的类也叫做Monostate(单态)模式

我们知道,在Python独享中,__dict___存储了一个对象所有的变量,所以如果我们在实例化对象时所有对象的__dict__指向的是同一个引用,那么当某个实例a更改了属性,那么其他实例也能同步,达到所有对象共享相同状态的目的。

"""
单态模式的实现
"""
class Borg:
    # 类属性
    __shared_state = {'name': None}
    def __init__(self):
        self.__dict__ = self.__shared_state


if __name__ == '__main__':
    a = Borg()
    b = Borg()
    # 此时,如果对a设置属性age,则b也会被赋予属性age
    a.age = 20

    print(a.age)
    print(b.age)

单态模式还可以通过__new__方法实现:

class NewBorg:
    __shared_state = {}
    # TODO 1 重新new方法
    def __new__(cls, *args, **kwargs):
        # TODO 2 生成shili
        borg_obj = super().__new__(cls, *args, **kwargs)
        # TODO 3 将实例的__dict__指向类属性__shared_state
        borg_obj.__dict__ = cls.__shared_state
        return borg_obj
    
    def __init__(self):
        self.name = 'Borg1'
        
        
if __name__ == '__main__':
    a = Borg()
    b = Borg()
    # 此时,如果对a设置属性age,则b也会被赋予属性age
    a.age = 20

    print(a.age)
    print(b.age)


    new_borg = NewBorg()
    new_borg_2 = NewBorg()
    print(f"更新name属性前:{new_borg.name}") # 更新name属性前:Borg1
    # 更改name属性
    new_borg_2.name = "Borg2"
    print(f"更新name属性后:{new_borg.name}") # 更新name属性后:Borg2

4. 单例和元类

4.1 元类

我们先来简单的了解一下元类,元类的意思是一个类的类,这意味着类是元类的实例。使用元类,我们有机会从预定义的Python类中创建自己想要的类型的类。下面我们来通过实例来体会一下什么是元类。

a = 10
print(type(a))  # <class 'int'>
print(type(int))  # <class 'type'>

类的定义有它的元类决定,当我们创建一个类A时,Python则是相当于实例化了type类: A=type(name,bases,dict)。

  • name : 这是类的名称
  • bases: 这是基类
  • dict:这是类的属性变量

4.2 元类实现单例模式

接下来我们看一下简单的元类示例:

class MyInt(type):
    # __call__方法在类以函数方式调用的时候被触发
    def __call__(cls, *args, **kwargs):
        print("*** 自定义元类 Int类型 ***", args)
        print("在这里可以自定义你自己的类的行为.....")
        return type.__call__(cls, *args, **kwargs)

# 使用上面自定义的元类来创建int类型
class int(metaclass=MyInt):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
if __name__ == '__main__':
    my_int = int(10, 20)
# 以下是上面代码的输出结果
*** 自定义元类 Int类型 *** (10, 20)
在这里可以自定义你自己的类的行为.....

将需要创建对象时,Python将调用__call__方法,所以上面在创建my_int对象时MyInt就会调用我们自定义的__call__方法。

由于元类对对象的实例化有更多的控制权,所以它可以用来创建单例。

下面我们基于元类来实现单例:

# 使用元类创建单例
class MetaSingleton(type):
    _instances = {}
    # 覆盖type的__call__方法

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class Logger(metaclass=MetaSingleton):
    def __init__(self, level):
        self.level = level


if __name__ == '__main__':
    my_int = int(10, 20)

    logger_1 = Logger('info')
    logger_2 = Logger('error')
    print(logger_1)
    print(logger_2)
    

以下是上面代码的输出:

>>>>>>
<__main__.Logger object at 0x00000204A6EFFCA0>
<__main__.Logger object at 0x00000204A6EFFCA0>

在上面的代码中,我们通过继承type类,然后重写__call__的方法,每次创建实例的时候都去_instances中去寻找已经创建的实例,然后返回。

5. 单例模式的缺点

单例具有全局访问权限,因此会存在以下问题。

全局变量可能在某处代码被修改,但是开发人员并未注意到,如果有其它地方也使用,可能会发生异常。

由于单例只能创建一次,所以会有多个引用指向同一个对象。

所有依赖全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能无意中影响另一个类。

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8