制作整个命名空间的副本?

时间:2016-05-17 16:38:19

标签: python

我想用动态构造的版本替换一些函数来复制整个命名空间。

换句话说,从命名空间(import tensorflow as tf)开始,我想复制它,用我自己的版本替换一些函数,并更新所有符号的__globals__以保持在新命名空间。这需要以依赖的拓扑顺序完成。

我开始做类似here的事情,但现在我开始怀疑我是否正在重新发明轮子。需要注意处理系统模块中的循环依赖,需要以不同方式更新函数/类型/对象等。

任何人都可以指向解决类似任务的现有代码吗?

3 个答案:

答案 0 :(得分:5)

要在导入一组函数的第二个实例时修补一组函数,可以覆盖标准的Python导入钩子并在导入时直接应用这些补丁。这将确保没有其他模块可以看到任何模块的未修补版本,因此即使它们直接通过名称从另一个模块导入函数,它们也只能看到修补的函数。这是一个概念验证实现:

from __future__ import print_function

import math
import random

def patched_log(x):
    print('Computing log({:g})'.format(x))
    return math.log(x)

patches = {'math.log': patched_log}
cloned_modules = clone_modules(patches, ['random'])
new_math = cloned_modules['math']
new_random = cloned_modules['random']
print('Original log:         ', math.log(2.0))
print('Patched log:          ', new_math.log(2.0))
print('Original expovariate: ', random.expovariate(2.0))
print('Patched expovariate:  ', new_random.expovariate(2.0))

这里有一些测试代码:

Computing log(4)
Computing log(4.5)
Original log:          0.69314718056
Computing log(2)
Patched log:           0.69314718056
Original expovariate:  0.00638038735379
Computing log(0.887611)
Patched expovariate:   0.0596108277801

测试代码有这个输出:

random

前两行输出结果来自these two lines in random,它们在导入时执行。这表明math立即看到修补后的功能。输出的其余部分表明原始randomlog仍然使用未修补版本的list view,而克隆模块都使用修补版本。

覆盖导入钩子的一种更简洁的方法可能是使用PEP 302中定义的元导入钩子,但提供该方法的完整实现超出了StackOverflow的范围。

答案 1 :(得分:3)

您可以欺骗Python导入第二次要复制的所有内容,而不是尝试复制模块的内容并修补其中的所有内容以使用正确的全局变量。这将为您提供所有模块的新初始化副本,因此它不会复制模块可能具有的任何全局状态(不确定您是否需要它)。

import importlib
import sys

def new_module_instances(module_names):
    old_modules = {}
    for name in module_names:
        old_modules[name] = sys.modules.pop(name)
    new_modules = {}
    for name in module_names:
        new_modules[name] = importlib.import_module(name)
    sys.modules.update(old_modules)
    return new_modules

请注意,我们首先从sys.modules删除要替换的所有模块,因此它们都会再次导入,并且这些模块之间的依赖关系会自动正确设置。在函数结束时,我们恢复原始状态sys.modules,所以其他所有内容都会继续查看这些模块的原始版本。

以下是一个例子:

>>> import logging.handlers
>>> new_modules = new_module_instances(['logging', 'logging.handlers'])
>>> logging_clone = new_modules['logging']
>>> logging
<module 'logging' from '/usr/lib/python2.7/logging/__init__.pyc'>
>>> logging_clone
<module 'logging' from '/usr/lib/python2.7/logging/__init__.pyc'>
>>> logging is logging_clone
False
>>> logging is logging.handlers.logging
True
>>> logging_clone is logging_clone.handlers.logging
True

最后三个表达式显示两个版本的日志记录是不同的模块,handlers模块的两个版本都使用logging模块的正确版本。

答案 2 :(得分:1)

在我看来,你可以轻松地做到这一点:

import imp, string

st = imp.load_module('st', *imp.find_module('string')) # copy the module

def my_upper(a):
    return "a" + a

def my_lower(a):
    return a + "a"

st.upper = my_upper
st.lower = my_lower


print string.upper("hello") # HELLO
print string.lower("hello") # hello

print st.upper("hello") # ahello
print st.lower("hello") # helloa

当您致电st.upper("hello")时,会产生"hello"

所以,你真的不需要搞乱全局变量。