本文主要通过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
,并且设置了metaclass
是MyMetaclass
,当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>
解释一下:
- 第一个
print
不用解释了 - 其实我们看到处于class里面的
print
也被执行了,那么同理,我们知道TestClass
中的var1=10
其实在定义类的时候,就已经被执行了,其实这个也很好理解,我们知道方法,比如func
其实在类定义的时候,就已经存在于类的__dict__
中,和方法处于同一层的var1
和print
同理也被绑定 - 针对第二条,我们在通俗一点,使用上面讲的
type
来创建类的时候,我们传递的第三个参数,其实也就是在创建类的时候已经传递过去了,一样的道理 - 在创建
TestClass
的时候,遇见了metaclass
,会使用MyMetaclass
来创建我们的这个类(这里可以看作是一个对象了,类,它本身也就是一个对象而已),那么MyMetaclass
肯定会使用类似MyMetaclass(...)
这种方式来调用,那么它的__init__
函数也就被执行了 - 在
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__
进行定制化的时候指定的,再次理解一下两个词的含义创建它与定制它
。