Python面经【8】- Python设计模式专题-上卷
- 一、接口
- 二、单例模式
- (1) 方法一:使用模块
- (2) 方法二: 装饰器实现【手撕 + 理解】(单下划线 + 闭包 + 装饰器 + 类方法)
- (3) 方法三:基于__new__方法【new和init 】
设计模式是一套 被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
一、接口
定义:一种特殊的类,声明了若干方法,要求继承该接口的类必须实现这些方法,可以通过抽象基类(Abstract Base Class)来实现。
作用:限制继承接口的类的方法的名称及调用方式,隐藏了类的内部实现。
1. from abc import ABCMeta, abstractmethod2. class Payment(metaclass = ABCMeta):3. @abstractmethod4. def pay(self, money):5. pass6. 7. class Payment_first(Payment):8. def pay(self, money):9. print("给%s块钱"%money)
10.
11. pay = Payment_first()
12. pay_first = Payment_first()
13. pay_first.pay(3)
14. 结果:给3块钱
二、单例模式
单例模式是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。在系统中,某个类只能出现一个实例对象时,单例对象就能排上用场。
定义:保证一个类只能有一个实例,而客户可以从一个众所周知的访问点访问它时。
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)。
比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个AppConfig的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要配置文件的内容,也就是说,很多地方都需要创建AppConfig对象的实例,这就导致系统中存在多个AppConfig的实例对象,而这样会严重浪费内存,类似于AppConfig这样的类,我们希望在程序运行期间只存在一个实例对象。
(1) 方法一:使用模块
实现方法:将需要实现的单例功能放到一个.py文件中
实现原理:Python的模块就是天然的单例模式,因为模块在第一次导入时,会生成.pyc文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
1. # singleton_module.py
2. class SingletonClass:
3. def __init__(self):
4. pass
5.
6. singleton_instance = SingletonClass()
将上面的代码保存在文件singleton_module.py中,要使用时,直接在其他文件中导入此文件中的对象,这个对象既是单例模式的对象。
1. # main.py
2. from singleton_module import singleton_instance
3.
4. # 使用singleton_instance
(2) 方法二: 装饰器实现【手撕 + 理解】(单下划线 + 闭包 + 装饰器 + 类方法)
定义一个装饰器函数,将被装饰的类实例保存在闭包中,每次获取实例时返回同一个实例对象。
1. 1. def Singleton(cls):2. 2. _instance = {}3. 3. def _singleton(*args, **kargs):4. 4. if cls not in _instance:#判断该实例是否存在,存在就直接返回,不存在就创建 5. 5. _instance[cls] = cls(*args, **kargs)6. 6. return _instance[cls]7. 7. return _singleton8. 8. 9. 9. @Singleton
10. 10. class A(object):
11. 11. a = 1
12. 12. def __init__(self, x=0):
13. 13. self.x = x
14. 14.
15. 15. a1 = A(2)
16. 16. a2 = A(3)
17. 17. # 输出:{<class '__main__.A'>: <__main__.A object at 0x7fb9af751af0>}
18. ===输出结果===
19. <__main__.A object at 0x000001E9D1F099B0>
20. <__main__.A object at 0x000001E9D1F099B0>
- 输出结果分析:
- 输出显示两个实例的内存地址是相同的(0x000001E9D1F099B0),这证明 a1 和 a2 实际上是同一个实例的引用
- 这证实了单例模式的实现,确保了类 A 无论被实例化多少次,始终只有一个实例存在
(3) 方法三:基于__new__方法【new和init 】
我们都知道,当我们实例化一个对象时,是先执行了类的__new__方法(我们没写时,默认调用object.new),实例化对象;然后再执行类的__init__方法,对这个对象进行初始化,所以我们可以基于这个,实现单例模式。
个人最初常用的是重写__new__方法的方式,但是用重写类中的__new__方法,在多次创建时,尽管返回的都是同一个对象,但是每次执行创建对象语句时,内部的__init__方法都会被自动调用,而在某些应用场景,可能存在初始化方法只能运行只能允许运行一次的需求,这是这种单例的方式并不可取。
1. class Download(object):2. instance = None3. def __init__(self):4. print("__init__")5. def __new__(cls, *args, **kargs):6. if cls.instance is None:7. cls.instance = super().__new__(cls)8. return cls.instance9. object1 = Download()
10. object2 = Download()
11. print(object1)
12. print(object2)
运行结果,可以看到初始化方法多次执行了。
1. __init__
2. __init__
3. <__main__.Download object at 0x7f56beb4ea90>
4. <__main__.Download object at 0x7f56beb4ea90>
init方法通常用在初始化一个类实例的时候,但其实它不是实例化一个类的时候第一个被调用的方法。当使用Student(id, name)这样的表达式来实例化一个类时,最先被调用的方法其实是new方法。
new方法接受的参数虽然也是和init一样,但init是在类实例创建之后调用,而new方法正式创建这个类实例的方法。
New为对象分配空间,是内置的静态方法,new在内存中为对象分配了空间也返回了对象的引用,init获得了这个引用才初始化这个实例。
Eg:示例
一个非常简单的单例
1. class A:
2. instance = None
3. def __new__(cls, *args, **kwargs):
4. if cls.instance is None:
5. cls.instance = super().__new__(cls)
6. return cls.instance
因为new方法是一个静态方法(也就是在定义的时候就没有cls参数),所以在这里要传入一个cls参数,而且这里的new你改造过了,所以要返回爸爸的new方法。
按造这个方法改造的单例怎么new都是同一个实例,但init仍然会被执行多次,也就是创建了几个对象就调用几次初始化方法。所以还要对init再进行一些判断。
1. 1. class A:2. 2. instance = None3. 3. init_flag = False # 初始化标记4. 4. 5. 5. def __new__(cls, *args, **kwargs):6. 6. if cls.instance is None:7. 7. cls.instance = super().__new__(cls)8. 8. return cls.instance9. 9.
10. 10. def __init__(self):
11. 11. if A.init_flag:
12. 12. return
13. 13. print('执行了初始化方法')
14. 14. A.init_flag = True
15. 15.
16. 16. if __name__ == '__main__':
17. 17. a = A()
18. 18. b = A()
19. 19. print(a)
20. 20. print(b)
21. ===结果如下===
22. 执行了初始化方法
23. <__main__.A object at 0x000001E9D1F096D8>
24. <__main__.A object at 0x000001E9D1F096D8>
结果分析:
- 执行了初始化
这一行表明 init 方法被执行了一次。由于在 init 方法中有一个条件检查 (if A.init_flag:),它确保初始化代码块只执行一次。在第一次创建实例 a 时,init_flag 是 False,所以 init 方法的内部代码被执行,并且 init_flag 被设置为 True。当创建第二个实例 b 时,由于 init_flag 已经是 True,init 方法内的代码不会再次执行。 - <main.A object at 0x000001E9D1F096D8>
这两行输出分别是打印的 a 和 b 实例。它们具有相同的内存地址 (0x000001E9D1F096D8),这证明了 a 和 b 是同一个对象的两个引用。这符合单例模式的预期效果,即不管创建多少次该类的实例,始终只有一个实例存在。