python中metaclass元类用法详解

python中metaclass元类用法详解

1. 内置函数type()和isinstance():

在介绍metaclass之前,首先要了解一些相关的内置函数:type() 和 isinstance()

1.1 type():

type()函数的主要功能有两个:

  • 查看一个变量(对象)的类型
  • 创建一个类(class)

另外type(这里不是指type()函数)本身也是一个类,这在后面进行介绍。

  1. 查看一个对象类型:

    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__属性 能相同,都是返回对象的类型。

  2. 创建一个类:
    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(object):语法创建的类是一样的。

    正常我们都是用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的关系为:
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类

这就对我们造成了极大的困扰,主要有以下三点:

  1. type是一个metaclass,而且是一个默认的metaclass。也就是说,type是object的类型,object是type的一个实例
  2. type是object的一个子类,继承了object的所有属性和方法;
  3. type还是可以callable的,即实现了__call__方法,可以当做一个函数使用。

type和object的关系有点像“鸡生蛋,蛋生鸡”一样。type是object的子类,同时object又是type的一个实例(type是object的类型),二者是不可分离的。另外,type的类型也是type。

type与object的关系如下图所示:
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):  # 继承自type
    pass

class CustomClass(metaclass=CustomMetaclass):  # 指定metaclass
    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))  # True,也是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)

对应图中的每一条实线表示一次具体操作,每一条虚线表示返回的过程。

一个实例对象的整个创建过程大致是这样的:

  1. metaclass.__init__进行一些初始化的操作,如一些全局变量的初始化;
  2. metaclass.__call__创建实例,在创建过程中会调用class的__new__和__init__方法;
  3. class.__new__进行具体的实例化操作,并返回一个实例对象obj(0x000001AF9ED34278);
  4. class.__init__对返回的实例对象obj(0x000001AF9ED34278)进行初始化,如一些状态和属性的设置;
  5. 返回一个用户真正需要使用的对象obj(0x000001AF9ED34278)。

至此,我们知道了,通过metaclass几乎可以自定义一个对象生命周期的各个过程,我们可以在创建对象时通过metaclass对其进行灵活的修改和定制


注意 : 通过这个例子我们也可以看出,__init__和__call__方法在metaclass和class中使用时不同的:

  • 在metaclass中:
    • __init__:对继承该metaclass的子类(子类相当于metaclass的实例)进行初始化(初始化类属性)
    • __call__:获取继承该metaclass的子类创建实例时的参数,然后调用子类的__new__和___init__方法对子类进行实例化和初始化(这里指创建子类实例对象)
  • 在class中:
    • __init__:用来对__new__创建的对象进行初始化(初始化对象属性)
    • __call__:用来声明该类的对象是可调用的

参考:

《人人都懂设计模式:从生活中领悟设计模式》 罗伟富

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值