面向对象之元类
什么是元类
一切源自于一句话:python中一切皆为对象,既然如此,类是不是也是对象呢?
class Teacher(object): school = "tsinghua" def __init__(self,name,age): self.name = name self.age = age def say(self): print("%s says welcome to the Beijing" % self.name) t1 = Teacher("Trish",18) print(type(t1)) # 查看对象t1的类是<class '__main__.Teacher'>
所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象t1是调用类Teacher得到的
一切皆对象的话,类也必然是一个对象,验证一下
tcls = Teacher li = [Teacher] def func(cls): print(cls) func(Teacher) #完全没问题把他当做对象来使用,和其他对象没有任何区别
思考,t1是通过Teacher实例化得到的,那Teacher对象是哪个类实例化的呢?
print(type(Teacher)) #<class 'type'>
可以推导出===>产生Teacher的过程一定发生了:Teacher = type(...)

用于实例化产生类的类称之为元类,就是此时的type类;
Teacher是通过type实例化得到的,既然如此,是不是可以自己调用type来实例化一个class呢?
创建类的流程分析
class关键字在于帮我们创建类时,必然帮我们调用了元类Teacher=type(...),那调用type时传入的参数是什么呢?
必然是类的关键组成部分,一个类有三大组成部分,分别是
1.类名class_name = "Teacher"
2.基类们class_bases = (object,)
3.类的名称空间class_dic,类的名称空间是执行类体代码而得到的
调用type时会依次传入以上三个参数
自己实例化一个类
class_name = "Teacher' class_body = """ def __init__(self,name,age): self.name = name self.age = age def say(self): print('%s says welcome to the Beijing' % self.name) """ class_dict = exce(class_body) bases = (object,) Teacher = type(class_name,class_body,bases)
综上,class关键字帮我们创建一个类应该细分为以下四个过程
1.获取类名
2.获取基类
3.获取名称空间
4.实例化元类得到类
自定义元类控制类的创建
一个类没有声明自己的元类,默认他的元类就是type,出了使用内置元类type,我们也可以通过集成
type来自定义元类,然后使用metaclass关键字参数为一个类指定元类
class Mymeta(type):
pass
class Teacher(object,metaclass = Mymeta): # Teacher = Mymeta('Teacher',(object),{...})
school = 'tsinghua'
def __init__(self,name,age):
self.name = name
self.age = age
def say(self):
print('%s says welcome to the Beijing' %self.name)
需求
1.规范类名必须大写
2.类中必须包含文档注释
class MyMate(type):
def __init__(self,name,bases,dic):
print("run")
if not dic.get("__doc__"):
raise TypeError("类必须有文档注释!")
if not name.istitle():
raise TypeError("类名必须大写开头!")
super().__init__(name,bases,dic)
class Foo(object,metaclass = MyMate):
pass
项目中的应用
在优酷系统中,需要根据类的信息来生成创建表的语句,必须知道类何时被创建了,使用元类可以轻松的
拦截类的创建过程,获取类相关信息来生成建表语句
class MyMetaClass(type):
def __init__(self,name,bases,dic):
table_name = name
columns = self.transfer_columns(dic)
sql = "create table if not exists %s(%s)" %(table_name,columns)
#自动建表
try:
OBDB().conn.execute(sql)
except Exception as e:
pass
super().__init__(name,bases,dic)
自定义元类控制类的调用
__call__ 函数得执行时机
该方法会在调用对象时自动触发执行(对象加括号)
class Foo: def __call__(self,*args,**kwargs): print("run") f = Foo() # 调用Foo得到f对象 f() # 调用对象时,触发__call__的执行
通常调用一个普通对象是没有意义的,那__call__在什么时候用呢?
我们说类也是一个对象,那么Foo()是不是也执行了Foo的类中的__call__函数呢?
Foo的类是谁呢?默认是元类type,通过mateclass来指定为自定义的元类来测试
# 测试 class M(type): def __call__(self,*args,**kwargs):
print("run mateclass __call__")
pass
class A(metaclass = M):
pass
print(A())
# 输出 run mateclass __call__
# 输出 None
覆盖__call__函数时的注意事项
第一行输出表明了,调用类A时,的确自动执行了__call__函数
第二行输出一个空,这是为什么呢?将__call__注释起来,再次测试,会发现打印结果变成了一个对象!
必须明确创建对象的过程:先创建空对象,执行初始化将属性存储到对象的名称空间中!
所以在__call__函数中必须完成这两步操作,同时将初始化完全的对象返回给调用者
一旦覆盖了__call__函数,就必须自己来完成上述的几个步骤
class MyMate(type): def __call__(self,*args,**kwargs): #创建空对象 #调用init #返回初始化后的对象 obj = object.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Foo(metaclass = MyMate) def __init__(self): print("初始化对象") f = Foo() print(f)
通过元类来控制一个类实例化对象的过程
只需覆盖__call__函数我们就能完成对实例化过程的控制
# 需求 # 要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument # key作为用户自定义类产生对象的属性,且所有属性变成大写
class Mymetaclass(type):
def __call__(self,*args,**kwargs):
if args:
raise TypeError("must use keyword argument for key function")
obj = object.__new__(self) # 创建对象,self为类Chinese
for k,v in kwargs.items():
obj.__dict__[k.upper()] = v
return obj
class Chinese(metaclass = Mymetaclass):
country = 'China'
tag = 'Devil May Cry' # 鬼泣
def walk(self):
print("%s is walking" %self.name)
p = Chinese(name = "egon",age = 18,sex = "male")
print(p.__dict__)
补充:
产生类Teacher的过程就是在调用Mymeta,而Mymeta也是type类的一个对象,那么Mymeta之所以可以调用,
一定是在元类type中有一个__call__方法
该方法中同样需要做至少三件事
#伪代码 class type: def __call__(self, *args, **kwargs): #self=<class '__main__.Mymeta'> obj=self.__new__(self,*args,**kwargs) # 产生Mymeta的一个对象 self.__init__(obj,*args,**kwargs) return obj
元类实现单例
什么是单例
单例指的是单个实例,指一个类只能有一个实例对象
为什么要用单例
当一个类的实例中的数据不会变化时使用单例,数据是不变的
例如开发一个音乐播放器程序,音乐播放器可以封装为一个对象,那你考虑一下,当你切歌的时候,是重新创建
一个播放器,还是使用已有的播放器?
因为播放器中的数据和业务逻辑都是相同的没有必要创建新的,所以最好使用单例模式,以节省资源
当两个对象的数据完全相同时,则没有必要占用两份资源
#使用classmethod 实现单例 class Player(): def __init__(self): print("创建播放器了") __play = None @classmethod def get_player(cls): if not cls.__play: cls.__play = Player() return cls.__play p1 = Player.get_player(); p1 = Player.get_player(); p1 = Player.get_player(); p1 = Player.get_player();
该方式无法避免使用者直接调用类来实例化,这样就不是单例了
使用元类实现单例模式
#在类定义时 自动执行init 在init中创建实例 call中直接返回已有实例 class MyMeta(type): __instance = None def __init__(self,name,bases,dic): if not self.__instance: self.__instance = object.__new__(self) self.__init__(self.__instance) super().__init__(name, bases, dic) def __call__(cls): return cls.__instance class Player(metaclass=MyMeta): def __init__(self): print("创建播放器了") Player() Player() # 仅执行一次创建播放器
元类之属性查找
当一个类既有父类又有元类时,属性的查找顺序是什么样的?
回顾一下,在没有元类时,属性的查找是基于MRO列表的顺序,这个点还是相同的,那我们为某个类增加元类后,元类中的属性
,什么时候会被使用呢?来看一个例子
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 n=444 def __new__(cls, *args, **kwargs): pass class Bar(object): n = 333 def __new__(cls, *args, **kwargs): pass class Foo(Bar): n=222 def __new__(cls, *args, **kwargs): pass class Teacher(Foo,metaclass=Mymeta): n=111 def __new__(cls, *args, **kwargs): pass school='Tsinghua' print(Teacher.__new__) print(Teacher.n)
测试结果表明:

令人迷惑的__new__函数与__init__函数
class M(type):
def __init__(self,clsname,bases,namespace):
print("init")
def __call__(self, *args, **kwargs):
pass
pass
class A(metaclass=M):
n = 1
pass
print(A.__name__)
print(A.__bases__)
print(A.__dict__)
"""输出
init
A
(<class 'object'>,)
{'__module__': '__main__', 'n': 1, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
"""
我们已经知道__init__可以控制类的创建过程,但是现在我们看到的是,init中没有任何代码但是类的三个基本信息已经都有了,这说明类的创建其实已经完成了
class M(type):
def __new__(cls, *args, **kwargs):
print("new")
#return type.__new__(cls,*args,**kwargs)
def __init__(self,clsname,bases,namespace):
print("init")
class A(metaclass=M):
n = 1
print(A.__name__)
print(A.__bases__)
print(A.__dict__)
"""输出
new
Traceback (most recent call last):
File "/Users/jerry/PycharmProjects/元类属性查找.py", line 43, in <module>
print(A.__name__)
AttributeError: 'NoneType' object has no attribute '__name__'"""
执行了__new__函数但是并没有执行__init__,因为__new__函数是真正用于创建类的方法,只有创建类成功了才会执行init函数,new必须要有返回值且返回值类型为__type__时才会执行__init__函数,
将__new__中被注释的代码打开 一切正常! 再一次印证了第四节中的伪代码
总结:元类中__new__是用于创建类对象的 __init__是用于初始化类的其他信息的


浙公网安备 33010602011771号