印象中,是在创建单例模式时知道可以用到元类(metaclass),但始终对其了解的不是很透彻,很多人也都说元类是Python中较难理解的概念之一,于是找来几本书,希望可以找到答案,本文以Python3为例。
本文参考:
《人人都懂设计模式》
《Python Cookbook》
《 流畅的Python》1044
先来简单介绍下:元类(metaclass)是一个类,你也可以理解为类的类,因为Python中的类是在运行时动态创建的,那么通过元类便可以控制类属性和类实例的创建过程。
来看看用元类实现的单例模式:
class Singleton(type):\"\"\"单例模式\"\"\"def __init__(cls, *args, **kwargs):cls.__instance = Nonesuper().__init__(*args, **kwargs)def __call__(cls, *args, **kwargs):if cls.__instance is None:cls.__instance = super().__call__(*args, **kwargs)return cls.__instanceclass Test(metaclass=Singleton):def __init__(self):passa = Test()b = Test()print(id(a))print(id(b))
具体的实现是:创建类时显式的指定类的metaclass,而自定义的metaclass继承type,并重新实现
__call__
方法。
于是,有了两个问题:
- 为什么自定义的metaclass继承type?
因为,在Python中,type是默认的metaclass(内建元类),Python允许我们自定义metaclass,自定义的metaclass必须继承自type,也就是:元类从type类继承了构建类的能力。
我们通常用type来获取对象所属的类,就像这样:
In [10]: a = 10In [11]: type(a)Out[11]: int
然而,type还是一个类,你可以通过type来新建一个类,看type的源码,通过
type(name, bases, dict)
便可以生成一个新的类:
In [44]: test_class = type(\'Test\', (), dict({\'name\': None}))In [45]: a = test_class()In [46]: a.name = \'Tony\'In [47]: a.nameOut[47]: \'Tony\'
默认情况下,Python中类都是type类的实例:
In [12]: class A:...: pass...:In [13]: A.__class__Out[13]: typeIn [14]: int.__class__Out[14]: type
当你使用class关键字时,Python在幕后做的事情,就是通过元类来实现的。
- 为什么重新定义
__call__
方法?
提出该问题是因为,与Python类创建相关的方法是:
__new__
:类方法,负责对象的创建,在定义类时需要返回一个实例,在我们通过类名进行实例化对象时自动调用。
__init__
:初始化函数,负责对new实例化的对象进行初始化,负责对象状态的更新和属性的设置,在每一次实例化对象之后调用。
而我们常用
__call__
方法只是为了声明这个类的对象是可调用的(callable)。
但是,在metaclass中
__call__
方法还负责对象的创建,这就是为什么要重新定义的原因了。
重定义了
__call__
方法之后,一个对象的创建过程大概如下图:
我们验证一下:
class TestMetaClass(type):def __init__(cls, what, bases=None, dict=None):print(\"metaclass init\")super().__init__(what, bases, dict)def __call__(cls, *args, **kwargs):print(\"metaclass call\")self = super(TestMetaClass, cls).__call__(*args, **kwargs)return selfclass TestClass(metaclass=TestMetaClass):def __init__(self, *args, **kwargs):print(\"class init\")super().__init__()def __new__(cls, *args, **kwargs):print(\"class new\")self = super().__new__(cls)return selfa = TestClass()
返回:
metaclass initmetaclass callclass newclass init
可以看到,
__call__
方法在类执行
__new__
和
__init__
之前执行,这样就可以解释:
在Singleton中的
__call__
方法对类属性
__instance
进行判断:
- 如果
__instance
为None,56c表明类还未进行实例化,那么给
__instance
赋值为元类的父类(type)的
__call__
方法。
- 如果
__instance
不为None,说明类已经进行过实例化,直接返回cls.__instance中的类实例。
便实现了单例模式。
除了重新定义
__call__
以外,元类可以通过实现
__init__
方法来定制实例,元类的
__init__
方法可以做到类装饰器能做到的任务事情,并且作用更大。
如果想要进一步定制类,可以在元类中实现
__new__
方法。
另,编写元类时,通常会把self参数改为cls,这样能更清楚的表明要构建的实例是类。
元类的调用
上述例子中,都是通过
metaclass=\'\'
来设置类的元类,还可以这样:
class TestClass():__metaclass__ = TestMetaClassdef __init__(self, *args, **kwargs):print(\"class init\")super().__init__()
在执行类定义时,解释器会先寻找这个类属性中的
__metaclass__
,如果此属性存在,就将这个属性赋值给此类作为它的元类,如果此属性没有定义的话,就会向上查找父类的
__metaclass__
,如果没有发现ad8任何的父类,并且解释器中也没有名字为
__metaclass__
的全局变量,这个类就是传统类,会使用type.ClassType作为此类的元类。
以上。