返回脚本中使用的导入Python模块列表?
我正在写一个程序,目的是根据Python文件中导入的模块来对这些文件进行分类。因此,我需要扫描一堆.py文件,并返回它们导入了哪些模块。举个例子,如果我导入的某个文件有以下几行:
import os
import sys, gtk
我希望它返回:
["os", "sys", "gtk"]
我尝试使用了modulefinder,并写了:
from modulefinder import ModuleFinder
finder = ModuleFinder()
finder.run_script('testscript.py')
print 'Loaded modules:'
for name, mod in finder.modules.iteritems():
print '%s ' % name,
但是这返回的结果不仅仅是脚本中使用的模块。举个例子,在一个脚本中仅包含:
import os
print os.getenv('USERNAME')
从ModuleFinder脚本返回的模块是:
tokenize heapq __future__ copy_reg sre_compile _collections cStringIO _sre functools random cPickle __builtin__ subprocess cmd gc __main__ operator array select _heapq _threading_local abc _bisect posixpath _random os2emxpath tempfile errno pprint binascii token sre_constants re _abcoll collections ntpath threading opcode _struct _warnings math shlex fcntl genericpath stat string warnings UserDict inspect repr struct sys pwd imp getopt readline copy bdb types strop _functools keyword thread StringIO bisect pickle signal traceback difflib marshal linecache itertools dummy_thread posix doctest unittest time sre_parse os pdb dis
...而我只想要返回'os',因为这是脚本中使用的模块。
有没有人能帮我实现这个目标?
更新:我想澄清一下,我希望在不运行被分析的Python文件的情况下,仅仅扫描代码来完成这个。
13 个回答
你可以试试这个叫做 dis 的工具(这里有个双关的意思哦):
import dis
from collections import defaultdict
from pprint import pprint
statements = """
from __future__ import (absolute_import,
division)
import os
import collections, itertools
from math import *
from gzip import open as gzip_open
from subprocess import check_output, Popen
"""
instructions = dis.get_instructions(statements)
imports = [__ for __ in instructions if 'IMPORT' in __.opname]
grouped = defaultdict(list)
for instr in imports:
grouped[instr.opname].append(instr.argval)
pprint(grouped)
输出结果是
defaultdict(<class 'list'>,
{'IMPORT_FROM': ['absolute_import',
'division',
'open',
'check_output',
'Popen'],
'IMPORT_NAME': ['__future__',
'os',
'collections',
'itertools',
'math',
'gzip',
'subprocess'],
'IMPORT_STAR': [None]})
你导入的模块是 grouped['IMPORT_NAME']
。
最近我需要获取一个特定 Python 脚本的所有依赖项,我采取了一种不同于其他回答的方法。我只关心顶层模块的名字(比如,我想从 import foo.bar
中提取出 foo
)。
下面是使用 ast 模块 的代码:
import ast
modules = set()
def visit_Import(node):
for name in node.names:
modules.add(name.name.split(".")[0])
def visit_ImportFrom(node):
# if node.module is missing it's a "from . import ..." statement
# if level > 0 it's a "from .submodule import ..." statement
if node.module is not None and node.level == 0:
modules.add(node.module.split(".")[0])
node_iter = ast.NodeVisitor()
node_iter.visit_Import = visit_Import
node_iter.visit_ImportFrom = visit_ImportFrom
我用一个名为 foo.py
的 Python 文件进行测试,文件内容是:
# foo.py
import sys, os
import foo1
from foo2 import bar
from foo3 import bar as che
import foo4 as boo
import foo5.zoo
from foo6 import *
from . import foo7, foo8
from .foo12 import foo13
from foo9 import foo10, foo11
def do():
import bar1
from bar2 import foo
from bar3 import che as baz
我可以通过类似下面的方式获取 foo.py
中的所有模块:
with open("foo.py") as f:
node_iter.visit(ast.parse(f.read()))
print(modules)
这样就会得到以下输出:
set(['bar1', 'bar3', 'bar2', 'sys', 'foo9', 'foo4', 'foo5', 'foo6', 'os', 'foo1', 'foo2', 'foo3'])
在我看来,最好的办法是使用这个http://furius.ca/snakefood/工具包。这个工具的作者已经做了很多工作,不仅可以直接导入模块,还能利用AST(抽象语法树)来解析代码,找出一些在静态分析中可能会遗漏的运行时依赖。
我准备了一个命令示例来演示:
sfood ./example.py | sfood-cluster > example.deps
这个命令会生成一个包含每个独特模块的基本依赖文件。如果你想要更详细的信息,可以使用:
sfood -r -i ./example.py | sfood-cluster > example.deps
如果你想遍历一个树形结构并找到所有的导入,你也可以用代码来实现。请注意,这个例程中的AST部分是从snakefood的源代码中提取的,版权信息是:Copyright (C) 2001-2007 Martin Blais. All Rights Reserved.
import os
import compiler
from compiler.ast import Discard, Const
from compiler.visitor import ASTVisitor
def pyfiles(startPath):
r = []
d = os.path.abspath(startPath)
if os.path.exists(d) and os.path.isdir(d):
for root, dirs, files in os.walk(d):
for f in files:
n, ext = os.path.splitext(f)
if ext == '.py':
r.append([d, f])
return r
class ImportVisitor(object):
def __init__(self):
self.modules = []
self.recent = []
def visitImport(self, node):
self.accept_imports()
self.recent.extend((x[0], None, x[1] or x[0], node.lineno, 0)
for x in node.names)
def visitFrom(self, node):
self.accept_imports()
modname = node.modname
if modname == '__future__':
return # Ignore these.
for name, as_ in node.names:
if name == '*':
# We really don't know...
mod = (modname, None, None, node.lineno, node.level)
else:
mod = (modname, name, as_ or name, node.lineno, node.level)
self.recent.append(mod)
def default(self, node):
pragma = None
if self.recent:
if isinstance(node, Discard):
children = node.getChildren()
if len(children) == 1 and isinstance(children[0], Const):
const_node = children[0]
pragma = const_node.value
self.accept_imports(pragma)
def accept_imports(self, pragma=None):
self.modules.extend((m, r, l, n, lvl, pragma)
for (m, r, l, n, lvl) in self.recent)
self.recent = []
def finalize(self):
self.accept_imports()
return self.modules
class ImportWalker(ASTVisitor):
def __init__(self, visitor):
ASTVisitor.__init__(self)
self._visitor = visitor
def default(self, node, *args):
self._visitor.default(node)
ASTVisitor.default(self, node, *args)
def parse_python_source(fn):
contents = open(fn, 'rU').read()
ast = compiler.parse(contents)
vis = ImportVisitor()
compiler.walk(ast, vis, ImportWalker(vis))
return vis.finalize()
for d, f in pyfiles('/Users/bear/temp/foobar'):
print d, f
print parse_python_source(os.path.join(d, f))