[TOC]
参考:
http://www.cnblogs.com/yuanchenqi/articles/8323452.html#_label1
使用文件(模块导入)
使用 new(类的方式)
使用元类(metaclass)
使用装饰器(decorator)
在哪里用得到单例:
- 数据库连接池
单例模式:
文件导入方式实现
利用文件导入,那么被导入文件只加载一次的特性,实现单例模式。
#s1.py
print('s111111111111111111')
#s2.py
print('s22222222222222222222222')
#run.py
import s1
import s2
import s1
import s2
实现多次导入
import s1
import s2
import s1
import s2
# 如果要想实现导入多次加载
import importlib
importlib.reload(s1)
importlib.reload(s2)
导入类的实例,被导入的文件中的类只实例化一次,而不会因为在其它文件或本文件内多次导入而生成多个实例。在连接数据库时,这个很重要
db.py
class Foo(object):
def __init__(self):
self.conn = "连接数据库"
def get(self):
print(self.conn)
obj = Foo()
# import redis
# obj = redis.Redis(host="10.0.0.61",
# port=6379
# )
view.py
import t_db
print(t_db.obj)
run.py
import t_db
import t_view
print(t_db.obj)
[图片上传失败...(image-e27196-1521871654703)]
类方式
普通实现
# 1\. 初步实现
# class Foo(object):
#
# instance = None #静态变量,实例共享
#
# def __init__(self):
# # 因为是单例,没有传参的必要,因为都以第一个为准
# self.a = 1
# self.b = 2
#
# @classmethod
# def get_instance(cls,*args,**kwargs):
# if not getattr(Foo,'instance'): #这个类中是否有instance,没有则创建后返回,有则直接返回
# obj = cls(*args,**kwargs)
# cls.instance = obj
# return cls.instance
#
# obj1 = print(Foo.get_instance())
# obj2 = print(Foo.get_instance())
#
# # 返回
# # <__main__.Foo object at 0x00000000011AAF28>
# # <__main__.Foo object at 0x00000000011AAF28>
# 缺点:
# 1.可以实现单位,但线程不安全
# 2.改变了实例化的形式(不是Foo())
# 2 最终实现
import threading
class Foo(object):
lock = threading.Lock() # 实例化一个锁
instance = None #静态变量,实例共享
def __init__(self):
# 因为是单例,没有传参的必要,因为都以第一个为准
self.a = 1
self.b = 2
import time
time.sleep(2)
@classmethod
def get_instance(cls,*args,**kwargs):
if not getattr(Foo, 'instance'):
# 每一次线程实例化时,都要上锁再释放,试想如果实例化时不是seleep不是2秒,而是10秒或更多,线程进来创建实例,第二次程序中已以有一instance,其实没有必要上锁了,但还是会进行加锁处理,所以判断一下
with cls.lock: # 相当于上锁,使用完自己relese
if not getattr(Foo,'instance'): #这个类中是否有instance,没有则创建后返回,有则直接返回
obj = cls(*args,**kwargs)
cls.instance = obj
return cls.instance
return cls.instance
# 使用多线程执行实例动作,看是否有还是单例
def task():
obj = Foo.get_instance()
print(obj)
import threading
for i in range(5):
t = threading.Thread(target=task,)
t.start()
1. 当使用多线程进行实例化时,init里如果不加sleep,那么程序执行太快,第二个线程进入程序已经有值,所以显示实例id还是一样的
但init里如果加sleep,那么第二次进入程序,类中没有instance,所以会再创建返回,这样的话 就不是单例了,这就是所谓的线程安全
2. 为了解决线程安全,可以使用加锁解决,但是使程序变成了串行,并且每一次线程实例化时,都要上锁再释放,试想如果实例化时不是seleep不是2秒,而是10秒或更多,第一个线程进来创建实例,第二个线程再进入程序中已以有一instance,其实没有必要上锁了,但还是会进行加锁处理,所以判断一下
new方法实现
使用new方法实现的好处是不会改变类实例化的方式还是obj = Foo()
# 使用__new__ 实现
import threading
import time
class Foo(object):
instance = None
lock = threading.Lock()
def __init__(self):
self.a1 = 1
self.a2 = 2
time.sleep(2)
@classmethod #一定要是类方法
def __new__(cls, *args,**kwargs):
if not cls.instance:
with cls.lock:
if not cls.instance:
obj = super(Foo,cls).__new__(*args, **kwargs) # obj = Foo() 即实例化Foo
cls.instance = obj
return cls.instance
return cls.instance
def task():
obj = Foo()
print(obj)
import threading
for i in range(5):
t = threading.Thread(target=task,)
t.start()
metaclass形式实现
metaclass方式的实现原理
[图片上传失败...(image-1b1b5e-1521871654703)]
import threading
lock = threading.Lock()
class Singleton(type):
# print(123) # 在class创建Foo类时就会执行Mytype
def __call__(self, *args, **kwargs):
if not hasattr(self,'instance'):
with lock:
# print(self) #<class '__main__.Foo'> ,self 就是Foo
if not hasattr(self,'instance'):
obj = self.__new__(self, *args, **kwargs) # new实例化一个对象,即如果没instance,那么创建一个
obj.__init__( *args, **kwargs) # 对象.init 即构造这个实例
setattr(self,'instance',obj) # 在这个实例中设置一个静态变量instance
return getattr(self,'instance')
return getattr(self, 'instance')
class Foo(object,metaclass=Singleton):
def __init__(self):
self.name = 'xxx'
def task():
obj = Foo()
print(obj)
import threading
for i in range(5):
t = threading.Thread(target=task,)
t.start()
[图片上传失败...(image-2bdda4-1521871654702)]
1. obj1 = Foo() 实例化类
2. Foo(),加了括号,执行父类的__call__
方法
3. 在这个方法中判断如果没有instance
4. 那么创建这个实例
5. 使用实例的init方法构造这个实例
7. 把实例赋值给这个实例的instance
8. return
9. obj2 = Foo 第二个实例化类
10. 如果有instance,已以有了
11. 直接返回
使用metaclass形式的好处是,Mytype类不用变,如果给让哪个类变成单例,那么使他metaclass=Mytype就好了。