Python - 面向对象编程 - __new__() 和单例模式
单例模式
这是一种设计模式
- 设计模式是前任工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案
- 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性
单例设计模式
- 目的:让某一个类创建的实例对象,在整个应用程序中只有唯一的一个实例对象而且该对象易于外界访问,从而方便对实例个数的控制并节约系统资源
- 每一次执行 类名() 返回的对象,内存地址是相同的
单例设计模式的应用场景
- 音乐播放器对象
- 回收站对象
- 打印机对象
- .....
为什么要单例模式?
- 提问:如何保证一个类只有一个实例并且这个实例易于被访问呢?
- 不使用单例模式:定义一个全局变量可以确保对象随时都可以被访问,但不能防止实例化多个对象
- 单例模式的出现:类自己负责只能创建一个实例对象,可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法
__new__ 方法
使用 类名() 创建对象时,Python 的解释器首先会调用 __new__ 方法为对象分配内存空间
class PoloBlog: def __new__(cls, *args, **kwargs): print("分配内存地址啦") def __init__(self): print("初始化对象...") blog = PoloBlog() print(blog) # 输出结果 分配内存地址啦 None
哎,为什么打印对象是 None,而且没有调用到 __init__ 方法呢??下面讲解!
内置的静态方法
__new__ 是一个由 object 基类提供的内置的静态方法
__new__ 主要作用
- 在内存中为实例对象分配空间
- 返回对象的引用给 Python 解释器
Python 的解释器获得对象的引用后,将对象的引用作为第一个参数,传递给 __init__ 方法
重写 __new__ 方法
- 重写的代码是固定的
- 重写 __new__ 方法一定要在最后 return super().__new__(cls)
- 如果不 return(像上面代码栗子一样),Python 的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法(__init__)
- 重点:__new__ 是一个静态方法,在调用时需要主动传递 cls 参数
class PoloBlog: def __new__(cls, *args, **kwargs): # 1、自动调用 __new__ print("分配内存地址啦") # 2、为对象分配空间得到的引用赋值给 instance instance = super().__new__(cls) print(id(instance)) # 3、返回对象引用给 Python 解释器 return instance def __init__(self): print("初始化对象...") print(id(self)) blog = PoloBlog() # 输出结果 分配内存地址啦 4363809888 初始化对象... 4363809888
可以看到打印的两个内存地址是同一个哦:证明 __new__ 分配的对象引用的确传给了 __init__ 方法的 self 参数
__new__ 实现单例模式
class PoloBlog: def __new__(cls, *args, **kwargs): print("分配内存地址啦") instance = super().__new__(cls) return instance def __init__(self): print("初始化对象...") blog = PoloBlog() blog1 = PoloBlog() print(id(blog)) print(id(blog1)) # 输出结果 4449363040 4449361984
很明显,两个对象各有自己的内存地址;单纯的重写 __new__ 方法并不能实现单例模式
__new__ 实现单例模式的逻辑
单例:在整个应用程序中只有唯一的一个实例对象
- 定义一个类属性,来保存单例对象的引用
- 重写 __new__ 方法
- 如果类属性 is None,则调用父类方法分配内存空间,并赋值给类属性
- 如果类属性已有对象引用,则直接返回
单例模式的代码实现
# 单例模式 class PoloBlog: instance = None def __new__(cls, *args, **kwargs): # 1、判断类属性是否为 None if cls.instance is None: # 2、为空,调用父类方法,给对象分配内存空间,并赋值给类属性 cls.instance = super().__new__(cls) # 3、如果不为空,则直接返回类属性保存的对象引用 return cls.instance def __init__(self): pass blog = PoloBlog() blog1 = PoloBlog() blog2 = PoloBlog() print(id(blog), id(blog1), id(blog2)) # 输出结果 4336982096 4336982096 4336982096
可以看到创建的三个实例对象其实都是同一个,这就是单例模式!
初始化工作仅执行一次
在每次使用类名()创建对象时,Python 的解释器都会自动调用两个方法
- __new__ 分配空间
- __init__ 对象初始化
上面所说的单例模式,是针对 __new__ 方法进行重写的,创建多个实例对象都会得到同一个实例对象
但是:初始化方法还是会被多次调用
class PoloBlog: instance = None def __new__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super().__new__(cls) return cls.instance def __init__(self): print("yep") blog = PoloBlog() blog1 = PoloBlog() blog2 = PoloBlog() # 输出结果 yep yep yep
假设想让初始化动作只执行一次呢?
其也很简单,和单例模式的解决思路差不多
- 定义一个类属性标记是否执行过初始化动作,初始值为 False
- 在 __init__ 方法中,判断类属性,如果 False,则执行初始化动作,然后设置为 True
- 如果 True 则直接跳过不执行
# 单例模式 class PoloBlog: instance = None init_flag = None def __new__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super().__new__(cls) return cls.instance def __init__(self): # 1、判断是否为 True,因为是实例方法,所以调用类属性要通过类对象 if PoloBlog.init_flag: # 2、如果 True,直接跳过不执行后续初始化动作 return # 3、如果 False,则执行 print("初始化动作") # 4、修改 init_flag PoloBlog.init_flag = True blog = PoloBlog() blog1 = PoloBlog() blog2 = PoloBlog() # 输出结果 初始化动作