python的魔术方法(反射)

一、魔术方法---反射

  •  概述:运行时,区别于编译时,指的是程序被加载到内存中执行的时候
  •  反射,reflection,指的是运行时获取类型信息,一个对象能够在运行时,像照镜子一样,反射出器类型信息
  •  简单说,在python中,能够通过一个对象,找出其type,class,attribute或者method的能力,称为反射或者自省
  •  具有反射能力的函数有:type(),isinstance(),callable(),dir(),getattr()

1、反射相关的函数和方法

需求:有一个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))
print(p.__dir__())


2、内建函数

  •   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,10)
print(repr(p1),repr(p2),sep='
')
print(p1.__dict__)
setattr(p1,'y',16)
print(p1.__dict__)
setattr(p1,'z',10)
print(p1.__dict__)
print(1111111111111,getattr(p1,'__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)

p1 = Point(4,5)
p2 = Point(10,10)
# print(repr(p1),repr(p2),sep='
')
# print(p1.__dict__)
# setattr(p1,'y',16)
# print(p1.__dict__)
# setattr(p1,'z',10)
# print(p1.__dict__)
# print(1111111111111,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在谁里面(add是类的方法,sub是实例的方法)
print(p1.__dict__)  #  
print(Point.__dict__)

思考:这种动态增加属性的方式是运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了
      因此反射能力具有更大的灵活性


二、反射相关的魔术方法

  • __getattr__(), __setattr__(), __delattr__()这三个魔术方法,分别测试

1、__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)

#一个类的属性会按照继承关系找,如果找不到,就会执行__getattr__()方法
#如果没有这个方法,就会抛出AttributeError异常表示找不到属性
#instance.dict-->instance.class.dict-->继承的祖先类(直到object)的dict——》调用getattr()
    


2、__setattr__()方法举例

class Base:
    n = 0

class Point(Base):
    z = 6
    def __init__(self,x,y):
        print(1111111111111111111)
        self.x = x
        self.y = y
    def show(self):
        print(self.x,self.y)
    
    def __getattr__(self,item):
        print(2222222222222222222)
        return "missing {}".format(item)
    
    def __setattr__(self,key,value):
        print(3333333333333333333)
        print("setattr {}={}".format(key,value))
        self.__dict__[key] = value  #__setattr__()方法,可以拦截对实例属性的增加,修改操作,如果要设置生效,需要自己操作实例的__dict__

p1 = Point(4,5)
print(p1.x)
print(p1.z)
print(p1.n)
#print(p1.t)
p1.x = 50
print(p1.__dict__)
print(Point.__dict__)

实例通过点设置属性,如果self.x = x,就会调用__setattr__()方法,属性要加到实例的__dict__中,需要自己完成
__setattr__()方法,可以拦截对实例属性的增加,修改操作,如果要设置生效,需要自己操作实例的__dict__

3、__delattr__()

  • 可以阻止通过实例删除属性的操作,但是通过类依然可以删除属性
class Base:
    n = 0


class Point(Base):
    z = 6
    def __init__(self,x,y):
        self.x =x
        self.y = y
    
    def __delattr__(self,item):
        print('Can not del {}'.format(item))

p = Point(14,5)
del p.x
p.z = 15
del p.z   # 实例不能删除属性
del p.z

print(111111111111111,Point.__dict__)
print(p.__dict__)
del Point.z           #类可以删除属性
print(Point.__dict__)


__getattribute__ : 实例所有的属性调用都从这个方法开始
方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法


4、属性查找顺序

  • 实例调用__getattribute__()——> instance.__dict__--> instance.__class__.__dict__-->继承的祖先类的dict-->都没找到最后调用__getattr__()
原文地址:https://www.cnblogs.com/jiangzuofenghua/p/11448790.html