Python反射,可以理解为利用字符串的形式去操作对象中的成员属性和方法。
考虑有这么一个场景,根据用户输入的url的不同,调用不同的函数,实现不同的操作:
# commons.py
def login():
print('登录页面!')
def logout():
print('退出页面!')
def index():
print('主页面!')
# index.py
import commons
def run():
inp = input("请输入您想访问页面的URL:").strip()
if inp == "login":
commons.login()
elif inp == "logout":
commons.logout()
elif inp == "index":
commons.index()
else:
print("404")
if __name__ == '__main__':
run()
我们运行index.py,输入:index,页面结果如下:
$ python3 index.py
请输入您想访问页面的URL:index
主页面!
这就实现了一个简单的WEB路由功能,根据不同的url,执行不同的函数,获得不同的页面。那现在如果我的网站内容变多了,在commons.py中有100个页面操作,那么相对应的我在index.py中也要使用if else对这100个页面函数进行手动指定。显然这是不可能的!那么怎么破?这个时候Python反射特性就可以排上用场了。
# index.py
import commons
def run():
inp = input('请输入您想访问页面的URL:').strip()
if hasattr(commons, inp):
func = getattr(commons, inp)
func()
else:
print('404')
if __name__ == "__main__":
run()
现实使用环境中,页面处理函数往往被分类放置在不同目录的不同模块中。比如,现在我又新增了一个account.py这个用户管理类的文件,也需要导入到首页以备调用。
__import__方法会根据字符串参数,动态的导入同名的模块。
# account.py
def add_user():
print('添加用户')
def del_user():
print('删除用户')
# commons.py
def login():
print('登录页面!')
def logout():
print('退出页面!')
def index():
print('主页面')
# index.py
def run():
inp = input('请输入您想访问页面的URL:').strip()
m, f = inp.split('/')
obj = __import__(m)
if hasattr(obj, f):
func = getattr(obj, f)
func()
else:
print('404')
if __name__ == "__main__":
run()
这里有个小瑕疵!如果我的项目进一步细化分工,现在又多了一层目录结构,如下所示:
|- index.py
|- commons.py
|- account.py
|- lib
|- __init__.py
|- connectdb.py
connectdb.py有如下方法:
def mysql():
print('连接成功!')
这个问题怎么解决呢?__import__函数中有一个fromlist参数,源码解释说,如果在一个包中导入一个模块,这个参数如果为空,则return这个包对象,如果这个参数不为空,则返回包下面指定的模块对象,于是做出如下修改:
# index.py
def getf(m, f):
if hasattr(m, f):
func = getattr(m, f)
func()
else:
print('404')
def run():
if len(inp.split('/')) == 2:
m, f = inp.split('/')
obj = __import__(m)
getf(obj, f)
elif len(inp.split('/')) == 3:
p, m, f = inp.split('/')
obj = __import__(p + '.' + m, fromlist=True)
getf(obj, f)
else:
print('404')
if __name__ == "__main__":
inp = input('请输入您想访问页面的URL:')
run()
现在运行测试:
$ python3 index.py
请输入您想访问页面的URL:commons/index
主页面!
$ python3 index.py
请输入您想访问页面的URL:lib/connectdb/mysql
连接成功!
进一步改进:
# index.py
def getf(m, f):
if hasattr(m, f):
func = getattr(m, f)
func()
else:
print('404')
def run():
t = inp.split("/")
f = t[-1]
p = '.'.join(t[0:-1])
obj = __import__(p, fromlist=True)
getf(obj, f)
else:
print('404')
if __name__ == "__main__":
inp = input('请输入您想访问页面的URL:')
run()
exec和eval也有它的舞台,在web框架里也经常被使用。
Reference: 运维那点事