python中metaclass元类用法详解
1. 内置函数type()和isinstance():
在介绍metaclass之前,首先要了解一些相关的内置函数:type() 和 isinstance()
1.1 type():
type()函数的主要功能有两个:
- 查看一个变量(对象)的类型
- 创建一个类(class)
另外type(这里不是指type()函数)本身也是一个类,这在后面进行介绍。
-
查看一个对象类型: class ClassA:
name = 'type test'
a = ClassA()
b = 3.0
print(type(a))
print(type(b))
print(type('this is string'))
print('-' * 20)
print(a.__class__)
print(b.__class__)
执行结果: <class '__main__.ClassA'>
<class 'float'>
<class 'str'>
--------------------
<class '__main__.ClassA'>
<class 'float'>
这个时候,type()通常与object.__class__属性 能相同,都是返回对象的类型。 -
创建一个类: type()函数可以通过传入以下三个参数来创建一个类: type(name, bases, dict)
- name: 要创建的类的名称
- bases:要创建的类的基类,因为python允许多继 因此这是一个tuple元组
- dict:要创建的类的属性,是一个dict字典,也就 字典,通过
object.__dict__ 属性可以查看该类的 相关信息。 ClassVar = type('ClassA', (object,), dict(name='type test'))
a = ClassVar()
print(type(a))
print(a.name)
执行结果: <class '__main__.ClassA'>
type test
我们通过type('ClassA', (object,), dict(name='type test')) 创建一个 ClassVar,再通过ClassVar()创建一个实例a。通过type创建的类ClassA和使 class ClassA语法创建的类是一样的。 正常我们都是用class 语法来定义一个类,但是type()函数允许我们可以动态的在代码中创建一个类 。Python是一种解释型的动态语言,动态语言与静态语言(C、Java等)的最大区别是: 可以很方便的在运行期间动态的创建类。
1.2 isinstance():
isinstance()的作用是判断一个对象是不是每个类型的实例:
isinstance(obj, classinfo)
- obj:要判断的对象
- classinfo:期望的类型
如果obj是classinfo的一个实例或classinfo子类的一个实例,则返回True,否则返回False。
class Base:
name = 'Base'
class SubClass(Base):
pass
base = Base()
sub = SubClass()
print(isinstance(base, Base))
print(isinstance(base, SubClass))
print('-' * 20)
print(isinstance(sub, Base))
print(isinstance(sub, SubClass))
执行结果:
True
False
--------------------
True
True
如果要知道子类与父类之间的继承关系,可用issubclass()方法 或object.__bases__属性 。
print(issubclass(Base, object))
print(issubclass(SubClass, Base))
print(Base.__base__)
print(SubClass.__base__)
执行结果:
True
True
<class 'object'>
<class '__main__.Base'>
2. metaclass:
metaclass直译为元类,他可以控制类的属性和类实例的创建过程。使用metaclass的主要目的也是在创建类实例的时候,精准的控制和定义类的创建过程和类对象的创建过程 。
在python中一切皆对象:一个整数是对象,一个字符串是对象,一个类实例是对象,类本身也是对象。一个类也是一个对象,和其他类一样,它是metaclass的一个实例。
如下图所示,对象obj、类class和metaclass的关系为:
class MyClass:
pass
m = MyClass()
print(type(MyClass))
print(type(m))
print('-' * 30)
print(isinstance(m, MyClass))
print(isinstance(MyClass, type))
执行结果:
<class 'type'>
<class '__main__.MyClass'>
------------------------------
True
True
默认的metaclass是type类型的,所以上面的代码中可以看到MyClass的类型的type。但是为了向后兼容,type类型总是让人感到困惑,因为它也可以作为函数使用,返回一个对象的类型。
这种困扰的始作俑者就是type,type在Python类中一个极为特殊的类型。为了更好的了解metaclass,我们首先要搞清楚type和object的关系。
2.1 type和object的关系:
在Python3中,object是所有类的基类,内置的类、自定义的类都直接或间接的继承自object类。在python源码中type类也继承自object类
这就对我们造成了极大的困扰,主要有以下三点:
- type是一个metaclass,而且是一个默认的metaclass。也就是说,
type是object的类型,object是type的一个类实例 ; - type是object的一个子类,继承了object的所有属性和方法;
- type还是可以callable的,即实现了__call__方法,可以当做一个函数使用。
type和object的关系有点像“鸡生蛋,蛋生鸡”一样。type是object的子类,同时object又是type的一个实例(type是object的类型),二者是不可分离的。另外,type的类型也是type。
type与object的关系如下图所示:
我们可以自定义metaclass,自定义的metaclass必须继承自type 。自定义的metaclass通常以Metaclass或Meta 作为后缀结尾,以示区分。CustomMetaclass和type都是metaclass类型的。
所有的类都继承自object,包括内置的类和用户自定义的类。一般来说,类Class的类型为type(即一般的类的metaclass是type,是type的一个实例)。如果要改变类的metaclass,必须要在定义类时显式地指定它的metaclass: class ClassA(metaclass=CustomMetaclass)
class CustomMetaclass(type):
pass
class CustomClass(metaclass=CustomMetaclass):
pass
print(type(object))
print(type(type))
print('-' * 20)
obj = CustomClass()
print(type(CustomMetaclass))
print(type(CustomClass))
print(type(obj))
print('-' * 20)
print(isinstance(obj, CustomClass))
print(isinstance(obj, object))
执行结果:
<class 'type'>
<class 'type'>
--------------------
<class 'type'>
<class '__main__.CustomMetaclass'>
<class '__main__.CustomClass'>
--------------------
True
True
2.2 自定义metaclass:
结合上面所述,自定义的metaclass必须继承自type 。自定义的metaclass通常以Metaclass或Meta 作为后缀结尾。
此外,自定义metaclass,需要注意以下几点:
-
object的__init__方法只有一个参数,但是自定义metaclass的__init__方法有4个参数; object的__init__方法只有一个参数: def __init__(self) 。 但type重写了__init__方法,有四个参数:def __init__(cls, what, bases=None, dict=None) 。 因为自定义metaclass类继承自type,所以重写__init__方法时也要有4个参数。 -
对于普通的类,重写__call__方法说明类的对象是可调用的。在metaclass中__call__方法还负责对象的创建。 一个对象的创建过程大致如下图所示: 其中有关__new__和__init__方法的使用可参考:《python中的__new__, __init__和__call__函数用法详解》
结合代码来看看这一过程:
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 __new__(cls, *args, **kwargs):
self = super().__new__(cls)
print('CustomClass.__new__ self: ', self)
return self
def __init__(self, *args, **kwargs):
print('CustomClass.__init__ self: ', self)
super().__init__()
def __call__(self, *args, **kwargs):
print('CustomClass.__call__ args: ', args)
obj = CustomClass('arg1', 'arg2', kwarg1=1, kwarg2=2)
print('-' * 30)
print(type(CustomClass))
print(obj)
obj(1, 2, 3)
执行结果:
CustomMetaclass.__init__ cls: <class '__main__.CustomClass'>
CustomMetaclass.__call__ args: ('arg1', 'arg2') {'kwarg1': 1, 'kwarg2': 2}
CustomClass.__new__ self: <__main__.CustomClass object at 0x000001AF9ED34278>
CustomClass.__init__ self: <__main__.CustomClass object at 0x000001AF9ED34278>
CustomMetaclass.__call__ self: <__main__.CustomClass object at 0x000001AF9ED34278>
------------------------------
<class '__main__.CustomMetaclass'>
<__main__.CustomClass object at 0x000001AF9ED34278>
CustomClass.__call__ args: (1, 2, 3)
对应图中的每一条实线表示一次具体操作,每一条虚线表示返回的过程。
一个实例对象的整个创建过程大致是这样的:
- metaclass.__init__进行一些初始化的操作,如一些全局变量的初始化;
- metaclass.__call__创建实例,在创建过程中会调用class的__new__和__init__方法;
- class.__new__进行具体的实例化操作,并返回一个实例对象obj(0x000001AF9ED34278);
- class.__init__对返回的实例对象obj(0x000001AF9ED34278)进行初始化,如一些状态和属性的设置;
- 返回一个用户真正需要使用的对象obj(0x000001AF9ED34278)。
至此,我们知道了,通过metaclass几乎可以自定义一个对象生命周期的各个过程,我们可以在创建对象时通过metaclass对其进行灵活的修改和定制 。
注意 : 通过这个例子我们也可以看出,__init__和__call__方法在metaclass和class中使用时不同的:
- 在metaclass中:
__init__ :对继承该metaclass的子类进行初始化(初始化类属性)__call__ :获取继承该metaclass的子类创建实例时的参数,然后调用子类的__new__和___init__方法对子类进行实例化和初始化 - 在class中:
__init__ :用来对__new__创建的对象进行初始化(初始化对象属性)__call__ :用来声明该类的对象是可调用的
参考:
《人人都懂设计模式:从生活中领悟设计模式》 罗伟富
|