什么是元类
在python中我们通过关键字class来定义/创建一个类
class People: def __init__(self, name, age): self.name = name self.age = age obj1 = People('bob', 32)
我们通过调用(实例化)类People产生了obj1这个对象,而python中有一切皆对象的思想,那么类People本身也是对象,它应该也是通过People = 某个类()的调用而产生的,这个类就是元类
可以将元类理解为: 元类是类的类,我们定义类的过程就是元类的实例化过程
那么class关键字是如何创建类的?
如果说我们自定义类这个对象是通过元类而产生的,也就是执行了类对象 = 元类(...)的过程,而括号内应该传入的参数是一个类的关键组成部分
1 传入类名. 2 传入基类 3 传入类的名称空间 最终调用type()这个类进行实例化
我们可以模拟class关键字产生类的步骤:
class_name = 'People' class_bases = (object,) class_dic = {} class_body = ''' def __init__(self, name, age): self.name = name self.age = age ''' # 类在定义阶段就会执行类体代码,将产生的名字存放到类的名称空间中 exec(class_body, {}, class_dic) #这一步是模拟类体代码的执行过程:产生名称空间,并存放执行过程中产生的名字... People = type(class_name,class_bases,class_dic) #实例化type类 obj1 = People('bob',32) #实例化People类 print(People__dict__) print(obj1.__dict__) #可以发现与通过class产生的类无差
为什么要了解使用class关键字创造类的过程?
可以自定义元类来控制类的产生与类的调用过程
1.控制类的产生过程:
我们知道产生对象实际上是发生两件事:创建一个空对象和自动触发构造方法__init__,将空对象和括号内的参数传入
so,也就是在我们创建类的时候是触发了元类的__init__方法,并将类的名字,基类,类的名称空间传入,
我们就可以在__init__方法内加上逻辑判断控制类的产生过程
class MyError(BaseException): # 自定义异常类 def __init__(self, msg): super().__init__() self.msg = msg def __str__(self): return '%s' % self.msg class MyType(type): # 一定要继承type类,否则就是在定义普通的类 def __init__(self, class_name, class_bases, class_dic): super().__init__(class_name, class_bases, class_dic) # 这里我们重写了父类type类的方法 if not class_name.istitle(): raise MyError('操作错误:类名第一个字母必须为大写!') doc = class_dic.get('__doc__') if doc is None or len(doc) == 0: raise MyError('类体代码错误:必须设置类的注释文档并且注释文档不能为空!') class People(object, metaclass=MyType): ''' 通过自定义元类控制类的产生过程 ''' # 不指定,默认通过type()类,实际上就是People = MyType(class_name,class_bases,class_dic)的过程 pass #如果我们将类名改为people小写: __main__.MyError: 操作错误:类名第一个字母必须为大写 #如果我们不设置文档或者文档为空: __main__.MyError: 类体代码错误:必须设置类的注释文档并且注释文档不能为空!
2.控制类的调用过程(控制对象的产生):
1.我们知道一个对象的调用是触发了其类的__call__方法(一个对象的可调用,说明其类中必然拥有__call__方法(当然也可能在类的父类中))
class People: def __init__(self,name): self.name = name bob = People('bob') bob() #错误,原因bob这个对象的类以及People的父类object内都没有__call__方法
class Bar: def __call__(self, *args, **kwargs): pass class People(Bar): def __init__(self,name): self.name = name # def __call__(self, *args, **kwargs): # pass bob = People('bob') bob() #不会报错
2.我们产生对象就是调用了自定义的那个类(本身也是对象),而调用自定义这个类对象,就是触发了其类的__call__方法
而对于产生一个对象又发生了三件事:
1.产生一个空对象
2.自动触发__init__构造方法,并将空对象以及括号内的参数传入
3 返回这个对象
所以我们在__call__内也应该做这三件事:
class MyType(type): # 一定要继承type类,否则就是在定义普通的类 def __init__(self, class_name, class_bases, class_dic): super().__init__(class_name, class_bases, class_dic) # 这里我们重写了父类type类的方法 def __call__(self, *args, **kwargs): # 这里的self为People这个类 # 创建一个空对象 obj = self.__new__(self) # 必须传参,代表创建一个People的空对象 # 触发构造方法 self.__init__(obj, *args, **kwargs) # 我们可以在这里加上其它逻辑,以控制类的实例化过程(这里将对象的数据属性进行封装) obj.__dict__ = {'_%s__%s' % (self.__name__, key): value for key, value in obj.__dict__.items()} # 返回对象 return obj class People(object, metaclass=MyType): def __init__(self, name, age): self.name = name self.age = age bob = People('bob', 32) print(bob.__dict__)
这里需要注意的是,上面的self.__new__,按照属性查找来说,是来自于父类object(而非自定义元类Mytype,或者type类),
那么为什么不直接使用object.__new__,原因是,使用object,就是指名道姓的去调用一个类的函数,它忽略了属性的查找顺序,如果类本身重写了__new__,还是去继承object类(方法重写就变得无意义)
再看属性查找:
我们之前说对象的属性查找顺序是:对象自己--->对象的类--->按照MRO表去查询对象类的父类
而,python中说一切皆对象,那么类本身也是对象,对于类这个对象而言,它的属性查找顺序是?
class Mythod(type): x = 'from metaclass' def __init__(self, class_name, class_bases, claa_dic): super(Mythod, self).__init__(class_name, class_bases, claa_dic) class Foo: x = 'from foo' class Bar(Foo): x = 'from bar' class People(Bar, object, metaclass=Mythod): x = 'from People' def __init__(self, name, age): self.name = name self.age = age print(People.__mro__) print(People.x) #MRO表:(<class '__main__.People'>, <class '__main__.Boo'>, <class '__main__.Foo'>, <class 'object'>) #运行,发现 x = 'from People' ,将People内的x注释 #运行,发现 x = 'from bar' ,将Bar内的x注释 #运行,发现 x = 'from foo',将Foo内的x注释 #运行,发现 x = 'from metaclass'
小结:对于类这个对象,查找属性是先遵循MRO列表查询,如果查询不到,再到元类层去查找(这里的元类层包含:自定义的元类Mythod---->type类)