python根据字符串动态加载模块和类
python作为一种动态解释型语言,在实现各种框架方面具有很大的灵活性。
最近在研究python web框架,发现各种框架中需要显示的定义各种路由和Handler的映射,如果想要实现并维护复杂的web应用,灵活性非常欠缺。
如果内容以“约定即配置”的方式完成handler和路由的映射操作,可以大大增加python web框架的灵活性,此时动态映射是必不可少的。
在java mvc框架中,可利用反射机制,实现动态映射,而python也可以利用本身的特性,实现动态映射。
1、获得指定package对象(其实也是module)
为了遍历此包下的所有module以及module中的controller,以及controller中的function,必须首先获得指定的package引用,可使用如下方法:
import(name, globals={}, locals={}, fromlist=)加载package,输入的name为package的名字字符串
controller_package=import('com.project.controller',{},{},["models"])
ps:直接使用import('com.project.controller')是无法加载期望的package的,只会得到顶层的package-‘com’,除非使用如下方法迭代获得。
def my_import(name):
mod = __import__(name)
components = name.split('.') for comp in components[1:]:
mod = getattr(mod, comp) return mod
官方文档描述如下
When the <var style="margin: 0px; padding: 0px;">name</var> variable is of the form package.module
, normally, the top-level package (the name up till the first dot) is returned,not the module named by <var style="margin: 0px; padding: 0px;">name</var>. However, when a non-empty <var style="margin: 0px; padding: 0px;">fromlist</var> argument is given, the module named by <var style="margin: 0px; padding: 0px;">name</var> is returned. This is done for compatibility with the bytecode generated for the different kinds of import statement; when using "<tt class="samp" style="margin: 0px; padding: 0px;">import spam.ham.eggs</tt>", the top-level package <tt class="module" style="margin: 0px; padding: 0px;">spam</tt> must be placed in the importing namespace, but when using "<tt class="samp" style="margin: 0px; padding: 0px;">from spam.ham import eggs</tt>", the spam.ham
subpackage must be used to find the eggs
variable. As a workaround for this behavior, use <tt class="function" style="margin: 0px; padding: 0px;">getattr()</tt> to extract the desired components.
2、遍历指定package下的所有module
为了获得controller_package下的module,可先使用dir(controller_package)获得controller_package对象范围内的变量、方法和定义的类型列表。然后通过
for name in dir(controller_package):
var=getattr(controller_package,name)
print type(var)
遍历此package中的所有module,并根据约定的controller文件命名方式,发现约定的module,并在module中发现约定好的class。
如果name代表的变量不是方法或者类,type(var)返回的值为"<type 'module'>"。
3、遍历指定module中的class
依然使用dir(module)方法,只不过type(var)返回的值为"<type 'classobj'>"。
4、遍历指定class中的method
依然使用dir(class)方法,只不过type(var)返回的值为"<type 'instancemethod'>"或者<type 'function'>,第一种为对象方法,第二种为类方法。
5、遍历指定method中的参数名
使用method的func_code.co_varnames属性,即可获得方法的参数名列表。
以上方法,适合在python web运行前,对所有的controller提前进行加载,如果需要根据用户的请求再动态发现controller,依然可以使用上面的方法完成,只是会更加的简单,需要需找的controller路径已知,只需递归获得controller的引用即可,再实例化,根据action名,执行执行的action。
四个内置函数
1. getattr()函数是Python自省的核心函数,具体使用大体如下:
class A:
def __init__(self):
self.name = 'zhangjing'
self.age='24'
def method(self):
print"method print"
Instance = A()
print getattr(Instance , 'name, 'not find') #如果Instance 对象中有属性name则打印self.name的值,否则打印'not find'
print getattr(Instance , 'age', 'not find') #如果Instance 对象中有属性age则打印self.age的值,否则打印'not find'
print getattr(a, 'method', 'default') #如果有方法method,否则打印其地址,否则打印default
print getattr(a, 'method', 'default')() #如果有方法method,运行函数并打印None否则打印default
2. hasattr(object, name)
说明:判断对象object是否包含名为name的特性(hasattr是通过调用getattr(ojbect, name)是否抛出异常来实现的)
3. setattr(object, name, value)
这是相对应的getattr()。参数是一个对象,一个字符串和一个任意值。字符串可能会列出一个现有的属性或一个新的属性。这个函数将值赋给属性的。该对象允许它提供。例如,setattr(x,“foobar”,123)相当于x.foobar = 123。
4. delattr(object, name)
与setattr()相关的一组函数。参数是由一个对象(记住python中一切皆是对象)和一个字符串组成的。string参数必须是对象属性名之一。该函数删除该obj的一个由string指定的属性。delattr(x, 'foobar')=del x.foobar
我们可以利用上述的四个函数,来对模块进行一系列操作.
r = hasattr(commons,xxx)判断某个函数或者变量是否存在
print(r)
setattr(commons,'age',18) 给commons模块增加一个全局变量age = 18,创建成功返回none
setattr(config,'age',lambda a:a+1) //给模块添加一个函数
delattr(commons,'age')//删除模块中某个变量或者函数
实例
基于反射机制模拟web框架路由
需求:比如我们输入:www.xxx.com/commons/f1,返回f1的结果。
# 动态导入模块,并执行其中函数
url = input("url: ")
target_module, target_func = url.split('/')
m = __import__('lib.'+target_module, fromlist=True)
inp = url.split("/")[-1] # 分割url,并取出url最后一个字符串
if hasattr(m,target_func): # 判断在commons模块中是否存在inp这个字符串
target_func = getattr(m,target_func) # 获取inp的引用
target_func() # 执行
else:
print("404")
#a.py
class b(){...}
#c.py
package = __import__('a')
temp_class = getattr(package,'b')
temp = temp_class() #b()
反射函数工具
def get_reflex_cls(name):
'''
映射函数
:param name: 类名
:return: 类的实例
'''
l_name = 'restapi.' + name.lower() + '.services' # 接口包路径
names = l_name.split('.')
try:
mod = __import__(l_name) # 获取指定路径package对象,但只会得到最顶层!
for n in names[1:]: # 循环遍历包路径
mod = getattr(mod, n)
cls = getattr(mod, name.capitalize()) # 获取指定文件下的类
clss = cls() # 创建实例
return clss
except Exception as e:
return False