如何重写Python导入?

41 投票
3 回答
48572 浏览
提问于 2025-04-15 23:47

我正在开发一个叫做 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 个回答

0

在Python 2中,有一个叫做imputil的模块,似乎能提供你需要的功能,但在Python 3中已经被移除了。这个模块的文档不是很好,但里面有个示例部分,展示了如何替换标准的导入功能。

对于Python 3,有一个叫做importlib的模块(在Python 3.1中引入),它包含了可以以各种方式修改导入功能的函数和类。这个模块应该适合将你的预处理器接入导入系统。

15

如果你想定义一种不同的导入行为,或者完全改变导入的过程,你需要写一些导入钩子。可以参考一下 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"

这样有什么问题呢?

47

这回答你的问题了吗?第二个导入就解决了这个问题。

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()

撰写回答