Python-面向对象魔术方法之反射
反射
概述
运行时,runtime,区别在于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义信息。
一个对象能够在运行时像镜子一样反射出其类型信息。
简单说,在Python中,能够通过一个对象,找到其type、class、attribute或method的能力,称之为反射或自省
具有反射能力的函数有type()、isinstance()、called()、dir()、getatter()等
反射相关的函数和方法
需求,有一个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'] = 10 print(p.__dict__) p.z = 20 print(p.__dict__) print(dir(p)) print(p.__dir__()) print(Point.__dict__) #输出 <Point 4,5> {'x': 4, 'y': 5} {'x': 4, 'y': 10} {'x': 4, 'y': 10, 'z': 20} ['__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__'] {'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000021625775AE8>, '__str__': <function Point.__str__ at 0x0000021625775B70>, 'show': <function Point.show at 0x000002162579AD08>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
上例通过属性字典__dict__来访问对象的属性,本质上也是利用反射的能力。
但是,上面的例子中,访问的方式不优雅,Python提供了内置的函数。
| 内建函数 | 意义 |
| getattr(object,name[,default] | 通过name返回object的属性值,当属性不存在,将使用default返回,如果没有default,则抛出AttributeError,name必须是字符串 |
| setattr(object,name,value) | object的属性存在,则覆盖,不存在则新增 |
| hasattr(object,name) | 判断对象是否有这个名字的属性,name必须为字符串 |
用上面的方法修改上列中的代码
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,20) print(repr(p1),repr(p2)) print(p1.__dict__) setattr(p1,'y',16) setattr(p1,'z',100) print(p1.__dict__) 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) print(p1.sub(p1,p2)) print(p1.__dict__) print(Point.__dict__) #输出 <__main__.Point object at 0x00000172971D82E8> <__main__.Point object at 0x00000172971BD2E8> {'x': 4, 'y': 5} {'x': 4, 'y': 16, 'z': 100} {'x': 4, 'y': 16, 'z': 100} <Point 4,16> <function <lambda> at 0x00000172971A5950> <bound method <lambda> of <__main__.Point object at 0x00000172971D82E8>> <Point 14,36> <function <lambda> at 0x00000172971CB6A8> <Point -6,-4> {'x': 4, 'y': 16, 'z': 100, 'sub': <function <lambda> at 0x00000172971CB6A8>} {'__module__': '__main__', '__init__': <function Point.__init__ at 0x00000172971A5AE8>, '__str__': <function Point.__str__ at 0x00000172971A5B70>, 'show': <function Point.show at 0x00000172971CBD08>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None, 'add': <function <lambda> at 0x00000172971A5950>}
思考
这种动态增加属性的方式和装饰器装饰一个类、Mixin方式的差异?
这种是动态增删属性的方法,是运行时改变类或实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射的能力更加的灵活。
反射相关的魔术方法
__getattr__()、__setattr__()、__delattr__()这三个魔术方法,分别测试这三个方法
__getattr__()
class Base: n = 0 class Point(Base): z = 10 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.m) #输出 4 10 0 Missing m
实例属性会按照继承关系查找,如果找不到,就会执行__getattr__()方法,如果没有这个方法就会抛出AttributeError异常便是找不到属性。
查找顺序为:
instance.__dict__ --> instance.__class__.__dict__-->继承的祖先类(直到object基类)的__dict__-->找不到-->调用__getattr__()
__setattr__()
class Base: n = 0 class Point(Base): z = 10 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) print(p1.z) print(p1.n) print(p1.m) p1.x = 100 print(p1.x) print(p1.__dict__) p1.__dict__['x'] = 20 print(p1.__dict__) print(p1.x) #输出结果 setattr x=4 setattr y=5 Missing x 10 0 Missing m setattr x=100 Missing x {} {'x': 20} 20
实例通过.号设置属性,例如self.x = x 属性赋值,就会调用__setattr__(),属性要加到实例的__dict__中,就需要自己完成。
class Base: n = 0 class Point(Base): z = 10 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) print(p1.z) print(p1.n) print(p1.m) p1.x = 100 print(p1.x) print(p1.__dict__) p1.__dict__['x'] = 20 print(p1.__dict__) print(p1.x) #输出 setattr x=4 setattr y=5 4 10 0 Missing m setattr x=100 100 {'x': 100, 'y': 5} {'x': 20, 'y': 5} 20
__setattr__()方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的字典__dict__.
class Base: n = 100 class Point(Base): z = 60 d = {} def __init__(self, x, y): self.x = x # self.y = y setattr(self,'y',y) self.__dict__['a'] = 5 def show(self): print(self.x, self.y) def __getattr__(self, item): print("missing {}".format(item)) return self.d[item] def __setattr__(self, key, value): print("setattr {}={}".format(key,value)) self.d[key] = value def __delattr__(self, item): print("can not del {}".format(item)) p1 = Point(4,5) print(p1.x,p1.y) print(p1.__dict__) print(p1.a) print(p1.d) print(Point.__dict__) #输出 setattr x=4 setattr y=5 missing x missing y 4 5 {'a': 5} 5 {'x': 4, 'y': 5} {'__module__': '__main__', 'z': 60, 'd': {'x': 4, 'y': 5}, '__init__': <function Point.__init__ at 0x000002118E635AE8>, 'show': <function Point.show at 0x000002118E635B70>, '__getattr__': <function Point.__getattr__ at 0x000002118E65BD08>, '__setattr__': <function Point.__setattr__ at 0x000002118E65B6A8>, '__delattr__': <function Point.__delattr__ at 0x000002118E65B8C8>, '__doc__': None}
__delattr_()
class Point: 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) print(p.x) del p.x p.Z = 10 del p.z del p.Z print(p.__dict__) print(p.__class__.__dict__) del Point.z print(Point.__dict__) #输出 4 Can not del x Can not del z Can not del Z {'x': 4, 'y': 5, 'Z': 10} {'__module__': '__main__', 'z': 5, '__init__': <function Point.__init__ at 0x000001C8DCFD5AE8>, '__delattr__': <function Point.__delattr__ at 0x000001C8DCFD5B70>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None} {'__module__': '__main__', '__init__': <function Point.__init__ at 0x000001C8DCFD5AE8>, '__delattr__': <function Point.__delattr__ at 0x000001C8DCFD5B70>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
通过实例删除属性,就会尝试调用该魔术方法,可以阻止通过实例来删除属性的操作,但是通过类依然能删除类的属性。
__getattribute__()
class Base: n = 10 class Point(Base): z = 1 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.m) print(Point.__dict__) print(Point.z) #输出 __dict__ x z n m {'__module__': '__main__', 'z': 1, '__init__': <function Point.__init__ at 0x000002BD3F595AE8>, '__getattr__': <function Point.__getattr__ at 0x000002BD3F595B70>, '__getattribute__': <function Point.__getattribute__ at 0x000002BD3F5BAD08>, '__doc__': None} 1
实例的所有的属性访问,第一都会调用__getattribute__()方法,它阻止了属性的查找,该方法应该返回值或抛出一个AttributeError异常。
1、它的return值将作为属性查找的结果
2、如果抛出AttributeError异常,则会直接调用__getattr__()方法,因为表示属性没有找到。
class Base: n = 10 class Point(Base): z = 1 def __init__(self, x, y): self.x = x self.y = y def __getattr__(self, item): return "Missing {}".format(item) def __getattribute__(self, item): # raise AttributeError("Not Found") # pass # return self.__dict__[item] return super().__getattribute__(item) p1 = Point(4,5) print(p1.__dict__) print(p1.x) print(p1.z) print(p1.n) print(p1.m) print(Point.__dict__) print(Point.z) #输出 {'x': 4, 'y': 5} 4 1 10 Missing m {'__module__': '__main__', 'z': 1, '__init__': <function Point.__init__ at 0x000002D6BFAD5AE8>, '__getattr__': <function Point.__getattr__ at 0x000002D6BFAD5B70>, '__getattribute__': <function Point.__getattribute__ at 0x000002D6BFAFAD08>, '__doc__': None} 1
__getattribute__方法中为了 避免在该方法中无限的递归,它的实现应该调用基类的同名方法以访问任何属性,例如object.__getattribute__(self, item)
注意:除非你知道__getattribute__方法用来做什么,否则不要用。
总结
| 魔术方法 | 意义 |
| __getattr__() | 当通过搜索实例、实例的类或祖先类查找不到属性,就会调用次方法,,如没次方法,则抛出AttributeError错误 |
| __setattr__() | 通过.访问实例属性,进行增加、修改都要调用它 |
| __delattr__() | 当通过实例来删除属性时调用此方法 |
| __getattribute__() | 实例所有的属性调用都从这个方法开始 |


浙公网安备 33010602011771号