从模块导入中撤消*

时间:2013-03-07 18:44:44

标签: python namespaces python-import

我有一个代码库,我正在清理前一个开发人员的一些混乱的决定。通常,他做过类似的事情:

from scipy import *
from numpy import *

...当然,这会污染名称空间,并且很难分辨模块中属性的来源。

有没有办法让Python分析并为我解决这个问题?有没有人为此做过一个实用程序?如果没有,这样的实用程序怎么可能会被制作出来?

4 个答案:

答案 0 :(得分:3)

是。删除导入并在模块上运行linter。

我建议使用flake8,尽管它也可能会产生很多关于样式错误的噪音。

仅仅删除导入并尝试运行代码可能是不够的,因为在您使用正确的输入运行恰当的代码行之前,不会引发许多名称错误。 linter将通过解析来分析代码,并且无需运行代码即可检测潜在的NameError

这一切都假设没有可靠的单元测试,或者测试没有提供足够的覆盖率。

在这种情况下,如果有多个 from module import *行,那么您需要弄清楚每个缺少的名称是什么模块提供该名称会更加痛苦。这将需要手动工作,但您只需在python解释器中导入模块并测试是否在该模块上定义了缺少的名称:

>>> import scipy, numpy
>>> 'loadtxt' in dir(numpy)
True

在这种特定情况下,您需要考虑到numpyscipy模块之间存在重叠;对于在两个模块中定义的任何名称,导入的模块最后获胜。

请注意,将任何 from module import *行留在原位意味着linter将无法检测出可能引发NameErrors的名称!

答案 1 :(得分:3)

我认为PurityLake和Martijn Pieters的辅助手动解决方案可能是最好的方法。但是以编程方式执行此操作并不是不可能

首先,您需要获取模块字典中可能在代码中使用的所有名称的列表。我假设你的代码没有直接调用任何 dunder 函数等。

然后,你需要迭代它们,使用inspect.getmodule()找出每个对象最初定义的模块。我假设你没有使用任何双重的from foo import * -ed。列出numpyscipy模块中定义的所有名称。

现在您可以获取该输出,只需将每个foo替换为numpy.foo

所以,把它放在一起,就像这样:

for modname in sys.argv[1:]:
    with open(modname + '.py') as srcfile:
        src = srcfile.read()
    src = src.replace('from numpy import *', 'import numpy')
    src = src.replace('from scipy import *', 'import scipy')
    mod = __import__(modname)
    for name in dir(mod):
        original_mod = inspect.getmodule(getattr(mod, name))
        if original_mod.__name__ == 'numpy':
            src = src.replace(name, 'numpy.'+name)
        elif original_mod.__name__ == 'scipy':
            src = src.replace(name, 'scipy.'+name)
    with open(modname + '.tmp') as dstfile:
        dstfile.write(src)
    os.rename(modname + '.py', modname + '.bak')
    os.rename(modname + '.tmp', modname + '.py')

如果其中任何一个假设是错误的,那么改变代码并不困难。此外,您可能希望使用tempfile.NamedTemporaryFile和其他改进,以确保不会意外地使用临时文件覆盖事物。 (我只是不想处理跨平台编写内容的麻烦;如果你没有在Windows上运行,那很容易。)并加入一些错误处理,很明显,可能还有一些报告。

答案 2 :(得分:0)

我现在做了一个小实用工具,我称之为'dedazzler'。它将找到“来自模块导入*”的行,然后展开目标模块的“目录”,替换行。

运行它之后,你仍然需要运行一个linter。这是代码中特别有趣的部分:

import re

star_match = re.compile('from\s(?P<module>[\.\w]+)\simport\s[*]')
now = str(time.time())
error = lambda x: sys.stderr.write(x + '\n')

def replace_imports(lines):
    """
    Iterates through lines in a Python file, looks for 'from module import *'
    statements, and attempts to fix them.
    """
    for line_num, line in enumerate(lines):
        match = star_match.search(line)
        if match:
            newline = import_generator(match.groupdict()['module'])
            if newline:
                lines[line_num] = newline
    return lines

def import_generator(modulename):
    try:
        prop_depth = modulename.split('.')[1:]
        namespace = __import__(modulename)
        for prop in prop_depth:
            namespace = getattr(namespace, prop)
    except ImportError:
        error("Couldn't import module '%s'!" % modulename)
        return
    directory = [ name for name in dir(namespace) if not name.startswith('_') ]
    return "from %s import %s\n"% (modulename, ', '.join(directory))

我在这里用一个更有用的独立实用程序表单来维护它:

https://github.com/USGM/dedazzler/

答案 3 :(得分:-1)

好吧,这是我认为你可以做的,打破程序。删除导入并注意所做的错误。然后只导入你想要的模块,这可能需要一段时间,但这是我知道这样做的唯一方法,如果有人知道帮助的工具,我会很高兴地感到惊讶

编辑: 啊是的,一个短绒,我没有想到这一点。