面向对象之反射、元类
反射reflect
什么是反射:反射就是反省、自省,在程序中,它指的是是程序可以访问检测和修改它本身的能力。
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
反射中涉及的四个函数,hasattr、getattr、setattr、delattr(这四个函数就是普通的内置函数,没有双下划线)
class Person: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender p = Person('jack',18,'male') #hasattr 判断某个对象是否存在某个属性 print(hasattr(p,'name')) # 判断p中是否存在name的属性 #括号中的参数,第一个是对象,第二个是属性名 #getattr 从对象中取出某个属性,如果没有返回默认值 print(getattr(p,'age',None)) # 从对象中取出age的属性,如果没有就返回None #括号中的参数,第一个是对象,第二个是属性名,第三个是返回的默认值 #setattr 为对象添加新的属性 setattr(p,'id','222') # 为对象添加新的属性“id” ,值为“222” print(p.id) #deltattr 从对象中删除属性 delattr(p,"id") # 将id从对象中删除 print(p.id)
使用场景:首先我们可以看出,反射其实就是对属性的增删改查,我们通过内置的dict来操作也可以实现,但是语法比较繁琐,另一方面,如果对象不是我自己写的而是由别人提供的,那我就需要判断这个对象是否能满足要求,也就是需要我的属性和方法。
反射为什么被称为框架的基石,因为框架的设计者不可能提前知道你的对象到底是怎么设计的,所以你要提供给框架的对象必须通过判断验证之后才能正常使用,而判断验证就是反射要做的事情。
########################
#框架代码,可插拔设计的实现#
########################
import importlib import settings # 框架已经实现的部分 def run(plugin): while True: cmd = input("请输入指令:") if cmd == "exit": break # 因为无法确定框架使用者是否传入正确的对象所以需要使用反射来检测 # 判断对象是否具备处理这个指令的方法 if hasattr(plugin,cmd): # 取出对应方法方法 func = getattr(plugin,cmd) func() # 执行方法处理指令 else: print("该指令不受支持...") print("see you la la!") # 创建一个插件对象 调用框架来使用它 # wincmd = plugins.WinCMD() # 框架之外的部分就有自定义对象来完成 # 框架 得根据配置文件拿到需要的类 path = settings.CLASS_PATH # 从配置中单独拿出来 模块路径和 类名称 module_path,class_name = path.rsplit(".",1) #拿到模块 mk = importlib.import_module(module_path) # 拿到类 cls = getattr(mk,class_name) # 实例化对象 obj = cls() #调用框架 run(obj) ### 作为框架使用者 在配置文件中指定你配合框架的类是哪个 CLASS_PATH = "libs.plugins.WinCMD" # 以后只需要改配置文件就可以了
class Person(object): def __init__(self,name): self.name = name t1 = Person('egon') print(type(t1)) #查看对象t1的类是<class '__main__.OldboyTeacher'> #我们知道所有对象都是实例化得到的,像这里t1就是调用Person得到的。 #如果按照万物皆对象的话,那么Person的本质是不是也是一个对象?既然所有对象都是调用类得到的,那么Person必然也是调用了一个类得到的,而这个类被我们称作元类。 print(type(Person)) # 结果为<class 'type'>,证明是调用了type这个元类而产生的Person,即默认的元类为type
class关键字创建类的流程分析
一个类有三大组成部分:
1、类的名字:class_name = ’Person'(字符串类型)
2、类的父类或者基类们:class_bases = (object)(是一个元组或者列表)
3、类的名称空间:class_dic (字典类型)
#了解了之后,我们来自己创建一个类 cls_obj = type('dog',(),{}) print(cls_obj) >>>:<class '__main__.dog'> #我们再用class的方法来创建一个类看看一不一样 class Cat: pass print(Cat) >>>:<class '__main__.Cat'> #结果是一样的
自己来定义元类
当我们的需求是在创建类对象的时候做一些限制时我们该怎么办?首先我们可以分析一下,因为刚刚我们已经了解了类也是一个对象,创建类的过程也就是实例化对象的过程,那么想要在创建时做出限制,我们该怎么办?没错,首先我们会想到初始化方法,那么我们如果找到类对象的类,也就是元类,覆盖其中__init__的方法是不是就可以了实现了,是的,但是我们当然不应该去随便的修改源码,所以我们就应该通过继承type的方式来编写自己的元类,这样就可以覆盖init来完成需求。
#首先我们要知道,只要继承了type,那么这个类就是元类 #接下来我们来定义一个类,让类的名字必须是以大驼峰的方式来书写 class MyType(type): def __init__(self,class_name,bases,dict): super().__init__(class_name,bases,dict) print(class_name,bases,dict) if not class_name.istitle(): raise Exception('我的天,你连类名都不会写?') #我们来实验一下 class dog(metaclass=MyType): # metaclass等于的是你为它指定的元类 pass # 结果报错 class Cat(metaclass=MyType): pass #Cat () {'__module__': '__main__', '__qualname__': 'Cat'}
元类中call方法
在之前的学习中我们知道了,调用一个对象(就是在对象后面加括号),就会触发对象所在类中的__call__方法的执行。
对于调用类对象也一样,当你在调用类对象时会自动执行元类中的__call__方法,并将这个类本身作为第一个参数传入。
注意:一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象。
class Mymeta(type): def __init__(self,name,bases,dict): super().__init__(name,bases,dict) print('A init run') def __call__(self,*args,**kwargs): print('元类 call run') return super().__call__(*args,**kwargs) # 返回这个对象 class Dog(metaclass=Mymeta): def __init__(self,name): self.name = name def __call__(self,*args,**kwargs): # 没有产生效果 print('类对象 call run') d = Dog('大黄') print(d)
那么call该如何使用。当你想要控制对象创建过程是,就覆盖call方法。
#来进行call方法的使用 #实现将对象所有的属性名转化为大写 class Mytype(type): def __call__(self,*args,**kwargs): new_args = [] for a in args: new_args.append(a.upper()) print(new_args) print(kwargs) return super().__call__(*new_args,**kwargs) class Person(metaclass=Mytype): def __init__(self,name,gender): self.name = name self.gender = gender p = Person('jack','woman') print(p.name) print(p.gender)
补充new方法
创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,然后会自动调用__init__来对这个类进行初始化操作。
注意:如果你覆盖了该方法,则必须保证new方法必须有返回值且必须是对应的类对象。
class Meta(type): def __new__(cls,*args,**kwargs): # print(cls) # 元类自己 # print(*args) # 创建类需要的几个参数,类名,基类,名称空间 # print(**kwargs) # 空的 print('new run') obj = type.__new__(cls,*args,**kwargs) return obj # def __init__(self,a,b,c): # super().__init__(a,b,c) # print('init run') class A(metaclass=Meta): pass print(A)
总结new方法和init 都可以实现控制类的创建过程,init更简单。
设计模式?用于解决某种固定问题的套路
例如:MVCMTV等
单例:指的是一个类产生一个对象
为什么要使用单例:单例是为了节省 资源,当一个类的所有对象属性全部相同时,则没有必要创建多个对象
class Single(type): def __call__(self,*args,**kwargs): if hasattr(self,'obj'): #判断是否存在已经有的对象 return getattr(self,'obj') # 有就返回 obj = super().__call__(*args,**kwargs) # 没有则创建 print('new 了') self.obj = obj # 并存入类中 return obj class Student(metaclass=Single): def __init__(self,name): self.name = name class Person(metaclass=Single): pass #只创建一个对象 Person() Person()
总结:用元类来实例化一个类对象,在定义时会先执行__new__,然后执行__init__,但是在调用时会先执行元类中的__call__,然后在执行__new__以及__init__(如果自己没有就找元类的)。