在python中使用单例模式非常的“容易”。
你在一个模块中初始化一个对象,然后去引用这个对象就行了。因为python中万物皆对象,哪怕是一个包(package),也可以当作一个对象。但是这种方式并不好,因为你的代码会看上去非常耦合,所以我从来不建议把包当成一个对象,你仅仅当成一个包就可以了。我再解释一下我的意思,就是你不应该直接从一个包中import一个对象,虽然你可以这么做。你应该import进来一个类,然后通过这个类,来创建对象,这才是单例模式正确的使用方式。而且对于IDE来说,那种从包中导入的对象,因为是动态的,所以你goto define也是找不到的。
而且如果你明白了我的意思,这些来来回回传递的对象,当你想要把一个函数封装出来单独写测试时,你会发现要把这些对象当做上下文当做传入的时候,简直头疼的要死。
下面将通过三个例子,来说明我的观点,第一个例子是我认为python日志的正确使用方式,第二个是tornado中是如何使用ioloop这个全局对象的,第三个例子是我认为的一个项目的配置文件应该使用的方式。
日志的使用方式
我现在接手的项目,这个web服务的日志就是我说的那样,他们在一个包中初始化了日志对象,比如这个叫main_logger, 那个叫biz_logger,然后到处引用这些对象。
实际上我自己的代码中,获取日志的方式简单有粗暴:
1 | logger = logging.getLogger(__name__) |
然后你在一个统一的地方为这个__name__配置handler就行了。而且实际上的工作量要比你想象的少很多,因为像”business.handler”,这样的name是可以继承”business”的日志配置的。你设计好包的格式,然后配置他们的日志打印方式,对于单测,只要以另一种方式初始化这些logger就行了,甚至可以不管,让他们使用root默认。
因为你的代码并没有“动态耦合”。
tornado中的ioloop
还记得你怎么写tornado的代码吗?
1 | tornado.ioloop.instance().start() |
我们可以去看tornado的ioloop源码
1 |
|
注意这里加了一个线程锁,相当于在类中维护了这个单例对象,你可以把这个例子用在你的单例对象中。
配置文件的使用方式
我的配置也是使用单例的方式,载入到服务中,然后instance去引用它。
当然我的配置类,可以有各种方式初始化其中的值,比如用json,用yaml,用conf文件,等等。。
需要注意的是,不要给你的单例对象写构造函数(init),因为即使是单例模式的对象,也会每次调用你的构造函数,防止你每次获取单个例都刷新了所有东西。而且按照java中的思路,单例模式本来就应该隐藏构造函数的。
python中我建议两种单例模式的实现,一种是:
1 | class Singleton(object): |
继承此类就可以了。
1 | def singleton(cls, *args, **kw): |
然后装饰到相应的类上就可以了。