- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议50:利用模块实现单例模式
在GOF的23种设计模式中,单例是最常使用的模式,通过单例模式可以保证系统中一个类只有一个实例而且该实例易于被外界访问,从而方便对实例个数的控制并节约系统资源。每当大家想要实现一个名为XxxManger的类时,往往意味着这是一个单例。在游戏编程中尤是如此,比如一个名为World的单例管理着游戏中的所有资源,包括一个名为Sun的单例,它给这个世界带来了光亮。
单例如此常见,所以有不少现代编程语言将其加到了语言特性中,如scala和falcon语言都把object定义成关键词,并用其声明单例。如在scala中,一个单例如下:
object Singleton { def show = println("I am a singleton") }
object定义了一个名为Singleton的单例,它满足单例的3个需求:一是只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。对于第三点,在任何地方都可以通过调用Singleton.show()来验证。在scala中,单例没有显式的初始化操作,但并不是所有在语法层面支持单例模式的编程语言都如此,比如falcon就不一样。
object object_name [ from class1, class2 ... classN] property_1 = expression property_2 = expression ... property_N = expression [init block] function method_1( [parameter_list] ) [method_body] end ... function method_N( [parameter_list] ) [method_body] end end
上面是falcon语言的单例语法,[init block]能够让程序员手动控制单例的初始化代码。但是与scala和falcon相比,动态语言Python就没有那么方便了,主要原因是Python缺乏声明私有构造函数的语法元素,实例又带有类型信息。比如以下方法是不可行的:
class _Singleton(object): pass Singleton = _Singleton() del _Singleton # 试图删除 class 定义 another = Singleton.__class__() # 没用,绕过! print(type(another)) # 输出 <class '__main__._Singleton'>
可见虽然把Singleton的类定义删除了,但仍然有办法通过已有实例的__class__属性生成一个新的实例。于是许多Pythonista把目光聚集到真正创建实例的方法__new__上,并做起了文章。
class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super(Singleton, cls).__new__( cls, *args, **kwargs) return cls._instance if __name__ == '__main__': s1=Singleton() s2=Singleton() assert id(s1)==id(s2)
这个方法很好地解决了前面的问题,现在基本上可以保证“只能有一个实例”的要求了,但是在并发情况下可能会发生意。为了解决这个问题,引入一个带锁的版本。
class Singleton(object): objs = {} objs_locker = threading.Lock() def __new__(cls, *args, **kv): if cls in cls.objs: return cls.objs[cls] cls.objs_locker.acquire() try: if cls in cls.objs: ## double check locking return cls.objs[cls] cls.objs[cls] = object.__new__(cls) finally: cls.objs_locker.release()
利用经典的双检查锁机制,确保了在并发环境下Singleton的正确实现。但这个方案并不完美,至少还有以下两个问题:
如果Singleton的子类重载了__new__()方法,会覆盖或者干扰Singleton类中__new__()的执行,虽然这种情况出现的概率极小,但不可忽视。
如果子类有__init__()方法,那么每次实例化该Singleton的时候,__init__()都会被调用到,这显然是不应该的,__init__()只应该在创建实例的时候被调用一次。
这两个问题当然可以解决,比如通过文档告知其他程序员,子类化Singleton的时候,请务必记得调用父类的__new__()方法;而第二个问题也可以通过偷偷地替换掉__init__()方法来确保它只调用一次。但是,为了实现一个单例,做大量的、水面之下的工作让人感觉相当不Pythonic。这也引起了Python社区的反思,有人开始重新审视Python的语法元素,发现模块采用的其实是天然的单例的实现方式。
所有的变量都会绑定到模块。
模块只初始化一次。
import机制是线程安全的(保证了在并发状态下模块也只有一个实例)。
当我们想要实现一个游戏世界时,只需简单地创建World.py就可以了。
# World.py import Sun def run(): while True: Sun.rise() Sun.set()
然后在入口文件main.py里导入,并调用run()函数,看,是不是感觉这种方式最为Pythonic呢?
# main.py import World World.run()
注意
Alex Martelli认为单例模式要求“实例的唯一性”本身是有问题的,实际更值得关注的是实例的状态,只要所有的实例共享状态(可以狭义地理解为属性)、行为(可以狭义地理解为方法)一致就可以了。在这一思想的进一步指导下,他提出了Borg模式(在C#中又称为Monostate模式)。
class Borg: __shared_state = {} def __init__(self): self.__dict__ = self.__shared_state # and whatever else you want in your class -- that's all!
通过Borg模式,可以创建任意数量的实例,但因为它们共享状态,从而保证了行为一致。虽然Alex的这个Borg模式仅适用于古典类(classic clasess),Python 2.2版本以后的新式类(new-style classes)需要使用__getattr__和__setattr__方法来实现(代码略),但其可开阔眼界。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论