TOC

反射

    不论是在Java还是Python中,反射都是一个比较重要的概念,在了解反射之前需要知道一个东西,即运行时,Python虽然是动态语言,但动态语言要想运行,一样需要事先进行编译,但是需要知道的是,编译的过程不是运行时,运行时是解释器进程的已经诞生了,程序确实已经运行起来了,这个运行起来的“时刻”,就叫运行时。也就是说,将代码转成字节码并将字节码用解释器进程运行起来了,那么这个运行起来的“时刻”叫运行时(Runtime)。
    而反射指的是,运行时可以获取对象的很多信息,可以将对象的类型信息获取到,在Python中简单的说,就像照镜子一样,让对象照镜子,可以在镜子里面看到它内部各种个样的组成部分,换一句就是能够通过反射这种技术,找出对象运行时的一些信息,比如type、class、attribute或method的能力,这就称为反射,也称之为自省,具有反射能力的函数很多,比如要获取对象的类型,可以使用type(),要知道它是否属于某一种类型,可以使用instance(),需要知道它是否是一个可调用对象,可以使用callable(),要知道这个对象内部提供的属性和方法,可以使用dir(),要知道这个对象类型是否存在某个指定的内建函数,可以使用gettattr();
class Point():
    def __init__(self,x,y):
        self.x=x
        self.y=y
p1=Point(1,2)
# 如果我们需要获取这个p1对象的信息,可以直接以对象的方式访问
print(p1.x)
print(p1.y)
# 也可以利用反射函数来获取对象内部的信息
print(dir(p1))
print(type(p1))
print(isinstance(p1,Point))
# 如果需要修改这个对象的信息,我们可以使用对象的方式来修改
p1.z=100
print(p1.z)
p1.__dict__["xyz"]=10000
print(p1.xyz)
    在Python里面,反射是一个很重要的概念,在Python源码里面大量的使用,它和上面说的概念一样,它可以把字符串映射到实例的变量或者实例的方法然后可以去执行调用、修改等操作,它有四个重要的方法;
# 反射函数
hasattr(obj,attribute):判断对象里面是否存在指定属性;
setattr(obj,attribute,value):设定对象里面指定的属性和值
getattr(obj,attribute):获取对象里面指定的属性值;
delattr(obj,attribute):删除对象里面指定的属性;
# 魔术方法
__getattr__:当通过搜索实例、实例的类及祖先类找不到属性时,就会调用次方法;
__setattr__:通过"."访问实例属性,进行增加、修改都会调用该方法;
__delattr__:当通过实例来删除属性时,会调用此方法;
__getattribute__:通过实例访问实例属性都从这个魔术方法开始;
class Point():
    def __init__(self,x,y):
        self.x=x
        self.y=y
p1=Point(1,2)
print(getattr(p1,'x')) # 1
# 给对象新增或者覆盖一个属性和值
setattr(p1,"x",2)
# 获取对象内部指定的属性
print(getattr(p1,'x')) # 2
# 判断对象是否存在某个属性
print(hasattr(p1,'x')) # True

# 使用setattr注入一个方法
if not hasattr(p1, 'show'):
    setattr(Point, 'show', lambda self: print(self.x, self.y))
p1.show() # 1 2

方法注入

    对于方法注入需要注意,在类上面注入和在实例上面注入是完全两种不同的结果,在类上面注入方法,这个方法会和类进行绑定,在这个类的实例进行调用时,会自动将第一参数self传入,并且,这个类下面所有的实例都可以共用这个方法。
    但是如果是对实例注入一个方法,就不一样了,它不会和类进行绑定,所以,在调用的时候,需要手动将实例作为第一参数传入才行,并且对于实例来讲,那个实例绑定了这个方法,哪个实例才可以使用,因为实例的作用就是用来保存数据,方法是属于类的。
class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def test(self):pass
p1 = Point(1, 2)
if not hasattr(p1, 'show1'):
    setattr(p1, 'show1', lambda self: print(self.x, self.y))
if not hasattr(Point, 'show2'):
    setattr(Point, 'show2', lambda self: print(self.x, self.y))
print(p1.show1) # <function <lambda> at 0x10262c048>
print(p1.show2) # <bound method <lambda> of <__main__.Point object at 0x10600f4e0>>
# 可以看到,注入实例和注入类的区别,注入实例只会对指定的实例注入,只能注入的这个实例可以使用,并且在注入实例的情况下,并没有绑定类
# 而如果注入类,就会和类进行绑定,并且在调用时,自动注入第一参数self

# 因为第一种。直接对实例的方式注入,它并没有绑定类,所以在注入时,并不会自动注入第一参数,所以在直接使用实例调用的时候会抛出异常,所以在调用时,我们需要手动传入self
p1.show1(p1) # 1 2
  • 更深层次的原因是因为,实例一般是用来保存数据的,方法都是在这个实例所属的类中,当实例需要调用方法都是去它所属的类找的,总结一句话,类实现方法,实例提供数据,上面,我们对 一个实例注入一个函数对象,在实例里面体现的是一个数据属性,只不过这个数据属性的值是一个函数对象而已,如下;
class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def test(self):pass
p1 = Point(1, 2)
print(Point.__dict__) # ['test': <function Point.test at 0x10600aa60>, ...]
print(p1.__dict__) # {'x': 1, 'y': 2}
# 实例提供数据,类提供操作数据的方法

魔术方法

    上面说过的, dir有__dict__的魔术方法,callable有__call__的魔术方法,那么对于反射,也有对应的方法,getattr有__getattr__的魔术方法,setattr有__setattr__的魔术方法,delattr有__delattr__的魔术方法;
    对于__getattr__魔术方法,有一个特点,我们需要知道,__getattr__魔术方法有一个特殊的功效,在正常情况下,如果我们对一个对象获取属性时,如果属性不存在,默认会抛出AttributeError这样的错误,但是如果这个对象所属的类,重写了__getattr__魔术方法就不一定了,默认情况下,重写了__getattr__魔术方法,当在遇到属性不存在会做一个拦截,其实相当于,__getattr__可以自定义当找不到对象时,会临时给一个缺省值;
class Point(object):
    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 item,"getattr"
p1=Point(1,2)
print(p1.testvalue) # ('testvalue', 'getattr')
print(p1.testvalue()) # TypeError: 'tuple' object is not callable
# 当然,如果是一个方法时,也是这样的,只不过如果不存在会抛出异常,但是流程都是一样的,如上调用testvalue方法,如果在实例和父类找不到就会调用__getattr__,因为__getattr__返回的是一个None,所以,当加上()做为一个方法来调用时,会抛出异常
    针对setattr的__setattr__魔术方法,它是专门用来给当前对象添加一个属性的,只不过它是在使用instance.attr_name=value或者使用instance.attr_name以及使用setattr语法给实例注入属性时才会调用,由此可以知道,在__setattr__魔术方法里面不可以使用instance.attr_name=value和setattr两个语法,否则会造成无限递归;
    因为在实例进行初始化时,也会调用__setattr__,将__init__里面的实例属性通过__setattr__注入到instance里面去,所以__setattr__魔术方法,不仅需要用于实例初始化,还用于实例添加新属性两种情形;
  • __setattr__中不可存在反射相关的语法,比如instance.key=value或者setattr等语法,否则会造成无限递归,因为反射方法默认就是调用对象的__setattr__方法;
class Point(object):
    z = 6
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __setattr__(self, key, value):
        pass
p1 = Point(1, 2)
print(p1.__dict__) # {}  当覆盖父类__setattr__方法并且语句快为pass时,会发现__dict__里面也没有数据
# 所以还必须在__setattr__的语句块中提供,实例的属性字典添加属性操作
class Point(object):
    z = 6
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __setattr__(self, key, value):
        self.__dict__[key]=value  # 以字典的方式进行属性添加,绕开反射方式添加,或者直接引用父类的super().__setattr__()
p1 = Point(1, 2)
print(p1.__dict__) # {'y': 2, 'x': 1}
    对于delattr的魔术方法__delattr__,见名知意它就是对象属性删除的方法,需要注意的是,只有删除实例属性的时候,才会调用__delattr__,直接使用类删除类属性时,不会调用__delattr__;
class Point(object):
    z = 6
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __delattr__(self, item):
        print("调用delattr删除实例属性{%s}"%item)
p1 = Point(1, 2)
del Point.z # 不走__delattr__
del p1.x # 调用delattr删除实例属性{x}

属性访问拦截器

    属性访问拦截器(__getattribute__),只要使用instance.attribute_name这种方式访问实例属性都会被属性访问拦截器拦截,它和__getattr__类似,只不过__getattr__是只有在实例属性和类属性中找不到时,才会调用__getattr__,而__getattribute__则是只要使用instance.attribute_name访问属性,都会调用__getattribute__方法;
    注意查看语法instance.attribute_name,是只有使用实例来访问属性的时候才会调用__getattribute__,使用类来访问是不会调用__getattribute__方法的。
class Point(object):
    number=0
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __getattribute__(self, item):
        __class__.number+=1 # 此处使用__class__.number的原因是,使用self.number会调用__getattribute__从而造成无限递归的情况
        print("第%s次调用getattribute" %__class__.number)
        return super(Point, self).__getattribute__(item)  # 或 object.__getattribute__(self,item) 之所以要传入self是因为object是类,而并非实例
p1 = Point(1, 2)
print("instance",p1.x)
print("instance",p1.y)
print("class",Point.number)
# 第1次调用getattribute
# instance 1
# 第2次调用getattribute
# instance 2
# class 2
    通过上述的例子也可以看出来,只有在访问实例属性的时候才会调用到__getattribute__,类属性不会调用__getattribute__,因为不知道__getattribute__内部的原理,所以只能调用父类,即object的__getattribute__方法;
结合__getattr__
    在上面说过,__getattr__的主要作用是,当访问一个实例属性时,如果实例属性字典或继承列表里面的父类属性列表找不到,就会调用__getattr__,这个找不到的意思就是抛出AttributeError,所以,此时我们就可以将__getattr__和__getattribute__结合起来使用,在特殊情况下对一些特殊的属性可以进行访问控制;
class Point(object):
    number=0
    custom_dict={"name":"cce"}
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __getattribute__(self, item):
        print("调用getattribute")
        if item == "name":
            raise AttributeError("attribute not found")
        return object.__getattribute__(self,item)
    def __getattr__(self, item):
        print("调用getattr")
        return __class__.custom_dict[item]
p1 = Point(1, 2)
print("instance",p1.name)
# 调用getattribute
# 调用getattr
# instance cce
  • 实例属性访问顺序 :__getattribute__() ---> instance.__dict__ ---> instance.__class__.__dict__ ---> 继承的祖先类(直到object).__dict__ ---> __getattr__()

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注