Python魔术方法——反射
init返回值一般不写return,要写只能写return,或者是return None。
概述
运行时,区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义信息。
一个对象能够在运行时,像照镜子一样,反射出其类型信息。
简单说,在Python中,能够通过一个对象,找出其type,class,attribute或method的能力,称为反射或者自省。
具有反射能力的函数有:type(),isinstance(),callabe(),dir(),getattr()
反射相关的函数和方法
需求
有一个Point类,查看它实例的属性,并修改它。动态为实例增加属性。
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(dir(p))#ordered list print(p.__dir__())#list 结果为: Point(4,5) {'x': 4, 'y': 5} {'x': 4, 'y': 16} {'x': 4, 'y': 16, 'z': 10} ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'show', 'x', 'y', 'z'] ['x', 'y', 'z', '__module__', '__init__', '__str__', 'show', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
上例通过属性字典__dict__来访问对象的属性,本质上也是利用的反射的能力。
但是,上面的例子中,访问的方式不优雅,Python提供了内置的函数。
getattr(object,name,[default]):通过name返回object的属性值。当属性不存在,将返回default返回,如果没有default,则抛出attributeerror错误,name必须为字符串。
setattr(object,name,vaule):object的属性存在,则覆盖,不存在,新增。
hasattr(object,name):判断对象是否有这个名字的属性,name必须为字符串。
class A: def __init__(self): self.x = 5 a = A() setattr(A,"y",10) print(A.__dict__) print(a.__dict__) print(getattr(a,"x")) print(getattr(a,"y")) if hasattr(a,"z"): print(getattr(a,"z")) setattr(a,"y",1000) print(A.__dict__) print(a.__dict__) 结果为: {'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3D90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10} {'x': 5} 5 10 {'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3D90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10} {'x': 5, 'y': 1000}
用上面的方法来修改上例的代码
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) p1 = Point(4,5) p2 = Point(10,10) print(repr(p1),repr(p2),sep="\n") print(p1.__dict__) setattr(p1,"y",16) setattr(p1,"z",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) #add在谁里面,sub在谁里面 print(p1.__dict__) print(Point.__dict__) 结果为: <__main__.Point object at 0x0000000005A98940> <__main__.Point object at 0x0000000005A98898> {'x': 4, 'y': 5} {'x': 4, 'y': 16, 'z': 10} point(4,16) <function <lambda> at 0x0000000005A9C400> <bound method <lambda> of <__main__.Point object at 0x0000000005A98940>> point(14,26) point(0,0) <function <lambda> at 0x0000000005A6E598> {'x': 4, 'y': 16, 'z': 10, 'sub': <function <lambda> at 0x0000000005A6E598>} {'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000000005A9CBF8>, '__str__': <function Point.__str__ at 0x0000000005A9CAE8>, 'show': <function Point.show at 0x0000000005A9CA60>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None, 'add': <function <lambda> at 0x0000000005A9C400>}
思考,这种动态增加属性的方式和装饰器修饰一个类,mixin方式的差异?
这种动态增删属性的方式是运行时改变或者实例的方式,但是装饰器或mixin都是定义时就决定了,因此反射能力具有更大的灵活性。
练习:命令分发器,通过名称找对应的函数执行。
思路:名称找对象的方法。
class dispatcher(): def cmd1(self): print("cmd1") def run(self): while True: cmd = input("please input command: ") if cmd.strip() =="quit": return getattr(self,cmd.strip(),self.defaultfn)() def defaultfn(self): print("default") dis = dispatcher() dis.run() 结果为: please input command: cmd1 cmd1 please input command: a default please input command: cdm default please input command: quit
下面的是可以实现自己注册的。
class dispatcher(): def cmd1(self): print("cmd1") def reg(self,cmd,fn): if isinstance(cmd,str): setattr(self.__class__,cmd,fn) else: print("error") def run(self): while True: cmd = input("please input command: ") if cmd.strip() =="quit": return getattr(self,cmd.strip(),self.defaultfn)() def defaultfn(self): print("default") dis = dispatcher() dis.reg("cmd2",lambda self:print(2)) dis.reg("cmd3",lambda self:print(3)) dis.run() 结果为: please input command: cmd1 cmd1 please input command: cmd3 3 please input command: cmd2 2 please input command: cd2 default please input command: quit
class Dispatcher: def __init__(self): self._run() def cmd1(self): print("i'm cmd1") def cmd2(self): print("i'm cmd2") def _run(self): while True: cmd = input("plz imput a command:").strip() if cmd =="quit": break getattr(self,cmd,lambda:print("unknown command {}".format(cmd)))() Dispatcher() 结果为: plz imput a command:1 unknown command 1 plz imput a command:3 unknown command 3 plz imput a command:quit <__main__.Dispatcher at 0x5825940>
上例中使用getattr方法找到对象的属性的方式,比自己维护一个字典来建立名称和函数之间的关系的的方式好多了。
class A: def __init__(self): self.x = 5 a = A() setattr(A,"y",10) print(A.__dict__) print(a.__dict__) print(getattr(a,"x")) print(getattr(a,"y")) if hasattr(a,"z"): print(getattr(a,"z")) setattr(a,"y",1000) print(A.__dict__) print(a.__dict__) setattr(a,"mtd",lambda self:1) print(A.__dict__) print(a.__dict__) 结果为: {'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3C80>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10} {'x': 5} 5 10 {'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3C80>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10} {'x': 5, 'y': 1000} {'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3C80>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10} {'x': 5, 'y': 1000, 'mtd': <function <lambda> at 0x00000000059E4400>}
上例中的方法添加到了实例的属性中。但是调用的时候会出错,要不出错。
class A: def __init__(self): self.x = 5 a = A() setattr(A,"y",10) print(A.__dict__) print(a.__dict__) print(getattr(a,"x")) print(getattr(a,"y")) if hasattr(a,"z"): print(getattr(a,"z")) setattr(a,"y",1000) print(A.__dict__) print(a.__dict__) setattr(A,"mtd",lambda self:1) print(A.__dict__) print(a.__dict__) print(a.mtd()) 结果为: {'__module__': '__main__', '__init__': <function A.__init__ at 0x0000000005C3DD90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10} {'x': 5} 5 10 {'__module__': '__main__', '__init__': <function A.__init__ at 0x0000000005C3DD90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10} {'x': 5, 'y': 1000} {'__module__': '__main__', '__init__': <function A.__init__ at 0x0000000005C3DD90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10, 'mtd': <function <lambda> at 0x0000000005C3D6A8>} {'x': 5, 'y': 1000} 1
反射相关的魔术方法
__getattr__(),__setattr__(),__delattr__()这三个魔术方法,分别测试。
__getattr__()方法
class Base: n = 0 class Point(Base): z= 6 def __init__(self,x,y): self.x = x self.y = y def show(self): print(self.x,self.y) def __getattr__(self,item): return "missing {}".format(item) p1 = Point(4,5) print(p1.x) print(p1.z) print(p1.n) print(p1.t) 结果为:
4 6 0 missing t
一个类的属性会按照继承关系找,如果找不到,就会执行__getattr__方法,如果没有这个方法。就会抛出attributeerror异常,表示找不到属性。
查找属性顺序为:
__setattr__()方法
class Base: n = 0 class Point(Base): z= 6 def __init__(self,x,y): self.x = x self.y = y def show(self): print(self.x,self.y) def __getattr__(self,item): return "missing {}".format(item) def __setattr__(self,key,value): print("setattr {} = {}".format(key,value)) p1 = Point(4,5) print(p1.x)#missing ,why print(p1.z) print(p1.n) print(p1.t)#missing p1.x = 50 print(p1.__dict__) p1.__dict__["x"] = 60 print(p1.__dict__) print(p1.x) 结果为:
setattr x = 4 setattr y = 5 missing x 6 0 missing t setattr x = 50 {} {'x': 60} 60
实例通过.点设置属性,如同self.x = x,就会调用__setattr__(),属性要加到实例的__dict__中,就需要自己完成。
class Base: n = 5 class A(Base): m = 6 def __init__(self,x): self.x = x def __setattr__(self,key,value): print("__setattr__",key,value) a = A(10) a.x = 100 结果为: __setattr__ x 10 __setattr__ x 100
class Base: n = 5 class A(Base): m = 6 def __init__(self,x): self.x = x def __setattr__(self,key,value): print("__setattr__",key,value) def __getattr__(self,item): print("__getattr__",item) a = A(10) a.x = 100 a.y = 200 print(a.__dict__) a.y 结果为: __setattr__ x 10 __setattr__ x 100 __setattr__ y 200 {} __getattr__ y
为什么后面的a.y没有访问到。
class Point(Base): z= 6 def __init__(self,x,y): self.x = x self.y = y def show(self): print(self.x,self.y) def __getattr__(self,item): return "missing {}".format(item) def __setattr__(self,key,value): print("setattr {} = {}".format(key,value)) self.__dict__[key] = value p1 = Point(4,5) print(p1.x)#missing ,why print(p1.z) print(p1.n) print(p1.t)#missing p1.x = 50 print(p1.__dict__) p1.__dict__["x"] = 60 print(p1.__dict__) print(p1.x) 结果为: setattr x = 4 setattr y = 5 4 6 0 missing t setattr x = 50 {'x': 50, 'y': 5} {'x': 60, 'y': 5} 60
__setattr__()方法,可以拦截对实例属性的增加,修改操作,如果要设置生效,需要自己操作实例的__dict__.
__delattr__()
class Point(Base): z= 5 def __init__(self,x,y): self.x = x self.y = y def __delattr__(self,item): print("can not del {}".format(item)) p = Point(4,5) del p.x p.z =15 del p.z del p.z print(Point.__dict__) print(p.__dict__) del Point.z print(Point.__dict__) 结果为: can not del x can not del z can not del z {'__module__': '__main__', 'z': 5, '__init__': <function Point.__init__ at 0x0000000005F3BD90>, '__delattr__': <function Point.__delattr__ at 0x0000000005F3BE18>, '__doc__': None} {'x': 4, 'y': 5, 'z': 15} {'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000000005F3BD90>, '__delattr__': <function Point.__delattr__ at 0x0000000005F3BE18>, '__doc__': None}
可以阻止通过实例删除属性的操作。但是通过类依然可以删除属性。
__getattribute__
class Base: n = 0 class Point(Base): z= 6 def __init__(self,x,y): self.x = x self.y = y def __getattr__(self,item): return "missing {}".format(item) def __getattribute__(self,item): return item p1 = Point(4,5) print(p1.__dict__) print(p1.x) print(p1.z) print(p1.n) print(p1.t) print(Point.__dict__) print(Point.z) 结果为: __dict__ x z n t {'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x0000000005C3D620>, '__getattr__': <function Point.__getattr__ at 0x0000000005C3DEA0>, '__getattribute__': <function Point.__getattribute__ at 0x0000000005C3D510>, '__doc__': None} 6
实例的所有的属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个attributeerror异常。
它的return值将作为属性查找的结果,如果抛出attributeerrror异常,则会直接调用__getattr__方法,因为表示属性没有找到。
class Base: n = 0 class Point(Base): z= 6 def __init__(self,x,y): self.x = x self.y = y def __getattr__(self,item): return "missing {}".format(item) def __getattribute__(self,item): return object.__getattribute__(self,item) p1 = Point(4,5) print(p1.__dict__) print(p1.x) print(p1.z) print(p1.n) print(p1.t) print(Point.__dict__) print(Point.z) 结果为: {'x': 4, 'y': 5} 4 6 0 missing t {'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x0000000005C3D400>, '__getattr__': <function Point.__getattr__ at 0x0000000005C3DE18>, '__getattribute__': <function Point.__getattribute__ at 0x0000000005C3DC80>, '__doc__': None} 6
__getattribute__方法中为了避免在该方法中无限的递归,它的实现永远调用基类的同名方法以访问需要的任何属性,例如object.__getattribute__(self,name)
总结: