Python 模块动态加载技术
2016-04-02 23:54
836 查看
Python 模块动态加载技术
C 语言中可以使用 dlopen,dlsym 和 dlclose 让程序在运行过程中按需加载和卸载动态库。Python 也支持这种方式,使用模块动态加载技术,我们可以把程序的配置文件写成可运行的 python 程序,在程序运行过程中可以动态去更新配置。当然也可以将 python 脚本作为业务逻辑加载到正在运行的主程序中,而不用重启服务。作者在个人项目 pyed 中使用了这种技术,本文对个人研究和使用这种技术的一个总结。如有问题,欢迎大家讨论。
在 Python 中执行代码
python 提供了 exec 用于在程序中执行一段 python 代码,官方说明:exec_stmt ::= "exec" or_expr ["in" expression ["," expression]]
该语句可以使用 exec() 函数进行替代。来看一个简单的例子:
>>> exec "print('Hello World')" Hello World >>>
这种使用方式,在程序中其实作用不大,我们使用动态加载,一般是希望将一个模块中的某个变量或函数按需引入到正在执行的程序中,而不仅仅是去执行一下,打印一句 “Hello World”,exec 中的 in 解决了这个问题。
in 的作用是将执行代码中的变量,函数或者类放入到一个字典中,这里再来看一个例子:
>>> exec "a=100" in tmp >>> print tmp {'__builtins__': ..., 'a': 100} >>>
上面的语句等效于:
exec("a=100", tmp)
执行结果中,tmp 除了我们给定的一个 a 变量,赋值为 100 外,还有一个 __builtins__ 成员,内容很多,这里使用 … 替代了实际的内容。如果要访问 a 的值,只需要像操作字典一样就行了:
>>> print tmp["a"] 100 >>>
简单的模块加载
简单模块加载库
按照上面的思路,我们构造了一个模块import traceback class loader(object): def __init__(self): pass def load(self, path): try: tmp = {} exec open(path).read() in tmp return tmp except: print("Load module [path %s] error: %s" % (path, traceback.format_exc())) return None
加载配置文件
有一个配置文件 test.conf:$ cat test.conf addr="127.0.0.1" port=2539 $
使用以下代码加载它:
load = loader() m = load.load("test.conf") addr = m["addr"] port = m["port"] print addr + ":" + str(port)
执行结果:
$ python loader.py 127.0.0.1:2539 $
加载和执行函数
如果要执行加载模块(test.py)中的函数:def greeting(name): print "Hello", name
使用以下代码加载它:
load = loader() m = load.load("test.py") func = m["greeting"] func("World")
执行结果:
$ python loader.py Hello World $
加载和使用模块中的类
按照上面的思路,如果加载的模块是一个类,其实调用方式也是大同小异的。修改 test.py
class test(object): def __init__(self): pass def greeting(self, name): print "Hello", name
使用以下代码加载它:
load = loader() m = load.load("test.py") c = m["test"] print c print dir(c) t = c() t.greeting("World")
执行结果:
$ python loader.py <class 'test'> ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'greeting'] Hello World $
从上面可以看到 m[“test”] 是一个 class 类型,我们可以使用它创建类的实例,并调用实例方法
加载的模块引入了其它模块
如果在加载的模块中导入了其它模块,调用方法也是不变的。我们引入一个 test1,继承上例中的 test:from test import test class test1(test): def __init__(self): test.__init__(self)
使用以下代码加载它:
load = loader() m = load.load("subtest.py") c = m["test1"] print c print dir(c) t = c() t.greeting("World")
执行结果:
$ python loader.py <class 'test1'> ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'greeting'] Hello World $
改进模块加载
上一节介绍了使用 exec … in … 的方式动态去加载模块。完成后可以直接使用返回的字典,访问模块中的变量,函数和类。但是从习惯上,我们更习惯使用模块去调用模块中的变量,函数和类,按此思路,我们对前面的模块加载器进行修改。新的模块加载器
import traceback, types class loader(object): def __init__(self): pass def load(self, name, path): try: m = types.ModuleType(name) exec open(path).read() in m.__dict__ return m except: print("Load module [path %s] error: %s" % (path, traceback.format_exc())) return None
这里使用 types.ModuleType 来构造一个模块 m,将 exec 生成的字典放入到 m.__dict__。这样就生成了一个简单的模块
使用新的模块加载器
待加载的模块:def test(): s = 0 for i in range(1000000): s += i print s
执行逻辑:
load = loader() m = load.load("test", "test.py") print m print m.__dict__ m.test()
执行结果:
$ python loader.py <module 'test' (built-in)> {'__builtins__': ..., '__name__': 'test', 'test': <function test at 0x1007f7398>, '__doc__': None} 499999500000 $
从执行结果,我们可以看到使用新的模块加载器,我们得到的是一个 module 类型的实例,其 __dict__ 中包含了 test 函数,我们可以直接使用 m.test() 调用该函数
相关文章推荐
- Python动态类型的学习---引用的理解
- Python3写爬虫(四)多线程实现数据爬取
- 垃圾邮件过滤器 python简单实现
- 下载并遍历 names.txt 文件,输出长度最长的回文人名。
- install and upgrade scrapy
- Scrapy的架构介绍
- Centos6 编译安装Python
- 使用Python生成Excel格式的图片
- 让Python文件也可以当bat文件运行
- [Python]推算数独
- Python中zip()函数用法举例
- Python中map()函数浅析
- Python将excel导入到mysql中
- Python在CAM软件Genesis2000中的应用
- 使用Shiboken为C++和Qt库创建Python绑定
- FREEBASIC 编译可被python调用的dll函数示例
- Python 七步捉虫法