1、什么是反射
在程序开发中,常常会遇到这样的需求:
在执行对象中的某个方法,或者在调用对象的某个变量,或是需要对对象的某个字段赋值,而方法名或是字段名在编码代码时并不能确定,需要通过参数传递字符串的形式输入。
举个具体的例子:当我们需要实现一个通用的DBM框架时,可能需要对数据对象的字段赋值,但我们无法预知用到这个框架的数据对象都有些什么字段,换言之,我们在写框架的时候需要通过某种机制访问未知的属性。这时我们需要一个特殊的方法或者机制来访问或操作该未知的方法或变量,这种机制就被称之为反射(反过来让对象告诉我们他是什么),或是自省。
反射机制:通过字符串的形式,映射object对象对应的方法或属性,并且可以操作(查找/获取/删除/添加)对象成员。在Python中,因为一切都是对象object。因此,可以通过反射用字符串导入模块,可以通过字符串找到类,也可以映射函数、方法、属性,并且能对其进行操作等。
使用反射获取到的函数和方法可以像平常一样加上括号直接调用,获取到类后可以直接构造实例;不过获取到的字段不能直接赋值,因为拿到的其实是另一个指向同一个地方的引用,赋值只能改变当前的这个引用而已。
2、反射实现的方法
反射依赖以下四种方法实现对object对象的操作:
2.1 hasattr(obj, name_str)
判断对象obj是否有name_str这个方法或属性,返回值为Bool类型,存在name_str特性返回True,否则返回False。注意:name_str必须是一个字符串。见下图:
2.2 getattr(obj, name_str[, default])
获取obj对象中与name_str同名的方法或属性。对象存在该属性则返回该属性:
不存的话抛出异常或者返回默认值,其中默认值为可选项。在属性不存在的情况下,设置了默认值则返回默认值:
若没有设置默认值则抛出(AttributeError)异常:
注意:如果获取的是方法,存在则返回对象中方法的内存地址,若想运行则需通过添加"()"来调用。
2.3 setattr(obj, name_str, value)
setattr(obj, name_str, value)为obj对象设置一个以name_str为名,以value具体只的方法或者属性。用于设置属性值,若属性不存在,则先创建再赋值:
2.4 delattr(obj, name_str)
delattr用于删除obj对象中的name_str方法或者属性,和setattr函数作用相反,属性必须存在,否则会抛出AttributeError的异常:
2、反射的使用场景
反射是在只知道类名或者函数名的情况下调用其对应的函数。广泛应用于:
- 配置文件,动态导入模块/方法
- 路由
2.1 动态导入模块/方法
import模块还有另一种用法: __import__(module_str),参数module_str为需要导入的模块名
预先定义class.py模块,里面有很多的类,比如Person,Dog等:
class Person(object):
CONST = "Say Hello"
def walk(self):
print("I can walk")
def eat(self, food=None):
print("can eat: {}".format(food))
class Dog(object):
CONST = "Say wangwang"
def walk(self):
print("I can walk")
def eat(self, food=None):
print("can eat: {}".format(food))
现在我们想使用person.py中的类及其方法,但是现在不确定具体使用哪个对象,想让用户自己输入来选择,可以这样实现:
module_name = input("请输入模块名:")
class_name = input("请输入类名:")
func_name = input("请输入函数名:")
module = __import__(module_name) # 通过输入字符串导入你所想导入的模块
if hasattr(module, class_name):
cls = getattr(module, class_name, None) # 从导入模块中找到你需要类
if cls:
instance = cls() # 生成类的实例
if hasattr(instance, func_name):
func = getattr(instance, func_name) # 从导入模块中找到你需要类方法
func() # 执行类中的方法
else:
print("没有找到方法")
else:
print("没有找到类")
else:
print("没有找到模块")
运行结果如下:
2.2 路由
在web API设计或者API自动化时,我们通常需要根据URL找到正确的入口函数。
web设计中,url路由
例如,需要根据url执行login操作,示例目录结构如下:
先定义登陆模块accout.py:
def login():
print("login")
def logout():
print("logout")
url路由模块index.py
# 输入url,例如accout/login
data = input("input url:")
# 获取模块名和函数名
data_array = data.split("/")
# 导入模块
userspace = __import__("backend." + data_array[0])
accout = getattr(userspace, data_array[0])
func = getattr(accout, data_array[1])
func()
运行输出:
input url:accout/login
login
另一种更常用实现方式:
url = input("input url:") # 输入www.xxx.com/accout/fun,则返回fun的结果,不存在则返回404
target_module, target_func = url.split('/')[0:-1]
module = __import__('backend.' + target_module, fromlist=True) # 采用fromlist=True这种方式就能导入backend.accout,
func_name = url.split('/')[-1]
if hasattr(module, target_func): # 判断module是否含有target_func成员
target_func = getattr(module, target_func) # 获取func_name的引用
target_func() # 执行函数
通过不同的请求方式,调用不同的函数,使用反射之前:
class RestAPI(object):
def get(self, url):
res = requests.get(url)
response = res.text
return response
def post(self, url):
res = requests.post(url)
response = res.text
return response
if __name__ == '__main__':
url = "http://www.baidu.com/"
method = input("请求方法>>>:")
h = RestAPI()
if method.upper() == 'GET':
result = h.get(url)
elif method.upper() == 'POST':
result = h.post(url)
else:
print('Method no allow!')
使用反射优化之后:
class RestAPI(object):
def get(self, url):
res = requests.get(url)
response = res.text
return response
def post(self, url):
res = requests.post(url)
response = res.text
return response
if __name__ == '__main__':
url = "http://www.baidu.com/"
method = input("请求方法>>>:")
h = RestAPI()
if hasattr(h, method):
func = getattr(h, method) # 获取method函数指针
res = func(url)
print(res)
else:
print("method not allow")
参考文献:
https://blog.csdn.net/perfect1t/article/details/80825372
http://www.cnblogs.com/yyyg/p/5554111.html
https://www.cnblogs.com/huxi/archive/2011/01/02/1924317.html
https://blog.csdn.net/biheyu828/article/details/89375332