反射
1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
2 python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数。都是在对象自身当中实现
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)。
所以这四个函数中形参object既可以传对象名,也可以传类名。
hasattr(object,name) #参数表(对象,字符串) # 判断object中有没有一个name字符串对应的方法或属性
def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case. """ pass
def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, 'y', v) is equivalent to ``x.y = v'' """ pass
def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, 'y') is equivalent to ``del x.y'' """ pass delattr(x, y)
class BlackMedium: #黑中介 feture='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('【%s】 正在卖房子,傻逼才买呢' %self.name) def rent_house(self): print('【%s】 正在租房子,傻逼才租呢' % self.name) print(hasattr(BlackMedium,'feture')) getattr() b1=BlackMedium('万成置地','天露园') b1.name--->b1.__dic__['name'] print(b1.__dict__) #{'addr':'天露园','name':'万成置地'} # b1.name #调用方式 # b1.sell_house #第一个hasattr(object,name) #检测的并不是属性字典里有没有,而是b1能不能调用后面的属性内容 print(hasattr(b1,'name')) #True #第二个参数传字符串形式 print(hasattr(b1,'sell_house')) #True print(hasattr(b1,'selasdfasdfsadfasdfasdfasdfasdl_hourse')) # 第二个getattr(object,name,default=None)查找数据属性、函数的地址。 # 参数表中的第三个参数为找不到的时候返回的运行值 # 从而通过查找到的函数地址直接运行该函数从而通过函数地址直接运行该函数 print(getattr(b1,'name')) #万成置地 # 从而通过查找到的函数地址直接运行该函数从而通过函数地址直接运行该函数 print(getattr(b1,'rent_house')) func=getattr(b1,'rent_house') func() # print(getattr(b1,'rent_hourseasdfsa')) #没有则报错 print(getattr(b1,'rent_hourseasdfsa','没有这个属性')) #没有则报错 # 第三个setattr(x,y,z) 对象x,属性sb,值z setattr(b1,'sb',True) # 与b1.sb=True等价 #创建一个新的属性 setattr(b1,'sb1',123) setattr(b1,'name','SB') #改原来的数据属性 # 函数属性 setattr(b1,'func',lambda x:x+1) setattr(b1,'func1',lambda self:self.name+'sb') #对name字符串拼接一个sb字符串 print(b1.__dict__) #新增的函数和数据属性成功添加到属性列表中 print(b1.func) #调用新增的函数属性 print(b1.func(10)) #11 print(b1.func1(b1)) #万成置地sb # 第四个delattr(x,y) del b1.sb del b1.sb1 delattr(b1,'sb') print(b1.__dict__)
class Foo(object): staticField = "old boy" def __init__(self): self.name = 'wupeiqi' def func(self): return 'func' @staticmethod def bar(): return 'bar' print getattr(Foo, 'staticField') print getattr(Foo, 'func') print getattr(Foo, 'bar')
# 模块的反射应用 #test.py中的两行 # def say_hi(): # print('你好啊') import test as obj #模块名也是对象 print(obj) print(hasattr(obj,'say_hi')) #True print(hasattr(obj,'say_hisssssssssssssssssssssssssssssssssssssssssss')) #False if hasattr(obj,'say_hi'): func=getattr(obj,'say_hi') #存在此函数,就直接执行此函数 func() else: print('其他的逻辑') #例2:想要直接运行当前文件(是一个模块)来查看是否含有某变量 x=111 y=222 #方法一: # import fanshe as obj1 #如果这样导入,会执行两遍: #导入的时候直接执行一遍,然后再顺序执行当前文件(就是你导入的模块)内容 #即直接运行两遍本文件 #方法二: import sys obj1=sys.modules[__name__] print('===>',hasattr(obj1,'x')) #查看obj中有无x变量
导入其他模块,利用反射查找该模块是否存在某个方法
#!/usr/bin/env python # -*- coding:utf-8 -*- def test(): print('from the test')
#!/usr/bin/env python # -*- coding:utf-8 -*- """ 程序目录: module_test.py index.py 当前文件: index.py """ import module_test as obj #obj.test() print(hasattr(obj,'test')) getattr(obj,'test')()
3 为什么用反射之反射的好处
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。彼此之间都不用等待。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
#egon还没有实现全部功能 class FtpClient: 'ftp客户端,但是还没有实现具体的功能' #ftp客户端具有上传下载的功能 def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr
#不影响lili代码编写 #from module import FtpClient f1=FtpClient('192.168.1.1') #实例化,传入一个IP地址 if hasattr(f1,'get'): #判断有没有get方法,有就运行,没有就写别的 func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑')
好处二:动态导入模块(基于反射当前模块成员)
如果模块是一串字符串,不是简单的一个模块名时。
# 方法一: module_t=__import__('m1.t') #直接导入一个字符串,想要导入t模块 print(module_t) #<module 'm1' (namespace)>结果返回的是m1模块(最顶层模块) module_t.t.test1() # 等价于m1.t.test() #module_t.test1()报错这样相当于m1.test1() # 方法2:在m1.t当中加入独有的_test2() # from m1.t import * #会报错,因为导入所有的时候,test2是独有的,无法导入 from m1.t import test1,_test2 # test1() # _test2() #可成功调用 # 方法三:以字符串形式来导入模块: import importlib m=importlib.import_module('m1.t') #这样可以直接就定位到t了 print(m) m.test1() m._test2()
注意:
在python中一切皆对象,在执行import导入一个包的时候,会自动执行包中的_init_函数,相当于在实例化。
动态导入的底层就是基于反射来实现的。
4 双下划綫开头的attr方法
#三个内置的attr方法,系统有内置的。 #如果你不自己定义,就默认使用系统的,如果像这样自己定义了,就用你自己写的
#类调用这三个相应功能时不会触发,只有对象调用属性时,才会触发。
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): #最常用 print('----> from getattr:你找的属性不存在') def __setattr__(self, key, value): print('----> from setattr') # self.key=value #这就无限递归了,你好好想想 #这样设置会导致,以这个方式赋值就触发__setattr__(),最终进入递归,死循环 if type(v) is str: print('开始设置') self.__dict__[key]=value #应该利用自己的属性字典来操作 #直接修改底层字典,直接一步到位,不会触发内置函数 else: print('必须是字符串类型才给设置') def __delattr__(self, item): print('----> from delattr') # del self.item #无限递归了 self.__dict__.pop(item) #__setattr__添加/修改(设置)属性会触发它的执行 f1=Foo(10) #实例化就会触发 print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 f1.z=3 print(f1.__dict__) #__delattr__删除属性的时候会触发 f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用点调用属性且属性不存在的时候才会触发,类名调用不会触发。 # 系统内置的就是,如果找的不存在就直接报错。 f1.xxxxxx