什么是元类
定义
python中的一切都是对象,类本身也是对象,元类则是创建类对象的类
python对象的创建过程 及 元类常使用方法的简介
加载模块时,
python解释器
自动创建模块中的类对象,全局函数对象 新建实例时,用户
主动调用类对象来创建实例 元类常用方法:__prepare__
,__new__
,__init__
,__call__
"""
元类常用方法,以及类对象创建和实例对象创建的时间点
"""
class MyMeta(type):
def __new__(mcs, name, bases, cls_dict):
""" 元类创建类对象方法
:param name: 类对象的类名
:param bases: 类对象的父类
:param cls_dict: 类对象的命名空间
:return:
"""
print('2. call MyMeta method: new', mcs)
return super().__new__(mcs, name, bases, cls_dict)
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
""" 类创建前的准备工作,返回一个字典,为空的命名空间对象
:param name:
:param bases:
:param kwargs:
:return:
"""
print('1. call MyMeta method: prepare', mcs)
return super().__prepare__(name, bases, **kwargs)
def __init__(cls, name, bases, namespace, **kwargs):
""" 类对象创建完成后的一些初始化工作
:param name:
:param bases:
:param namespace:
:param kwargs:
"""
print('3. call MyMeta method: init', cls)
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
""" 使用类对象创建实例时调用
:param args:
:param kwargs:
:return:
"""
print('1> call MyMeta method: call', cls)
return super().__call__(*args, **kwargs)
class MyClass(metaclass=MyMeta):
def __new__(cls, *args, **kwargs):
print('2> call MyClass method: new', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('3> call MyClass method: init', self)
super().__init__()
def __str__(self):
return 'hello world'
def instance_get():
print('call instance get method:')
instance = MyClass()
print(instance)
if __name__ == '__main__':
print('load model completed!\n---------------------------------------------------------\n')
instance_get()
上述代码的输出:
1. call MyMeta method: prepare <class '__main__.MyMeta'>
2. call MyMeta method: new <class '__main__.MyMeta'>
3. call MyMeta method: init <class '__main__.MyClass'>
load model completed!
---------------------------------------------------------
call instance get method:
1> call MyMeta method: call <class '__main__.MyClass'>
2> call MyClass method: new <class '__main__.MyClass'>
3> call MyClass method: init hello world
hello world
元类的使用例子
我们可以通过继承元类 type
,对其中的一些方法做处理,编写我们需要的定制化元类。元类在构建框架的时候用的较多,像django就使用了元类来构建orm框架(通过元类来感知字段的定义顺序,从而顺序的将字段映射到数据库中。)
平常的开发中,可以使用元类来创建单例、做对象缓存等,其它方式肯定也可以做这些工作,不过元类提供的方案比较便捷还比较优雅。
1. 创建单例
通过上面的程序,我们知道创建实例对象的入口方法是元类中的__call__
方法,我们可以通过覆写这个方法来实现单例模式
"""
使用元类实现单例模式
注意: 这种写法非线程安全!
"""
class Singleton(type):
def __init__(cls, *args, **kwargs):
cls._instance = None # 将实例定义为自己的一个属性
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
print('call Singleton call method')
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class SingletonCls(metaclass=Singleton):
def __init__(self):
print('call SingletonCls init method')
if __name__ == '__main__':
s1 = SingletonCls()
s2 = SingletonCls()
print(s1 is s2)
以下为该脚本的输出:
call Singleton call method
call SingletonCls init method
call Singleton call method
True
2. 做对象缓存
对象创建代价比较打的时候,做对象缓存是个不错的选择,很多缓存是用专门的缓存池来完成,这里也可以直接用元类来做缓存。
import weakref
class CacheMeta(type):
"""
使用元类做对象缓存处理
"""
def __init__(cls, name, bases, namespace, **kwargs):
super().__init__(name, bases, namespace)
cls._cache = weakref.WeakValueDictionary()
def __call__(cls, *args):
# 传入的参数相同的,就给同一个对象,如果对应参数的对象还没创建就先创建对象
if args in cls._cache:
return cls._cache[args]
obj = super().__call__(*args)
cls._cache[args] = obj
return obj
class CacheClass(metaclass=CacheMeta):
def __init__(self, name):
print('Creating instance({!r})'.format(name))
self.name = name
if __name__ == '__main__':
o1 = CacheClass('hello')
o2 = CacheClass('world')
o3 = CacheClass('hello')
print(o1 is o2)
print(o1 is o3)
以下为该脚本输出
Creating instance('hello')
Creating instance('world')
False
True
3. 子类覆盖方法的签名检查
使用元类可以检查子类的方法前面是否和父类的保持一直,如果要强迫继承时签名不能修改,这个是一个操作方式。平时使用IDE在进行编码的时候,如果参数和父类不一致(参数名不一致不会提示),是有提示的,所以这点不用太过在意。
from inspect import signature
import logging
class MatchSignaturesMeta(type):
def __init__(cls, classname, bases, clsdict):
super().__init__(classname, bases, clsdict)
sup = super(cls, cls)
for name, value in clsdict.items():
if name.startswith('_') or not callable(value):
continue
sup_func = getattr(sup, name, None)
if sup_func:
sup_sig = signature(sup_func)
val_sig = signature(value)
if sup_sig != val_sig:
logging.warning(
'Signature mismatch in %s. %s != %s',
value.__qualname__,
sup_sig,
val_sig
)
class Root(metaclass=MatchSignaturesMeta):
pass
class A(Root):
def foo(self, x, y):
pass
def spam(self, x, *, z):
pass
class B(A):
def foo(self, a, b):
pass
def spam(self, x, z):
pass
b = B()
在初始化的时候,日志里面会打印如下内容:
WARNING:root:Signature mismatch in B.foo. (self, x, y) != (self, a, b)
WARNING:root:Signature mismatch in B.spam. (self, x, *, z) != (self, x, z)
4. 动态创建新的类
创建类的时候,是将写在类下面的属性、方法和类对象(通过描述器协议)绑定起来,python提供了手动实现这个过程的方式,这种操作有点类似于反射。
import abc
import types
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
cls_dict = {
'__init__': __init__,
'cost': cost,
}
Stock = types.new_class(
'Stock', # 类名
(), # 父类
{'metaclass': abc.ABCMeta}, # 元类及类定义参数
lambda ns: ns.update(cls_dict) # ns 为 prepare 方法(见上)返回的字典对象
)
Stock.__module__ = __name__
s = Stock('abc', 200, 35)
print(s)
打印出来的结果表明,s就是类Stock的一个对象,这个和直接将Stock定义为class是一样的效果。
<__main__.Stock object at 0x7f153c19bb00>
参考:
python cookbook 第九章; python高级编程 第三章