从模块导入反转 *
Posted
技术标签:
【中文标题】从模块导入反转 *【英文标题】:Reversing from module import * 【发布时间】:2013-02-23 03:01:51 【问题描述】:我有一个代码库,我正在清理以前开发人员的一些混乱决定。他经常这样做:
from scipy import *
from numpy import *
...这当然会污染名称空间,并且很难分辨模块中的属性最初来自哪里。
有什么方法可以让 Python 为我分析和解决这个问题?有没有人为此做了一个实用程序?如果没有,如何制作这样的实用程序?
【问题讨论】:
我对你有感觉。希望你能找到一些不错的工具。 (+1) 更好的是,我希望你写一个很好的工具(无论是否基于我的回答)并在 PyPI 上发布,所以如果我需要这样的东西,我不必自己做. :) 也看到这个问题:Is there an IDE/utility to refactor Python * imports to use standard module.member syntax? 【参考方案1】:我认为 PurityLake 和 Martijn Pieters 的辅助手动解决方案可能是最好的方法。但以编程方式执行此操作并非不可能。
首先,您需要获取模块字典中可能在代码中使用的所有名称的列表。我假设您的代码没有直接调用任何 dunder 函数等。
然后,您需要遍历它们,使用inspect.getmodule()
找出每个对象最初定义在哪个模块中。我假设您没有使用任何被加倍from foo import *
-ed 的东西。列出在 numpy
和 scipy
模块中定义的所有名称。
现在您可以获取该输出并将每个 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 上运行,这很容易。)显然,添加一些错误处理,可能还有一些报告。
【讨论】:
我认为这不会奏效。名为my_foo
的变量呢?突然间你得到my_numpy.foo
。哎呀。当然,如果有一个合适的解析器(我在想ast
),你可能会这样做。
这可能是我想要的更多。这家伙偶尔有模块,其中进行了五种不同的通配符导入。这是精神错乱。这里的搜索和替换有点危险,因为它做了一些假设,但这个想法让我朝着正确的方向前进。
最终,这是一个对整个社区有用的工具的合理开始。我也想知道这种事情会如何与__all__
交互。要真正做到正确,您可能需要过滤掉 module.__all__
中不存在的内容(如果存在)。
@mgilson:你是对的,如果你有 both my_foo
和 foo
,就没有办法在不破坏另一个的情况下修复一个。你甚至不需要一个合适的解析器,只需要一个词法分析器,这要简单得多。但你是对的,我们已经在ast
中拥有一个合适的解析器,所以也许这就是要走的路。
@mgilson:我最初的想法是我不会这样做——我会依赖 linter 和单元测试以及手动编辑,就像 Martijn Pieters 的回答一样。但是既然你提到了它,如果有人接受它并对其进行改进,它可能是一个有用的通用工具。【参考方案2】:
是的。删除导入并在模块上运行 linter。
我建议使用flake8
,尽管它也可能会产生很多关于样式错误的噪音。
仅仅删除导入并尝试运行代码可能是不够的,因为在您使用正确的输入运行正确的代码行之前,不会引发许多名称错误。相反,linter 将通过解析来分析代码,并且无需运行代码即可检测潜在的NameError
s。
这一切都假定没有可靠的单元测试,或者测试没有提供足够的覆盖率。
在这种情况下,如果有 多个 from module import *
行,它会变得更加痛苦,因为您需要为每个缺失的名称找出哪个模块提供了该名称。这将需要手动工作,但您可以简单地在 python 解释器中导入模块并测试是否在该模块上定义了缺少的名称:
>>> import scipy, numpy
>>> 'loadtxt' in dir(numpy)
True
您确实需要考虑到在这种特定情况下,numpy
和 scipy
模块之间存在重叠;对于两个模块中定义的任何名称,最后导入的模块获胜。
请注意,保留 any from module import *
行意味着 linter 将无法检测到哪些名称可能引发 NameErrors!
【讨论】:
如果我删除导入,这将如何告诉我名称来自哪个模块?它不会只是对现在未列出的所有属性的名称错误吗? @Kelketek:是的,你必须弄清楚每个模块应该来自哪个模块。幸运的是,这并不难。 如果一次删除一个,这可能会更容易。 大概,我应该先运行 linter 以获取它可能发现的任何一般问题,解决它们,删除一个导入行,再次运行它以查看哪些名称作为名称错误弹出,然后添加它们到“从”导入,然后对每个通配符导入重复该过程? @MartijnPieters:这实际上可能会非常痛苦。以 OP 的from scipy import *
和 from numpy import *
为例,一些函数的语义,例如 log10
将取决于最后一个导入(它们存在于两个模块中,但行为不同)。见***.com/questions/6200910/…【参考方案3】:
我现在制作了一个小实用程序来执行此操作,我称之为“dedazzler”。它会找到'from module import *'的行,然后展开目标模块的'dir',替换这些行。
运行它之后,你仍然需要运行一个 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/
【讨论】:
【参考方案4】:好的,这就是我认为你可以做的,打破程序。删除导入并注意所犯的错误。然后只导入你想要的模块,这可能需要一段时间,但这是我知道的唯一方法,如果有人知道有帮助的工具,我会很高兴
编辑: 啊,是的,一个短绒,我没想到。
【讨论】:
以上是关于从模块导入反转 *的主要内容,如果未能解决你的问题,请参考以下文章