【python进阶】type和metaclass

本文主要通过type这个类来说明python里面对类的创建过程和原理。

回顾

我们在创建类的时候,大致都是使用如下的方式:

class TestClass(object):
    def __init__(self):
        do()
    
    def func(self):
        do()

其实,当使用语句class TestClass的时候,python后台做的工作就是,使用元类来创建我们的这个类对象,注意了,这里说的是类对象,python里面一切皆对象,也就是说,函数是一个对象,我们的类其实也是一个对象,只不过它是通过元类来创建的。

我们知道__init__其实只是对这个类的实例进行初始化,那么这个实例时哪里产生的呢,是__new__这个函数,__new__函数才是真正创建实例的工厂,__init__是类的一个属性,对,你没看错,它是在类的__dict__中的,实例的__dict__是没有这个属性的,当TestClass的实例x在调用 x.func的时候,后面的逻辑是type(x).func(x),其中type(x)返回的是x所属的类。

只不过类里面的方法(method)他们都有一个属性__self__,就是指定的实例,如果没有实例,那么默认是unbound method,如果有就是bound method

type

type虽然很多时候会认为它是一个函数,其实它是一个类,我们看官方的定义:

class type(object)
class type(name, bases, dict)

其中第一种方式就是返回当前对象的类型,一般情况下就是等于object.__class_,这是我们常用的方式,我们这次主要集中在第二种方式上,它有三个参数,先看官方的解释:

With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the __bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and is copied to a standard dictionary to become the __dict__ attribute.

我们可以看到如果传递了3个参数,那么type会返回一个新的类型对象(简单理解为创建了一个新的类),第一个参数是类的名字,第二个参数是一个远祖,包含了基类,第三个参数是一个字典,就是这个类的属性,也就是会变成类的__dict__

>>> class X:
...     a = 1
...
>>> X = type('X', (object,), dict(a=1))

上面的两个语句相同的,就是创建了一个类X,并且有一个属性a,初始值为1.

metaclass

我们直接上代码,看例子:

print('Define my metaclass.')
class MyMetaclass(type):
    print('MyMetaclass')

    def __init__(self, name, bases, attrs):
        print("MyMetaclass:__init__")
        print(name, bases, attrs)

print('Define a test class.')
class TestClass(list, metaclass=MyMetaclass):
    print('TestClass')

    var1 = 10
    def __init__(self):
        print("TestClass:__init__")

    def func(self):
        pass

首先,上面我们只是简单的定义了一个类MyMetaclass,它继承自type这个类,根据上面的知识,我们知道,type可以创建新的类,在定义TestClass的时候,我们让它继承了list,并且设置了metaclassMyMetaclass,当python解释器遇到这个的时候,会使用MyMetaclass去创建我们的这个类,而不是默认的元类,上面的结果是:

Define my metaclass.
MyMetaclass
Define a test class.
TestClass
MyMetaclass:__init__
TestClass (<class 'list'>,) {'__module__': '__main__', '__qualname__': 'TestClass', 'var1': 10, '__init__': <function TestClass.__init__ at 0x0000021C660786A8>, 'func': <function TestClass.func at 0x0000021C66078730>

解释一下:

  1. 第一个print不用解释了
  2. 其实我们看到处于class里面的print也被执行了,那么同理,我们知道TestClass中的var1=10其实在定义类的时候,就已经被执行了,其实这个也很好理解,我们知道方法,比如func其实在类定义的时候,就已经存在于类的__dict__中,和方法处于同一层的var1print同理也被绑定
  3. 针对第二条,我们在通俗一点,使用上面讲的type来创建类的时候,我们传递的第三个参数,其实也就是在创建类的时候已经传递过去了,一样的道理
  4. 在创建TestClass的时候,遇见了metaclass,会使用MyMetaclass来创建我们的这个类(这里可以看作是一个对象了,类,它本身也就是一个对象而已),那么MyMetaclass肯定会使用类似MyMetaclass(...)这种方式来调用,那么它的__init__函数也就被执行了
  5. MyMetaclass__init__里面,我们定义了三个参数(self不算),其实就是和type的三个参数一样的,只不过最后一个参数,会把这个类里面的顶层(也就是诸如var1 __init__ func)当成属性字典传递过来了。

实例

上面只是简单的打印了一些信息,看清了metaclass的执行流程和类的创建过程,没有实际做什么动作,现在要求创建类的时候,增加一个属性author表明这个类的作者是谁,我们可以按照下面的方式执行:

print('Define my metaclass.')
class MyMetaclass(type):
    print('MyMetaclass')

    def __init__(self, name, bases, attrs):
        print("MyMetaclass:__init__")
        print(name, bases, attrs)

print('Define a test class.')
class TestClass(list, metaclass=MyMetaclass):
    print('TestClass')

    var1 = 10
    def __init__(self):
        print("TestClass:__init__")

    def func(self):
        pass

a = TestClass()
print(a.author)

可以看见print(a.author)这句代码会抛出异常,因为我们并没有设置author这个属性,下面看正确的代码:

print('Define my metaclass.')
class MyMetaclass(type):
    print('MyMetaclass')

    def __new__(cls, name, bases, attrs):
        print("MyMetaclass:__new__")
        attrs['author'] = 'Liburro'
        return type.__new__(cls, name, bases, attrs)

    def __init__(self, name, bases, attrs):
        print("MyMetaclass:__init__")
        print(name, bases, attrs)

print('Define a test class.')
class TestClass(list, metaclass=MyMetaclass):
    print('TestClass')

    var1 = 10
    def __init__(self):
        print("TestClass:__init__")

    def func(self):
        pass

a = TestClass()
print(a.author)

结果是:

Define my metaclass.
MyMetaclass
Define a test class.
TestClass
MyMetaclass:__new__
MyMetaclass:__init__
TestClass (<class 'list'>,) {'__module__': '__main__', '__qualname__': 'TestClass', 'var1': 10, '__init__': <function TestClass.__init__ at 0x0000016B39AB8730>, 'func': <function TestClass.func at 0x0000016B39AB87B8>, 'author': 'Liburro'}
TestClass:__init__
Liburro

我们这里为什么增加了__new__,因为__new__是一个静态函数(就算没有加@staticmethod,python解释器遇到这个函数,默认它就是一个静态函数),它才是真正产生实例的,也就是说__new__产生实例,__init__只不是对实例进行定制化,因为两者的参数(最后三个都和type参数一样)基本一致,我们在使用__new__的时候,增加了一个属性,也就是在创建这个类的时候,增加了一个author的属性,那么后面使用的时候,就可以成功访问了。

上面是通过__new__在创建的时候就设置了属性,那么同理,我们可以通过__init__中的self来设置属性,因为这里的self就是我们这个类(因为这个类是一个实例),看下面的代码:

print('Define my metaclass.')
class MyMetaclass(type):
    print('MyMetaclass')

    def __init__(self, name, bases, attrs):
        print("MyMetaclass:__init__")
        self.author = 'Liburro'
        print(name, bases, attrs)

print('Define a test class.')
class TestClass(list, metaclass=MyMetaclass):
    print('TestClass')

    var1 = 10
    def __init__(self):
        print("TestClass:__init__")

    def func(self):
        pass

a = TestClass()
print(a.author)

结果是:

Define my metaclass.
MyMetaclass
Define a test class.
TestClass
MyMetaclass:__init__
TestClass (<class 'list'>,) {'__module__': '__main__', '__qualname__': 'TestClass', 'var1': 10, '__init__': <function TestClass.__init__ at 0x00000233DAE686A8>, 'func': <function TestClass.func at 0x00000233DAE68730>}
TestClass:__init__
Liburro

可以看见,我们仍然可以访问author这个属性,但是这次没有__new__,只是单纯的通过__init__self赋值了一个属性而已,同样我们看见了传递给__init__的最后一个字典参数中,是没有author这个属性的,因为我们不是在创建的时候指定的,而是在__init__进行定制化的时候指定的,再次理解一下两个词的含义创建它与定制它

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值