魔术方法之反射
概述
运行时(runtime)区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义的信息。
一个对象能够在运行时,像照镜子一样,反射出其类型信息。
简单说,在python中,能够通过一个对象,找出type,class,attribute或method的能力,称之为反射或自省。
具有反射能力的函数由type(),isinstance(),callable(),dir(),getattr()等。
反射相关的函数和方法
需求
有一个Point类,查看他的实例的属性,并修改他,动态为实例增加属性
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return 'Point({}{})'.format(self.x,slef.y)
def add():
'ssss'
pass
p = Point(4,5)
p.__dict__['x'] = 10
print(add.__name__)
上例通过属性字典来访问对象的属性,本质上也是利用反射的能力。但是上面例子访问的方式不优雅,python提供了内置函数。
内建函数 | 意义 |
---|---|
getattr(obj,name[,default]) | 得到obj的属性name(name必须为字符串),如果没有则返回def。如果没有给定def则抛出AttributeError, |
setattr(obj,name,value) | obj的属性存在则覆盖,没有则新增(name必须为字符串) |
hasattr(obj,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)
p = Point(4,5)
def add():
'ssss'
pass
print(getattr(p,'z',20))
print(getattr(add,'__doc__'))
if not hasattr(p,'z'):#如果p没有z属性,-> p.z = 20
setattr(p,'z',20)
print(p.z)
if not hasattr(Point,'__add__'):#给类动态增加方法,注意obj参数
setattr(Point, '__add__', lambda self,other:Point((self.x + other.x),(self.y + other.y)))
print(p + p)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oUWwHnHO-1572778937226)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571840988218.png)]
这种动态增加属性的方式是运行时改变类或者实例的方式,但是装饰器或者mixin都是定义时就决定了。因此反射能力具有更大的灵活性。
反射相关魔术方法
__getattr__()
,__setattr__()
__delattr__()
这三个魔术方法,接下来分别测试这三种放法
__getattr__()
class Base:
z = 0
class Point(Base):
n = 10
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return 'Point({},{})'.format(self.x,self.y)
def __getattr__(self, item):#当以实例访问属性时出现AttributeError,就会调用该方法,return值作为访问返回值
print(self,item,type(item))
p = Point(4,5)
print(p.x,p.y,p.z,p.n)
print(p.b)
print(Point.b)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a67gi9go-1572778937226)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571880041413.png)]
实例属性会按照继承关系找,如果找不到,就会执行__getattr__()
方法,如果没有这个方法,就会抛出AttributeError。
查找属性顺序:
instance.__dict__--> instance.__class__.__dict__--> 继承的祖先类(直到object)的dict
找不到调用__getattr__()
__setattr__()
class Base:
z = 0
class Point(Base):
n = 10
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return 'Point({},{})'.format(self.x,self.y)
def __getattr__(self, item):
print('getattr:',item)
return item
def __setattr__(self, name, value):
print('setattr:',name,value)
#self.__dict__[name] = value
p = Point(4,5)
print(p.x,p.y,p.z,p.n)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LKKlcK5N-1572778937228)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571880766849.png)]
由此可以看出,当类实现了__setattr__()
方法,在给实例添加属性例如self.n = 100
,都会调用该方法,而类不受该方法的影响。属性要添加到实例字典里就只能自己完成self.__dict__[name] = value
。
该方法可以拦截对实例属性的增加修改操作,如果要设置生效,需要自己操作实例字典。
例子:setattr和getattr的中和应用
class Point:
d = {}
def __init__(self,x,y):
self.x = x
self.y = y
self.__dict__['z'] = 100
def __str__(self):
return 'Point({},{})'.format(self.x,self.y)
def __getattr__(self, item):
return self.d[item]
def __setattr__(self, name, value):
self.d[name] = value
p = Point(4,5)
print(p.x, p.y, p.z)
print(p.__dict__,Point.d)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIwtWtQJ-1572778937229)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571881832830.png)]
上例中实例的x,y属性放到了类属性的d字典里,而自己的字典里只有z。
__delattr__()
class Point:
d = {}
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return 'Point({},{})'.format(self.x,self.y)
def __delattr__(self, item):
print('Can not del {}'.format(item))
p = Point(4,5)
del p.y
del Point.d
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olgqyauk-1572778937230)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571882330901.png)]
可以阻止通过实例来删除属性的操作。但是通过类依然可以删除属性。
__getattribute__()
class Point:
n = 100
def __init__(self,x,y):
self.x = x
self.y = y
def show(self):
pass
def __str__(self):
return 'Point({},{})'.format(self.x,self.y)
def __getattr__(self, item):
return self.d[item]
def __getattribute__(self,item):
print('getattribute',item)
p = Point(4,5)
print(p.x, p.n, p.show, p.__dict__, p.__class__)
Point.n, Point.show
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zu858xRi-1572778937231)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571882731764.png)]
当类实现了__getattribute__()
方法时,实例所有的属性访问都被该方法拦截了,该方法返回计算后的值,或抛出AttributeError.
- 他的return值将作为属性查找的结果。
- 如果抛出AttributeError异常,则会直接调用
__getattr__
方法,因为表示属性没有找到。
该方法中为了避免无限递归,大的实现应该永远调用基类的同名方法以访问需要的任何属性,例如
object.__getattribute__(self, name)#注意self不会实例绑定,因为是类调用。
注意:除非明确地知道该方法的作用,否则不要使用它。
总结
反射魔术方法 | 意义 |
---|---|
__getattr__() | 当通过实例查找属性(不管是自己的还是继承的),找不到就会调用 |
__setattr__() | 通过. 访问实例自身的属性,并对其增加或修改都会调用。(实例可以修改祖先类的引用类型属性如:字典,列表等) |
__delattr__() | 当通过实例来删除自身属性时调用此方法 |
__getattribute__() | 实例所有的属性调用都会被拦截 |
属性查找顺序:
实例调用__getattribute__() --> instance.__dict__ --> instance.__class__.__dict__ -->继承的祖先类直到object的__dict__ -->找不到调用__getattr__()
属性时调用此方法 |
| __getattribute__()
| 实例所有的属性调用都会被拦截 |
属性查找顺序:
实例调用__getattribute__() --> instance.__dict__ --> instance.__class__.__dict__ -->继承的祖先类直到object的__dict__ -->找不到调用__getattr__()