前言
之前在介绍单例模式的实现方法时,其中一种方法是通过metaclass
实现的,这篇文章主要介绍metaclass
。
内置函数type()
这里我们从type入手,详细了解。相信很多同学都使用过type(),用来查看一个对象的类型。
class classA:
name = "type test"
a = classA()
b = 3.0
print(type(a)) # <class '__main__.classA'>
print(type(b)) # <class 'float'>
当然可能有些同学是这样看对象类型的
print(a.__class__) # <class '__main__.classA'>
print(b.__class__) # <class 'float'>
可以看到,此时type()
与object.__class__
的功能相同,都是返回对象的类型
翻开源码看看,type
居然还可以创建一个类
class type(object):
"""
type(object_or_name, bases, dict)
type(object) -> the object's type
type(name, bases, dict) -> a new type
"""
......
从源码注解中可以看到,当传入三个参数时,用来创建一个类。我们看下面这个例子
ClassA = type('ClassA', (object,), dict(name = "type test"))
a = ClassA()
print(type(a)) # <class '__main__.ClassA'>
print(a.name) # type test
可以看到已经成功创建了ClassA
类型的类
通常,我们都是用class xxx来定义一个类的;但是type()函数也允许我们动态的创建一个类。 python是一种解释型的动态语言,动态语言与静态语言最大的区别是,可以方便的在运行期间动态的创建类。
感兴趣的同学,也可以看看底层源码
cpython/Objects/typeobject.c
static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
/* 检查参数 */
if (!PyType_Check(type)) {
PyErr_Format(PyExc_TypeError,
"descriptor '__call__' requires a 'type' object but "
"received an '%.200s'",
type->tp_name);
return NULL;
}
/* 创建一个新对象 */
PyObject *obj = type->tp_new(type, args, kwds);
if (obj == NULL)
return NULL;
/* 初始化对象 */
int init_result = type->tp_init(obj, args, kwds);
if (init_result < 0) {
Py_DECREF(obj);
return NULL;
}
return obj;
}
上述源码是 type
函数的一个简化版本,在实际的源码中还包含了更多的逻辑和处理细节。这段代码主要进行了以下操作:
- 检查传入的
type
参数是否是一个类型对象。 - 使用类型对象的
tp_new
方法创建一个新的对象。 - 调用对象的
tp_init
方法初始化对象。 - 返回创建并初始化后的对象。
metaclass
进入正题,看看到底啥是metaclass
呢?metaclas直译为元类,可控制类的属性和类实例的创建过程。
在 Python 中,使用元类的常见方式是通过在类定义的时候将元类指定为所谓的 __metaclass__
属性。例如:
class MyClass(metaclass=MyMetaClass):
pass
在上述示例中,MyMetaClass
将作为 MyClass
类的元类,它负责控制 MyClass
类的创建和行为。
python中,一切都可以是对象:一个整数、一串字符串、一个类实例、类本身都是对象。 一个类也是一个对象,他是元类(metaclass)的一个实例。看迷糊了吧,我们看个例子,就会清晰很多
class MyClass:
pass
m = MyClass()
print(type(MyClass)) # <class 'type'>
print(type(m)) # <class '__main__.MyClass'>
print(isinstance(m, MyClass)) # True
print(isinstance(MyClass, type)) # True
默认的metaclass是type类型的,所以我们看到MyClass的类型是type。(MyClass是metaclass的一个实例) type在python中是一个极为特殊的类型。为了彻底理解metaclass,我们先搞清楚type与object的关系。
type与object的关系
在python3中,object是所有类的基类,内置的类、自定义的类都直接或间接的继承自object类。如果去看源码,会发现type类也继承自object类。这对 我们的理解造成了极大的困扰,主要以下三点:
- type是object的类型,object是type的一个实例
- type是object的一个子类,继承object的所有属性和行为
- type还是一个callable,即实现了
__call__
方法,可以当成一个函数使用。
type与object有点像"蛋生鸡"与"鸡生蛋"的关系,type是object的子类, 同时object又是type的一个实例(type是object的类型),二者是不可分离的 type的类型也是type,先记住吧。
print(type(object)) # <class 'type'>
print(type(type)) # <class 'type'>
我们可以自定义metaclass,自定义的metaclass必须继承自type。 一般来说,类class的类型为type(即一般的类的metaclass是type,是type的一个实例)。如果要改变类的metaclass,必须在定义类时 显示地指定他的metaclass
class CustomMetaClass(type):
pass
class CustomClass(metaclass=CustomMetaClass):
pass
obj = CustomClass()
print(type(CustomClass)) # <class 'CustomMetaClass'>
print(type(obj)) # <class '__main__.CustomClass'>
print(isinstance(obj, CustomClass)) # True
print(isinstance(obj, object)) # True
自定义metaclass
class CustomMetaClass(type):
def __init__(cls, what, bases=None, dict=None):
print("CustomMetaClass.__init__ cls:", cls)
super().__init__(what, bases, dict)
def __call__(cls, *args, **kwargs):
print("CustomMetaClass.__call__ args:", args, kwargs)
self = super(CustomMetaClass, cls).__call__(*args, **kwargs)
print("CustomMetaClass.__call__ self:", self)
return self
class CustomClass(metaclass=CustomMetaClass):
def __init__(self, *args, **kwargs):
print("CustomClass.__init__ self:", self)
super().__init__()
def __new__(cls, *args, **kwargs):
self = super().__new__(cls)
print("CustomClass.__new__ self:", self)
return self
def __call__(self, *args, **kwargs):
print("CustomClass.__call__ args:", args)
obj = CustomClass("Meta arg1", "Meta arg2", kwarg1=1, kwarg2=2)
"""
CustomMetaClass.__init__ cls: <class '__main__.CustomClass'>
CustomMetaClass.__call__ args: ('Meta arg1', 'Meta arg2') {'kwarg1': 1, 'kwarg2': 2}
CustomClass.__new__ self: <__main__.CustomClass object at 0x11b73b5e0>
CustomClass.__init__ self: <__main__.CustomClass object at 0x11b73b5e0>
CustomMetaClass.__call__ self: <__main__.CustomClass object at 0x11b73b5e0>
"""
实例对象的整个创建过程大致是这样的:
metaclass.__init__
进行一些初始化的操作,如一些全局变量的初始化metaclass.__call__
创建实例,在创建的过程中会调用class的__new__
和__init__
方法class.__new__
进行具体的实例化的操作,并返回实例对象obj(0x11b73b5e0)class.__init__
对返回的实例对象obj(0x11b73b5e0)进行初始化,如一些状态和属性的设置- 返回一个用户真正需要使用的对象obj(0x11b73b5e0)
到这里我们应该知道了,通过metaclass几乎可以自定义一个对象生命周期的各个过程。
注意:
- object的
__init__
方法只有1个参数,但自定义的metaclass的__init__
有4个参数 object的__init__
方法只有1个参数:def __init__(self)
,但type重写了__init__
方法,有4个参数:def __init__(cls, what, bases=None, dict=None)
: 因为自定义metaclass继承自type,所以重写init方法时也要4个参数 - 对于普通的类,重写
__call__
方法说明对象是callable的。在metaclass中__call__
方法还负责对象的创建。
实战
示例一
看下面这段代码
import yaml
class Monster(yaml.YAMLObject):
yaml_tag = '!Monster'
def __init__(self, name, hp, ac, attacks):
self.name = name
self.hp = hp
self.ac = ac
self.attacks = attacks
def __repr__(self):
return f"({self.__class__.__name__}name={self.name}, hp={self.hp}, ac={self.ac}, attacks={self.attacks})"
m = yaml.load("""
--- !Monster
name: Cave spider
hp: [2,6] # 2d6
ac: 16
attacks: [BITE, HURT]
""", Loader=yaml.Loader)
print(m) # (Monstername=Cave spider, hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])
Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])
print(yaml.dump(Monster(
name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT'])))
'''
!Monster
ac: 16
attacks:
- BITE
- HURT
hp:
- 3
- 6
name: Cave lizard
'''
这段代码是一个使用 PyYAML 库的示例,它演示了如何使用 YAML 格式来定义(Monster)对象,并使用 PyYAML 的 load()
方法将 YAML 数据加载为 Monster 类的实例。
1、定义 Monster 类:class Monster(yaml.YAMLObject)
定义了一个名为 Monster 的类。通过继承 yaml.YAMLObject
类并设置 yaml_tag
属性为 !Monster
,将 Monster 类与 YAML 中的 !Monster
标签关联起来。
2、Monster 类的构造函数:def __init__(self, name, hp, ac, attacks)
是 Monster 类的构造函数,用于初始化 Monster 对象的属性。name 表示怪物的名称,hp 表示生命值,ac 表示防御等级,attacks 表示攻击方式。
3、Monster 类的显示方法:def __repr__(self)
定义了 Monster 类的显示方法,用于返回类对象的字符串表示。
4、使用 yaml.load()
加载 YAML 数据:yaml.load("""...""", Loader=yaml.Loader)
使用 PyYAML 的 load()
方法加载 YAML 数据。"""..."""
之间的内容是 YAML 格式的数据,表示一个Monster对象的属性。Loader=yaml.Loader
指定使用 PyYAML 的 Loader 加载器来解析 YAML 数据。
5、最后,代码会将 YAML 数据加载为 Monster 类的实例,并打印该对象的字符串表示。
可以看到,只要简单的继承yaml.YAMLObject
,就可以让普通的Python Object
具有序列化和逆序列化能力。
YAML 的这种动态序列化 / 逆序列化功能正是用 metaclass 实现的。
源码
class YAMLObjectMetaclass(type):
"""
The metaclass for YAMLObject.
"""
def __init__(cls, name, bases, kwds):
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
if isinstance(cls.yaml_loader, list):
for loader in cls.yaml_loader:
loader.add_constructor(cls.yaml_tag, cls.from_yaml)
else:
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
cls.yaml_dumper.add_representer(cls, cls.to_yaml)
class YAMLObject(metaclass=YAMLObjectMetaclass):
......
如何将将 Monster 类与 YAML 中的 !Monster
标签关联起来呢?
一个简单的做法就是建立一个全局变量registry,把所有需要逆序列化的 YAMLObject,都注册进去。比如下面这样:
registry = {}
def add_constructor(target_class):
registry[target_class.yaml_tag] = target_class
然后,在 Monster 类定义后面加上下面这行代码:
add_constructor(Monster)
很明显,这会现的很麻烦,而且也很容易忘记。那YAML
是如何做的呢?看上面的源码,正是使用了metaclass
解决该问题
解释一下上面的源码:
-
定义 YAMLObjectMetaclass 类:
class YAMLObjectMetaclass(type)
定义了一个名为 YAMLObjectMetaclass 的元类(metaclass),它继承自内建的 type 类。 -
初始化方法:
def __init__(cls, name, bases, kwds)
是 YAMLObjectMetaclass 类的初始化方法。在此方法中,首先调用父类的初始化方法super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
来确保正确地初始化当前类。然后检查是否定义了 yaml_tag 属性,并且该属性不为 None。 -
注册构造函数和序列化器:根据 yaml_tag 属性的值,将当前类的 from_yaml() 方法注册为解析器的构造函数,将当前类的 to_yaml() 方法注册为序列化器的表示器。
- 如果 yaml_loader 属性是一个列表(list),则遍历列表中的每个加载器(loader),通过 add_constructor() 方法将当前类的 yaml_tag 和 from_yaml() 方法关联起来。
- 如果 yaml_loader 属性不是列表,则假定它是一个加载器对象,直接使用 add_constructor() 方法将当前类的 yaml_tag 和 from_yaml() 方法关联起来。
最后,使用 cls.yaml_dumper.add_representer() 方法将当前类和 to_yaml() 方法关联起来,以便在将对象序列化为 YAML 数据时调用 to_yaml() 方法。
YAML 应用 metaclass,拦截了所有 YAMLObject 子类的定义。也就是说,在你定义任何 YAMLObject 子类时,Python 会强行插入运行下面这段代码,把我们之前想要的add_constructor(Monster)给自动加上。这样开发者使用起来就简单很多。
示例二
用来实现单例模式
class Singleton(type):
def __init__(cls, what, bases=None, dict=None):
super().__init__(what, bases, dict)
cls._instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class CustomClass(metaclass=Singleton):
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
最后
metaclass要慎用,它会改变类的行为,使用不当,可能造成很大的风险。一般应用层,metaclass都用不到。这里主要作为一个了解,知道python的魔法特性。