反射说简单点 --> 就是利用字符串的形式去对象(模块)中操作(寻找/检查/删除/设置)成员。
1.根据字符串的形式导入模块。
2.根据字符串的形式去对象(某个模块)中操作其成员
说反射之前先介绍一下__import__方法,这个和import导入模块的另一种方式
1. import commons 2. __import__('commons')
如果是多层导入:
1. from list.text import commons 2. __import__(' list.text.commons',fromlist=True) #如果不加上fromlist=True,只会导入list目录
反射即想到4个内置函数分别为:getattr、hasattr、setattr、delattr 获取成员、检查成员、设置成员、删除成员下面逐一介绍先看例子:
class Foo(): def __init__(self): self.name = 'abc' def func(self): return "OK" obj = Foo() # 获取成员 ret = getattr(obj, 'func') # 获取的是个对象 r = ret() print(r) # 检查成员 ret = hasattr(obj, 'func') # 因为有func方法所以返回True print(ret) # 设置成员 print(obj.name) # 设置之前为:abc ret = setattr(obj, 'name', 19) print(obj.name) # 设置之后为:19 # 删除成员 print(obj.name) # abc delattr(obj, 'name') #print(obj.name) # 报错
OK
True
abc
19
web实例
考虑有这么一个场景,根据用户输入的url的不同,调用不同的函数,实现不同的操作,也就是一个url路由器的功能,这在web框架里是核心部件之一。下面有一个精简版的示例:
首先,有一个test模块,它里面有几个函数,分别用于展示不同的页面,代码如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : Jack.Ming def login(): print("这是一个登陆界面") def logout(): print("这是一个退出界面") def look(): print("这是一个浏览界面")
其次,有一个web模块,作为程序入口,接受用户输入,展示相应的页面,代码如下:(这段代码是比较初级的写法)
import test def run(): inp = input("请输入您想访问页面的url: ").strip() if inp == "login": test.login() elif inp == "logout": test.logout() elif inp == "home": test.look() else: print("404") if __name__ == '__main__': run()
运行该程序:
请输入您想访问页面的url: login
这是一个登陆界面
这就实现了一个简单的WEB路由功能,根据不同的url,执行不同的函数,获得不同的页面。
如果在text模块有成千上万了函数,那么在web模块中也需要有成千上万个判断语句,那这样岂不是很麻烦,我们可以用反射的方式解决这种问题:
反射机制(动态导入)
仔细观察web中的代码,我们会发现用户输入的url字符串和相应调用的函数名好像!如果能用这个字符串直接调用函数就好了!但是,前面我们已经说了字符串是不能用来调用函数的。为了解决这个问题,我们可以使用getattr和hasattr这两个内置函数,代码如下:
# !/usr/bin/env python # -*- coding: utf-8 -*- # @Author : Jack.Ming def run(): url = input("请输入URL地址:").strip() modules, index = url.split("/") obj = __import__(modules, fromlist=True) if hasattr(obj, index): func = getattr(obj, index) func() else: print("404 页面不存在") if __name__ == '__main__': run()
运行:
请输入URL地址:test/login
这是一个登陆界面
分析一下以上代码:
首先,我们并没有定义任何一行import语句;
其次,用户的输入URL被要求为类似“commons/home”这种格式,其实也就是模拟web框架里的url地址,斜杠左边指向模块名,右边指向模块中的成员名。
然后,modules,index = url.split("/")处理了用户输入,使我们获得的2个字符串,并分别保存在modules和index变量里。
接下来,最关键的是obj = __import__(modules)这一行,它让程序去导入了modules这个变量保存的字符串同名的模块,并将它赋值给obj变量。
使用hasattr方法判断在该模块中是否有该方法。
最后的调用中,getattr去modules模块中调用index成员的含义和以前是一样的。
总结:通过__import__函数,我们实现了基于字符串的动态的模块导入。
利用反射查看面向对象成员归属
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2017/12/26 0026 15:18 # @Author : ming # 以字符串的形式去对象中查找成员 class Foo: def __init__(self, name): self.Name = name def show(self): print("show") # 反射:类,只能查找类中的成员 r = hasattr(Foo, "Name") print(r) r = hasattr(Foo, "show") print(r) # 反射:对象,即可查找对象,也可以查找类的成员 obj = Foo("yang") r = hasattr(obj, "Name") print(r) r = hasattr(obj, "show") print(r)
运行结果:
False
True
True
True
利用反射导入模块、查找类、创建对象、查找对象中的字段
内容如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/1/2 0002 14:34 # @Author : ming # 导入模块 m = __import__("s1", fromlist=True) # 去模块中找类 lei = getattr(m, "Foo") # 获取类对象 obj1 = lei("yang") # 去类中找属性值Name obj2 = getattr(obj1,"Name") # print print(obj2)
yang