0. 引言
在python代码的写作中,我们有时需要全局化某一对象,即让这一对象有且只有一个,从而实现共同控制,共享存储空间等功能。
当然,一种简单粗暴的方法就是直接定义变量之后然后将其实例化然后再设置为全局变量,但是这种操作非常野蛮,且必须确保其执行顺序处于最前方。
另一种简单粗暴的方法是如果确定这一变量必然为某个类的操作对象时,我们可以将其定义为该类的类变量,利用类变量先天的唯一性实现全局的操作。
但是,同样的,这样的方式也不够优雅,更多的情况下,我们希望的是对该类的现状透明化操作,反正就是要用了我就实例化一下,但是实例化之后返回的永远是同一个类的实例,即单例的方法。
单例实现的根本思路事实上就是对类的实例化过程进行重载,当发现类已经被实例化过之后,就返回已经实例化得到的类的实例,否则就进行实例化然后返回实例化对象。
因此,要对单例的实现有一个更为系统的了解,我们首先来考察一下python类的实例化过程。
1. python类的实例化过程
我们首先来看一下python类从定义到实例化的完整过程:
- 类变量的实例化以及类方法的申明;
- 元类的
__new__
- 元类的
__init__
- 元类的
__call__
- 类本身的
__new__
- 类本身的
__init__
其中,前三步发生于类的申明过程中,后三步在每一次类的实例化过程中都会执行一次。
具体的,我们可以考察如下代码例子:
class MyMetaclass(type):
print("MyMetaclass")
def __new__(cls, name, bases, attrs):
print("MyMetaclass :: __new__")
return super().__new__(cls, name, bases, attrs)
def __init__(self, *args, **kwargs):
print("MyMetaclass :: __init__")
return super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
print("MyMetaclass :: __call__")
return super().__call__(*args, **kwargs)
class MyClass(list, metaclass=MyMetaclass):
print("MyClass")
def __new__(cls, *args, **kwargs):
print("MyClass :: __new__")
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print("MyClass :: __init__")
return super().__init__(*args, **kwargs)
定义完成后上述代码即会打印出如下结果:
MyMetaclass
MyClass
MyMetaclass :: __new__
MyMetaclass :: __init__
之后,我们对MyClass
类进行实例化,会打印出结果如下:
MyMetaclass :: __call__
MyClass :: __new__
MyClass :: __init__
不过坦率地说,对于类的申明过程以及实例化过程的更加深入的理解,还有元类这个概念存在的本身事实上我就不太了解了,毕竟不是科班出身,虽然写了不少python代码,但是对于python本身的理解还是不够的。
下面就是一些我目前不太理解的问题:
- python在类的申明过程中是通过什么机制进行类变量的实例化和类方法的申明的,这个过程叫做什么?
- 元类的定义是什么?为什么要有元类的存在,它能够实现什么特别的方法?
如果有朋友对这部分内容有足够的了解。欢迎进行补充讲解。
2. 单例的实现方法
如前所述,单例的实现方式事实上就是对类的实例化过程进行介入,重载其中某些过程,使得当类的实例以及存在时,直接返回已经实例化的类的实例,从而确保这一个类的实例对象永远为同一个。
而根据介入时间点的不同,单例的实现方式大致可以分为以下三种,由前至后分别为:
- 通过装饰器方式重载类的实例化方法
- 重载元类的
__call__
方法 - 重载类的
__new__
方法
1. 通过装饰器的方式实现
通过装饰器的方式进行单例的实现事实上就是完全重载类的实例化方法。
给出其代码实现如下:
def singleton(cls):
instances = {}
def getinstance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return getinstance
@singleton
class Counter(object):
def __init__(self, counter=0):
self.counter = counter
这种实现方式直接重载了Counter()
函数,当这个类已经被实例化过之后,后续在进行Counter()
函数的调用,装饰器会直接调用getinstance()
方法,返回现有的类的实例,此时后续从元类的__call__
方法到类本身的__new__
与__init__
方法都不会再执行。
给出例子如下:
a = Counter()
print(a.counter) # 0
b = Counter(2)
print(b.counter) # 0
b.counter += 1
print(b.counter) # 1
print(a.counter) # 1
2. 通过重载元类的__call__函数的方式实现
重载元类的__call__
函数是另一种实现单例的方式。
我们同样给出代码样例如下:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Counter(object, metaclass=Singleton):
def __init__(self, counter=0):
self.counter = counter
由于类本身的__new__()
方法与__init__()
方法都是在元类的__call__()
函数中进行实现的。因此,上述重载方式在二次实例化过程中也不会再执行类的__new__()
方法与__init__()
方法。
a = Counter()
print(a.counter) # 0
b = Counter(2)
print(b.counter) # 0
b.counter += 1
print(b.counter) # 1
print(a.counter) # 1
3. 通过重载类的__new__函数的方式实现
单例的第三类实现方式是通过重载类的__new__()
方法进行实现。
给出代码样例如下:
class Counter(object):
_instances = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__new__(cls, *args, **kwargs)
return cls._instances[cls]
def __init__(self, counter=0):
self.counter = counter
可以看到,其代码实现方式与上述重载metaclass的__call__()
函数方式几乎一模一样,但是其区别在于,重载__new__()
函数的情况下类在实例化过程中依然会走完完整地实例化流程。因此,后续他依然会去执行__init__()
函数操作,可能会带来使用上的隐患,需要注意。
给出一个例子如下:
a = Counter()
print(a.counter) # 0
b = Counter(2)
print(b.counter) # 2
b.counter += 1
print(b.counter) # 3
print(a.counter) # 3
4. 总结
综上,单例的实现本质上来说就是通过某种方式介入到类的实例化过程当中。当发现类已经进行了实例化之后,就直接返回现有类的实例。
根据介入时间点的不同,单例的实现方式大致可以分为以下三种,由前至后分别为:
- 通过装饰器方式重载类的实例化方法
- 重载元类的
__call__
方法 - 重载类的
__new__
方法
给出各个模式下的单例在二次实例化过程中会进行的操作如下:
method | metaclass.__call__ | class.__new__ | class.__init__ |
---|---|---|---|
装饰器 | X | X | X |
重载元类的__call__ 方法 | O | X | X |
重载__new__ 方法 | O | O | O |