元类
元类
一、什么是元类
- 元类属于python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也只是自圆其说、点到为止,从对元类的控制上来看就破绽百出、逻辑混乱,今天我就来带大家来深度了解python元类的来龙去脉。
- 笔者深入浅出的背后是对技术一日复一日的执念,希望可以大家可以尊重原创,为大家能因此文而解开对元类所有的疑惑而感到开心!!!
- 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类
简单说只要继承了type他就是元类
class Person: # Foo=元类()
pass
元类 type—实例化--->类Foo ---实例化类--> 对象
Person类也是个对象,那他一定是由一个类实例化得到的,这个类就叫元类
1.1 如何找元类
print(type(p1))
# 同理:type类是产生所有类的元类
print(type(Person))
print(type(list))
print(type(dict))
print(type(object))
# 内置函数
print(type(print))
<class '__main__.Person'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'builtin_function_or_method'>
二、为什么用元类
- 元类是负责产生类的,所以我们学习元类或者自定义元类的目的:是为了控制类的产生过程,还可以控制对象的产生过程
三、 内置函数exec
cmd = """
x=1
print('exec函数运行了')
def func(self):
pass
"""
class_dic = {}
# 执行cmd中的代码,然后把产生的名字丢入class_dic字典中
exec(cmd, {}, class_dic)
exec函数运行了
print(class_dic)
{'x': 1, 'func': <function func at 0x10a0bc048>}
四、class创建类
- 如果说类也是对象,那么用class关键字的去创建类的过程也是一个实例化的过程,该实例化的目的是为了得到一个类,调用的是元类
- 用class关键字创建一个类,用的默认的元类type,因此以前说不要用type作为类别判断
- class 加类名,就会把类构造出来
class People: # People=type(...)
country = 'China'
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print('%s is eating' % self.name)
print(type(People))
<class 'type'>
六、type元类
Person 类是有type实例化产生的,向type传了一堆参数,然后type() 调用类的__init__方法,就会创建一个类,
type()创建类格式:
type(object, bases(继承的基类), dict)
object: object (所有的基类) or name (类名:你所要创建的名字),类的名字, 是个字符串;
base: 是他的所有的父类(元组的形式)、基类;
dict: 名称空间,是一个字典;
创建类的3个要素:类名,基类,类的名称空间 ;
通过type直接产生的类,不用class关键字;
l = {}
g = {}
exec("""
school='hnnu'
def __init__(self,name):
self.name=name
def score(self):
print('分数是100')
""", g, l)
def __init__(self, name):
self.name = name
# 创建类 People = type(类名,基类,类的名称空间)
Person = type('Person', (object, ), {'school': 'hnnu', '__init__': __init__})
# 创建对象
p = Person('randy')
print(p.name)
# 创建类
Person = type('Person', (object,), l)
print("*"*9)
print(Person)
print("*"*9)
print()
type('Person', (object,), l)
# # 获取属性值
print(Person.school)
#
print(Person.__dict__)
#
print(Person.__bases__)
print(Person.mro())
七、通过元类来控制类的产生
自定义元类:来控制类的产生, 可以控制类名, 可以控制类的继承父类, 控制类的名称空间
自定制的元类必须继承type,写一个类继承type,这种类就叫元类
class Mymeta(type):
# def __init__(self, *args, **kwargs):
def __init__(self, name, base, dic):
print(name) # 类名
print(base) # 基类
print(dic) # 名称空间
"""
通过 class创建的类继承,自定义的元类, 在Person中定义第一个参数表示类的名称,第二个参数表示继承的基类
,而类中的代码则是,产生的名称空间,
"""
class Person(object, metaclass=Mymeta):
school = 'hnnu'
def __init(self, name):
self.name = name
def score(self):
print("分数是: 100")
p = Person()
7.1 控制类的产生
# 练习一类名以sb开头
class Mymeta(type):
def __init__(self, name, base, dic):
# 练习一 加限制, 控制类名必须以sb开头
print(name)
if not name.startswith('sb'):
raise Exception("类名没有以sb开头")
class sb_Person(object, metaclass=Mymeta):
school = 'hnnu'
def __init__(self, name):
self.name = name
print(2)
def score(self):
print("分数是: 100")
p1 = sb_Person('ran')
# 练习二, 类必须有注释
class Mymeta(type):
def __init__(self, name, base, dic):
print(self.__dict__) # 产生的名称空间是类
print(dic)
doc = self.__dict__['__doc__']
print("元类")
if not doc:
raise Exception("必须有注释")
print(doc)
class Person(object, metaclass=Mymeta):
"""
我已经注释了
"""
school = 'hnnu'
def __init__(self, name):
print("自己")
self.name = name
def score(self):
print("分数是: 100")
# 继承元类,首先会先进入元类中的__init__在返回来执行自己的
p1 = Person('ran')
继承元类,首先会先进入元类中的__init__在返回来执行自己的
7.2 控制类产生的模板
# 控制类产生模板
class Mymeta(type):
def __init__(self, name, base, dic):
if self.name == Person:
raise Exception("名称错误")
class Person(metaclass=Mymeta):
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('laowang', 19)
八、通过元类控制类的调用过程(控制创建类对象的过程)
要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法、__call__方法,该方法会在调用对象时自动触发
我们之前说类实例化第一个调用的是__init__,但__init__其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 __new__ 方法。
__new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。
8.1 __call__控制类的调用过程
# 创建元类
class Mymeta(type):
def __call__(self, *args, **kwargs):
print(self)
print(self.__dict__)
print(args)
print(kwargs)
print('进入__call__')
return 123
# 创建类
class Person(object, metaclass=Mymeta):
school = 'hnnu'
def __init__(self, name):
print('person__init__')
self.name = name
def score(self):
print("分数100")
def __call__(self, *args, **kwargs):
print('Person 进入__call__')
return 1
<class '__main__.Person'>
('randy',)
{}
进入元类__call__
123
8.2 __call__控制类的调用过程实现
class Mymeta(type):
def __call__(self, *args, **kwargs):
"""
因为__call__返回的空对象,所以要自己生成对象,通过__new方式)
:param self: self ==> Person
:param args: Person(randy)
:param kwargs: Person(age=18)
:return:
"""
print('进入元类__call__')
# self ==> Person
# 出现递归的原因 self()加括号运行相当于Person()所以还是调用元类的__call__方法
# return self(*args) # 出现递归
# 实例化产生一个Person类的对象,借助__new__来产生,需要把类传过去,才能产生对象
# 方式一(调用父类object中的__new__)
self.__new__(self)
# obj 是Person类的对象,只不过是空的,
# 方式二
obj = object.__new__(self) # (因为__call返回的空对象,所以要自己生成对象,通过__new方式)
# obj = self.__new__(self)
# 调用__init__方法完成初始化
# 类来调用__init__方法, 就是个普通函数,有几个参数就要传几个参数
# self.__init__(obj, *args, **kwargs)
# 对象来调用__init__方法,对象的绑定方法,会把自身传过去
kwargs['age'] = 18
obj.__init__(*args, **kwargs)
print(obj)
return obj
class Person(object, metaclass=Mymeta):
school = 'hnnu'
def __init__(self, name, *args,**kwargs):
print('person__init__')
self.name = name
def score(self):
print("分数100")
def __call__(self, *args, **kwargs):
print('Person 进入__call__')
return 1
p = Person('randy')
print(p.__dict__)
print(Person.__dict__)
p.name
进入元类__call__
person__init__ 2
{'age': 18}
<__main__.Person object at 0x038D4D70>
{'name': 'randy'}
{'__module__': '__main__', 'school': 'hnnu', '__init__': <function Person.__init__ at 0x0C68FF18>, 'score': <function Person.score at 0x0C68FED0>, '__call__': <function Person.__call__ at 0x0C68FE88>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
class Mymeta(type):
def __call__(self, *args, **kwargs):
# self 就是Person类
print(2)
print(self.mro()) # 类对象的属性查找顺序
# obj=self.__new__(self) # 方法1,通过self的new方法创建对象
obj = object.__new__(self) # 方法2,通过object的new方法创建对象
obj.__init__(*args, **kwargs) # 调用对象的init方法
return obj
def __init__(self, *args):
print(3)
print(args)
class Person(metaclass=Mymeta): # 实际上是先调用元类的call方法,再回调自定义元类的init方法
age = 33 # 39;18'
def __init__(self, name): # 当类实例化生成对象后,会通过call调用init方法
print(1)
self.name = name
p = Person(name= 'randy') # 先调用自定义元类的call方法
print(p.__dict__)
print(p.name)
print(p)
3
('Person', (), {'__module__': '__main__', '__qualname__': 'Person', 'age': 33, '__init__': <function Person.__init__ at 0x0C017198>})
2
[<class '__main__.Person'>, <class 'object'>]
1
{'name': 'randy'}
randy
<__main__.Person object at 0x03263730>
总结:
- __call__的第一个参数self是创建类的类名,第二、三参数为创建对象属性的值;
- 直接返回return self(*args) 会出现递归原因在于,self(*args)相当于Person(*args)最终还是调用元类的 __call__,所以会出现死循环;解决办法,创建类对象调用自己的__init__方法
# 方式一
obj = object.__new__(self) # (因为__call返回的空对象,所以要自己生成对象,通过__new方式)
# obj = object.__new__(Person)
obj.__init__(*args, **kwargs) # 调用Person类中自己的__init__方法
return obj
# 方式三
obj = self.__new__(self) # 调用还是object中的__new__方法
obj.__init__(*args, **kwargs) #等同与 self.__init__(obj, *args, **kwargs) 调用Person类中自己的__init__方法
return obj
print(self.__new__ is object.__new__) #True
8.3 当前对象的__call__
class Person1():
school = 'oldboy'
def __init__(self, name):
print(1)
self.name = name
def score(self):
print('分数是100')
def __call__(self, *args, **kwargs):
print('xxxx')
# 自动调用触发init的执行
p = Person1('randu')
# 触发自己的__call__
p()
print('xxxx')
8.4 __call__ 最终版
注意:__new()__ 函数只能用于从object继承的新式类。
class Mymeta(type):
def __call__(self, *args, **kwargs):
print(self) # self是People
print(args) # args = ('nick',)
print(kwargs) # kwargs = {'age':18}
# return 123
# 1. 先造出一个People的空对象,申请内存空间
# __new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。
obj = self.__new__(self) # 虽然和下面同样是People,但是People没有,找到的__new__是父类的
# 2. 为该对空对象初始化独有的属性
self.__init__(obj, *args, **kwargs)
# 3. 返回一个初始化好的对象
return obj
- People = Mymeta(),People()则会触发__call__
class People(object, metaclass=Mymeta):
country = 'China'
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print('%s is eating' % self.name)
# 在调用Mymeta的__call__的时候,首先会找自己(如下函数)的,自己的没有才会找父类的
# def __new__(cls, *args, **kwargs):
# # print(cls) # cls是People
# # cls.__new__(cls) # 错误,无限死循环,自己找自己的,会无限递归
# obj = super(People, cls).__new__(cls) # 使用父类的,则是去父类中找__new__
# return obj
-
类的调用,即类实例化就是元类的调用过程,可以通过元类Mymeta的__call__方法控制
-
分析:调用Pepole的目的
- 先造出一个People的空对象
- 为该对空对象初始化独有的属性
- 返回一个初始化好的对象
obj = People('randy', age=18)
<class '__main__.People'>
('randy',)print(obj.__dict__)
九、把对象属性都变成私有
class Mymeta(type):
def __call__(self, *args, **kwargs):
print("__call__ 第一")
# 产生空对象
obj = object.__new__(self)
# 调用子类__init__
obj.__init__(*args, **kwargs)
# 名称空间创建完成之后进行处理
# 会进入self==> person类中__init__然后在回来
obj.__dict__ = {f"_{self.__name__}__{k}": v for k, v in obj.__dict__.items()}
print("123456", obj.__dict__)
print("__call__ ", obj.__dict__)
return obj
class Person(object, metaclass=Mymeta):
def __init__(self, name):
print("___init__ 第二")
self.name = name
def score(self):
print('分数是100')
p = Person(name='randy')
print(p.__dict__)
print(p._Person__name)
p = Person(name='randy')
print(p.__dict__)
print(p._Person__name)
__call__ 第一
___init__ 第二
123456 {'_Person__name': 'randy'}
__call__ {'_Person__name': 'randy'}
{'_Person__name': 'randy'}
randy
十、自定义元类后继承顺序
结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???
在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object
class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n = 444
def __call__(self, *args,
**kwargs): #self=<class '__main__.OldboyTeacher'>
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj
class Bar(object):
n = 333
class Foo(Bar):
n = 222
class OldboyTeacher(Foo, metaclass=Mymeta):
n = 111
school = 'oldboy'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('%s says welcome to the oldboy to learn Python' % self.name)
print(
OldboyTeacher.n
) # 自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type
111
print(OldboyTeacher.n)
111
- 查找顺序:
- 先对象层:OldoyTeacher->Foo->Bar->object
- 然后元类层:Mymeta->type
依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__的查找
总结:
- 类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错
- 对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错
10.1 练习
class Mymeta(type):
def __init__(self, class_name, class_bases, class_dic):
# 加上逻辑,控制类Foo的创建
super(Mymeta, self).__init__(class_name, class_bases, class_dic)
def __call__(self, *args, **kwargs):
# 加上逻辑,控制Foo的调用过程,即Foo对象的产生过程
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
# 修改属性为隐藏属性
obj.__dict__ = {
'_%s__%s' % (self.__name__, k): v
for k, v in obj.__dict__.items()
}
return obj
class Foo(object, metaclass=Mymeta): # Foo = Mymeta(...)
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
obj = Foo('randy', 18, 'male')
print(obj.__dict__)
十一、总结
继承元类,首先会先进入元类中的__init__在返回来执行自己的
控制类产生模板:
# 控制类产生模板
class Mymeta(type):
def __init__(self, name, base, dic):
if self.name == Person:
raise Exception("名称错误")
class Person(metaclass=Mymeta):
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('laowang', 19)
控制对象的产生:
class Mymeta(type):
def __call__(self, *args, **kwargs):
# 第一步产生空对象
obj=object.__new__(self)
# 第二部初始化空对象,把初始值放到对象中
obj.__init__(*args, **kwargs)
# 第三步返回对象
return obj
class Person(metaclass=Mymeta):
def __init__(self,name):
self.name=name
def __call__(self, *args, **kwargs):
print('xxx')
p=Person('randy')
对象查找顺序:
类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错
对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错