我有一个代码库,我正在清理以前开发人员的一些混乱决定。他经常做这样的事情:
from scipy import *
from numpy import *
...这当然会污染名称空间,并且很难分辨模块中的属性最初来自何处。
有没有办法让 Python 为我分析和解决这个问题?有没有人为此做了一个实用程序?如果没有,如何制作这样的实用程序?
我有一个代码库,我正在清理以前开发人员的一些混乱决定。他经常做这样的事情:
from scipy import *
from numpy import *
...这当然会污染名称空间,并且很难分辨模块中的属性最初来自何处。
有没有办法让 Python 为我分析和解决这个问题?有没有人为此做了一个实用程序?如果没有,如何制作这样的实用程序?
是的。删除导入并在模块上运行 linter。
我建议使用flake8
,尽管它也可能会产生很多关于样式错误的噪音。
仅仅删除导入并尝试运行代码可能是不够的,因为在您使用正确的输入运行正确的代码行之前不会引发许多名称错误。相反,linter 将通过解析来分析代码,并将检测潜在NameError
的 s 而无需运行代码。
这一切都假定没有可靠的单元测试,或者测试没有提供足够的覆盖率。
在这种情况下,如果有多 from module import *
行,它会变得更加痛苦,因为您需要为每个缺失的名称找出哪个模块提供了该名称。这将需要手动工作,但您可以简单地在 python 解释器中导入模块并测试是否在该模块上定义了缺少的名称:
>>> import scipy, numpy
>>> 'loadtxt' in dir(numpy)
True
您确实需要考虑到在这种特定情况下,numpy
和scipy
模块之间存在重叠;对于两个模块中定义的任何名称,最后导入的模块获胜。
请注意,保留任何 from module import *
行意味着 linter 将无法检测到哪些名称可能引发 NameErrors!
我认为 PurityLake 和 Martijn Pieters 的辅助手动解决方案可能是最好的方法。但是以编程方式执行此操作并非不可能。
首先,您需要获取模块字典中可能在代码中使用的所有名称的列表。我假设您的代码没有直接调用任何dunder函数等。
然后,您需要遍历它们,inspect.getmodule()
用于找出每个对象最初定义在哪个模块中。我假设您没有使用任何被双重化的东西from foo import *
。列出在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 上运行,这很容易。)显然,添加一些错误处理,可能还有一些报告。
我现在制作了一个小实用程序来执行此操作,我称之为“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))
我在这里以更有用的独立实用程序形式维护它:
好的,这就是我认为你可以做的,打破程序。删除导入并注意所犯的错误。然后只导入你想要的模块,这可能需要一段时间,但这是我知道的唯一方法,如果有人知道有帮助的工具,我会很高兴
编辑:啊,是的,一个短绒,我没有想到这一点。