反射
概述
运行时,区别于编译时,指的是程序被加载到内存中执行时
反射,reflection,指的是运行时获取类型定义信息
大部分动态语言提供了反射
一个对象运行时,像照镜子一样,反射出类型信息
自省也是反射的一种称呼
在Python中能够通过一个对象,找出其 type,class ,attrbute或method的能力,称为反射或者自省。
有反射能力的函数有:
type() 返回类,相当于.__class__
isinstance(对象,类型) 返回bool值,判断类型,类型可以是多个类型包起来的元组,判断其中是否有对象的类型
callable() 看一个对象是否为可调用对象,调__call__方法
dir() 返回类的或者对象的所有成员的列表,调用__dir__方法
class Point: def __init__(self,x,y): self.x = x self.y = y def __str__(self): return "Point({},{})".format(self.x, self.y) def show(self): print(self.x, self.y) p = Point(4, 5) print(p) print(p.__dict__) p.__dict__['y'] = 16 ###不推荐,没事少改字典, print(p.__dict__) p.z = 10 print(p.__dict__) print(sorted(dir(p))) print(sorted(p.__dir__()))
属性字典__dict__来访问对象的属性,本质上也是利用反射的能力,但这种方式十分不优雅
class Point: def __init__(self,x,y): self.x = x self.y = y def __str__(self): return "Point({},{})".format(self.x,self.y) def show(self): print(self.x,self.y) p1 = Point(4,5) p2 = Point(10,10) print(getattr(p1,'__dict__')) ##动态方法调用 if hasattr(p1,'show'): getattr(p1,'show')() ##动态增加方法,为类增添方法 if not hasattr(Point,'add'): setattr(Point,'add',lambda self,other:Point(self.x+other.x,self.y+other.y)) print(Point.add) print(p1.add) print(p1.add(p2))##绑定 ##动态赠添方法,为实例增添方法,未绑定 if not hasattr(p1,'sub'): setattr(p1,'sub',lambda self, other: Point(self.x - other.x, self.y - other.y)) print(p1.sub(p1,p1)) print(p1.sub)
getattr(object, name[, default=None]) 通过name返回object的属性值。当属性不存在,使用default返回,如果没有default就抛出AttributeError,name要求必须是字符串。
setattr(object,name,value) object属性存在,则覆盖,不存在,新增
hasattr(object,name) 返回bool,判断对象是否有这个名字的属性,name必须为字符串
delattr 删除
可以绑定在对象和类上
可以对任何对象直接增加属性。使用内建函数或者 对象.属性访问
装饰器和Mixin都是定义时就修改了类的属性,而setattr能在运行时修改属性,或者删除属性。
反射有更大的灵活性
命令分发器:通过名称找对应函数执行
class Dispatcher: def __init__(self): pass def reg(self,name,fn): setattr(self,name,fn) def run(self): while True: cmd = input(">>>>").strip() if cmd == 'quit': break getattr(self, cmd, lambda :print('command not found'))() dis=Dispatcher() dis.reg('ls',lambda :print('ls function')) dis.run()
使用getattr方法找对象的属性的方式,比自己维护一个字典来建立名称和函数之间的关系的方式好多了
反射的相关魔术方法
__getattr__ 实例查找属性的顺序,会按照自己的字典,类的字典,继承的祖先类的字典,直到object的字典,找不到就找__getattr__的方法,没有就抛AttributeError
getattr是查找属性没找到后的最后一道防线,然后就是抛异常
__setattr__ 实例通过.点号设置,例如self.x= x ,就会调用__setattr__,属性要加到实例的__dict__中,就需要自己完成。该方法可以拦截实例属性的增加,修改操作,如果要设置生效,就需要自己操作实例的__dict__。
class Point: s = 100 d = {} def __init__(self,x,y): self.x = x##调setattr self.y = y self.__dict__['a'] = 5##不调setattr def __getattr__(self, item): return self.d[item] def __setattr__(self, key, value): print(key) print(value) self.d[key] = value##将属性存放到类的属性中,此处可以改成任何存储位置 def __delattr__(self, item): print('cannot del {}'.format(item)) p1 = Point(3,2)##调用setattr,添加属性 print(p1.__dict__)##字典里有a的属性 print(Point.__dict__) print(p1.x,p1.y)##调用getattr print(p1.a)##在实例的字典找到了,不去往后找了
__delattr__ 可以阻止通过实例删除属性的操作,但通过类依然可以删除属性。
class Point: z = 100 def __init__(self,x,y): self.x = x self.y = y def __delattr__(self, item): print('cannot del {}'.format(item)) p1 = Point(3,2) del p1.x p1.z = 15 del p1.z print(Point.__dict__) print(p1.__dict__) del Point.z ##通过类删除属性,类的属性删除成功 print(Point.__dict__)
__getattribute__ 实例的所有属性访问,都会先调用__getattribute__方法,它阻止了属性的查找,它将返回值,或者抛异常,AttributeError
它的返回值就是属性查找的结果,
如果抛出异常,就会直接调用__getattr__方法,因为表示属性没找到。
class B: n =0 class Point(B): z = 100 def __init__(self,x,y): self.x = x self.y = y def __getattr__(self, item): return 'misssing {}'.format(item) def __getattribute__(self, item): return item ###给啥返回啥 p1 = Point(3,2) print(p1.__dict__)##返回__dict__ print(p1.x) print(p1.z) print(p1.n) print(p1.t)##对象访问属性,都去调__getattribute__ print(Point.__dict__)##返回类字典, print(Point.z)##类访问属性
__getattr__方法中为了避免该方法中无限递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如object.__getattr__(self,name)
class B: n =0 class Point(B): z = 100 def __init__(self,x,y): self.x = x self.y = y def __getattr__(self, item): return 'misssing {}'.format(item) def __getattribute__(self, item): #raise AttributeError ##抛出异常 ##pass ##只有这句返回None,返回值就是None ##return self.__dict__[item] ##递归调用 return object.__getattribute__(self,item) ##到object找__getattribute__方法 p1 = Point(3,2) print(p1.__dict__)##返回__dict__ print(p1.x) print(p1.z) print(p1.n) print(p1.t) print(Point.__dict__) print(Point.z)
总结:
__getattr__() 当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法
__setattr__() 通过访问实例的属性,进行增加修改,都会调用它
__delattr__() 当通过实例来删除属性时调用此方法
__getattribute__() 实例的所有属性调用都从这个方法开始