魔术方法之反射

魔术方法之反射

概述

运行时(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__()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值