如何重写Python导入?
我正在开发一个叫做 pypreprocessor 的工具,它是一个预处理器,可以处理类似C语言的指令。我已经让它像传统的预处理器那样工作(它会自我消耗,并实时执行处理后的代码),但有一个问题,就是它会破坏库的导入。
问题是:这个预处理器会遍历文件,处理内容,然后输出到一个临时文件,最后执行这个临时文件。被导入的库需要稍微不同的处理,因为它们不是被执行的,而是被加载并让调用模块可以使用。
我需要做的是:在导入的过程中打断这个导入(因为预处理器是在导入的中间运行的),加载处理后的代码作为一个临时模块,并用这个临时模块替换掉原来的导入,以此来欺骗调用脚本,让它相信这个临时模块就是原来的模块。
我到处寻找,但到目前为止还没有找到解决办法。
这个 Stack Overflow 的问题是我见过的最接近答案的:在Python中覆盖命名空间
这是我目前的代码。
# Remove the bytecode file created by the first import
os.remove(moduleName + '.pyc')
# Remove the first import
del sys.modules[moduleName]
# Import the postprocessed module
tmpModule = __import__(tmpModuleName)
# Set first module's reference to point to the preprocessed module
sys.modules[moduleName] = tmpModule
moduleName 是原始模块的名字,而 tmpModuleName 是处理后代码文件的名字。
奇怪的是,这个解决方案运行得完全正常,就好像第一个模块已经正常加载完成;除非你删除最后一行,否则会出现找不到模块的错误。
希望 Stack Overflow 上有人对导入的知识比我多,因为这个问题让我很困惑。
注意:我只会奖励一个解决方案,或者如果在Python中这不可能的话,给出最详细的解释,说明为什么这是不可能的。
更新:对任何感兴趣的人来说,这里是有效的代码。
if imp.lock_held() is True:
del sys.modules[moduleName]
sys.modules[tmpModuleName] = __import__(tmpModuleName)
sys.modules[moduleName] = __import__(tmpModuleName)
‘imp.lock_held’ 这一部分用来检测模块是否作为库被加载。接下来的几行代码完成了其余的工作。
3 个回答
如果你想定义一种不同的导入行为,或者完全改变导入的过程,你需要写一些导入钩子。可以参考一下 PEP 302。
举个例子,
import sys
class MyImporter(object):
def find_module(self, module_name, package_path):
# Return a loader
return self
def load_module(self, module_name):
# Return a module
return self
sys.meta_path.append(MyImporter())
import now_you_can_import_any_name
print now_you_can_import_any_name
它的输出是:
<__main__.MyImporter object at 0x009F85F0>
简单来说,它返回了一个新的模块(这个模块可以是任何对象),在这个例子中就是它自己。你可以用它来改变导入的行为,比如在导入 xxx
时返回 processe_xxx
。
在我看来:Python 不需要预处理器。你想实现的任何功能都可以在 Python 本身中完成,因为 Python 非常灵活。例如,在调试的例子中,文件顶部加上
debug = 1
然后在后面加上
if debug:
print "wow"
这样有什么问题呢?
这回答你的问题了吗?第二个导入就解决了这个问题。
Mod_1.py
def test_function():
print "Test Function -- Mod 1"
Mod_2.py
def test_function():
print "Test Function -- Mod 2"
Test.py
#!/usr/bin/python
import sys
import Mod_1
Mod_1.test_function()
del sys.modules['Mod_1']
sys.modules['Mod_1'] = __import__('Mod_2')
import Mod_1
Mod_1.test_function()