2010-04-03 68 views
23

我正在編寫一個程序,通過它們導入的模塊對Python文件列表進行分類。因此,我需要掃描.py文件集合並返回它們導入哪些模塊的列表。舉個例子,如果我導入文件中的一個具有以下行:返回腳本中使用的導入Python模塊的列表?

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 

...而我只是希望它返回「操作系統」,因爲這是腳本中使用的模塊。

任何人都可以幫助我實現這個目標嗎?

UPDATE:我只是想澄清一下,我想這樣做,而不運行被分析的Python文件,只是掃描代碼。

+0

是使用import語句手動添加的那些額外模塊中的任何一個嗎?如果不是的話,你能不能僅僅創建一個空模塊,運行你的模塊查找器,並通過從其他模塊的結果中刪除這些模塊來使用結果?即。 'modulefinder(some_module) - modulefinder(empty_module)'? – 2010-04-03 21:09:52

回答

12

IMO最好的辦法是使用http://furius.ca/snakefood/包。作者已經完成了所有必要的工作,不僅可以直接導入模塊,還可以使用AST來解析運行時依賴關係的代碼,以便進行更靜態的分析。

後處理的命令的例子來演示:

sfood ./example.py | sfood-cluster > example.deps 

,將產生的每個唯一模塊的基本相關性文件。對於更詳細的使用方法:

sfood -r -i ./example.py | sfood-cluster > example.deps 

走路一棵樹,找到所有進口的,你也可以做到這一點代碼: 請注意 - 這個程序的AST塊狀物從具有該版權的snakefood源解除:版權所有(C)2001-2007 Martin Blais。版權所有。

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)) 

+0

這似乎是生成圖形的第三方工具,但不是我可以在代碼中使用的東西,而不是在子進程中使用它。這是你推薦的嗎? – 2010-04-03 21:00:15

+0

啊 - 你尋找的東西真的很基本 - 包括 - 讓我咀嚼那個 – bear 2010-04-03 21:05:18

+0

當然,如果*運行*我的樣本 - 在實際工作版本上工作:/ – bear 2010-04-03 21:25:28

2

嗯,你總是可以編寫一個簡單的腳本搜索import聲明文件。這一次找到所有導入模塊和文件,包括那些在函數或類進口:

def find_imports(toCheck): 
    """ 
    Given a filename, returns a list of modules imported by the program. 
    Only modules that can be imported from the current directory 
    will be included. This program does not run the code, so import statements 
    in if/else or try/except blocks will always be included. 
    """ 
    import imp 
    importedItems = [] 
    with open(toCheck, 'r') as pyFile: 
     for line in pyFile: 
      # ignore comments 
      line = line.strip().partition("#")[0].partition("as")[0].split(' ') 
      if line[0] == "import": 
       for imported in line[1:]: 
        # remove commas (this doesn't check for commas if 
        # they're supposed to be there! 
        imported = imported.strip(", ") 
        try: 
         # check to see if the module can be imported 
         # (doesn't actually import - just finds it if it exists) 
         imp.find_module(imported) 
         # add to the list of items we imported 
         importedItems.append(imported) 
        except ImportError: 
         # ignore items that can't be imported 
         # (unless that isn't what you want?) 
         pass 

    return importedItems 

toCheck = raw_input("Which file should be checked: ") 
print find_imports(toCheck) 

這並不做from module import something風格的進口任何東西,但很容易被加入,這取決於你想如何處理那些。它也不會執行任何語法檢查,所以如果您有一些有趣的業務,如import sys gtk, os,它會認爲您已導入了所有三個模塊,即使該行是錯誤的。它也不涉及與關於導入的try/except類型語句 - 如果它可以被導入,該函數將列出它。如果您使用as關鍵字,它也不能很好地處理每行多個進口。這裏真正的問題是我必須編寫一個完整的解析器才能真正做到這一點。給定的代碼在很多情況下都適用,只要你明白有明確的角落案例。

一個問題是,如果此腳本與給定文件不在同一目錄中,則相對導入將失敗。您可能需要將給定腳本的目錄添加到sys.path

+0

我只是試過這個腳本;它在應用程序中找到了27個導入語句中的9個。這是行不通的。 – Chelonian 2012-04-16 23:30:33

+0

@Chelonian你能在這裏更具體嗎?你只是說「它不工作」。該程序沒有找到的進口有什麼特別之處? – dm76 2014-06-04 12:41:52

+0

@ dm76 - 我認爲你的代碼只是尋找'import'命令 - 因此錯過' import '命令 - 使用inspect是我認爲的python方式...... – 2017-08-17 19:14:46

4

這取決於你想成爲多麼徹底。使用過的模塊是一個完整的問題:一些python代碼使用延遲導入來導入他們在特定運行中實際使用的東西,一些產生動態導入的東西(例如插件系統)。

python -v會跟蹤導入語句 - 它可以說是最簡單的檢查。

-1

我正在編輯我的原始答案來說這個。這可以通過下面的代碼片段來實現,但解析AST可能是最好的選擇。

def iter_imports(fd): 
    """ Yield only lines that appear to be imports from an iterable. 
     fd can be an open file, a list of lines, etc. 
    """ 
    for line in fd: 
     trimmed = line.strip() 
     if trimmed.startswith('import '): 
      yield trimmed 
     elif trimmed.startswith('from ') and ('import ' in trimmed): 
      yield trimmed 

def main(): 
    # File name to read. 
    filename = '/my/path/myfile.py' 
    # Safely open the file, exit on error 
    try: 
     with open(filename) as f: 
      # Iterate over the lines in this file, and generate a list of 
      # lines that appear to be imports. 
      import_lines = list(iter_imports(f)) 
    except (IOError, OSError) as exIO: 
     print('Error opening file: {}\n{}'.format(filename, exIO)) 
     return 1 
    else: 
     # From here, import_lines should be a list of lines like this: 
     #  from module import thing 
     #  import os, sys 
     #  from module import * 
     # Do whatever you need to do with the import lines. 
     print('\n'.join(import_lines)) 

    return 0 

if __name__ == '__main__': 
    sys.exit(main()) 

需要進一步的字符串解析來抓取模塊名稱。這不會捕獲多行字符串或文檔字符串包含單詞「導入」或「從X導入」的情況。這就是爲什麼我建議解析AST。

+0

你能在你的答案中提供一些代碼嗎? – 2012-10-11 01:13:23

+0

哇,stackoverflow謀殺了該代碼。我做錯了什麼?無論如何,這需要多一點,但如果你只是在尋找進口,那麼與現在的編解碼器相比,腳本將會很小。 – 2013-01-02 01:37:11

+0

編輯答案而不是將代碼添加爲註釋,它看起來很好。 – 2013-01-02 02:31:50

0

對於大多數的腳本,其僅在頂部級導入模塊,它是相當足夠裝載文件作爲一個模塊,並且掃描其成員的模塊:

import sys,io,imp,types 
scriptname = 'myfile.py' 
with io.open(scriptname) as scriptfile: 
    code = compile(scriptfile.readall(),scriptname,'exec') 
newmodule = imp.new_module('__main__') 
exec(codeobj,newmodule.__dict__) 
scriptmodules = [name for name in dir(newmodule) if isinstance(newmodule.__dict__[name],types.ModuleType)] 

這模擬模塊正在運行作爲腳本,通過將模塊名稱設置爲'__main__'。因此它應該捕獲時髦的動態模塊加載。它不會捕獲的唯一模塊是僅導入本地範圍的模塊。

+0

我得到一個錯誤('code = compile(scriptfile.readall(),scriptname,'e​​xec') AttributeError:'TextIOWrapper'對象沒有屬性'readall'')通過'io.FileIO'改變'io.open'但它無法打開用戶定義的模塊... – Llopis 2014-02-28 12:58:33

2

這工作 - 使用導入庫實際導入模塊,並檢查得到的成員:

#! /usr/bin/env python 
# 
# test.py 
# 
# Find Modules 
# 
import inspect, importlib as implib 

if __name__ == "__main__": 
    mod = implib.import_module("example") 
    for i in inspect.getmembers(mod, inspect.ismodule): 
     print i[0] 

#! /usr/bin/env python 
# 
# example.py 
# 
import sys 
from os import path 

if __name__ == "__main__": 
    print "Hello World !!!!" 

輸出:

[email protected] .../~:$ ./test.py 
path 
sys 

0

我一直在尋找類似的東西,我在名爲PyScons的包裹中找到了一顆寶石。掃描儀只用你想要的(7行),使用import_hook。下面是一個簡短的例子:

import modulefinder, sys 

class SingleFileModuleFinder(modulefinder.ModuleFinder): 

    def import_hook(self, name, caller, *arg, **kwarg): 
     if caller.__file__ == self.name: 
      # Only call the parent at the top level. 
      return modulefinder.ModuleFinder.import_hook(self, name, caller, *arg, **kwarg) 

    def __call__(self, node): 

     self.name = str(node) 

     self.run_script(self.name) 

if __name__ == '__main__': 
    # Example entry, run with './script.py filename' 
    print 'looking for includes in %s' % sys.argv[1] 

    mf = SingleFileModuleFinder() 
    mf(sys.argv[1]) 

    print '\n'.join(mf.modules.keys()) 
1

你可能想嘗試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']

0

它的實際工作挺好用

print [key for key in locals().keys() 
    if isinstance(locals()[key], type(sys)) and not key.startswith('__')] 
+0

這無法找到任何使用「from foo import bar」語法導入的模塊。 – 2016-11-16 02:15:26

0

感謝託尼薩福克的檢查,導入庫的樣本......我建這個凌晨模塊和你都歡迎,如果它可以幫助你使用它。回饋,yaaaay!

import timeit 
import os 
import inspect, importlib as implib 
import textwrap as twrap 

def src_modules(filename): 
    assert (len(filename)>1) 

    mod = implib.import_module(filename.split(".")[0]) 
    ml_alias = [] 
    ml_actual = [] 
    ml_together = [] 
    ml_final = [] 
    for i in inspect.getmembers(mod, inspect.ismodule): 
     ml_alias.append(i[0]) 
     ml_actual.append((str(i[1]).split(" ")[1])) 
     ml_together = zip(ml_actual, ml_alias) 
    for t in ml_together: 
     (a,b) = t 
     ml_final.append(a+":="+b) 

    return ml_final 

def l_to_str(itr): 
    assert(len(itr)>0) 

    itr.sort() 
    r_str = "" 
    for i in itr: 
     r_str += i+" " 
    return r_str 

def src_info(filename, start_time=timeit.default_timer()): 
    assert (len(filename)>1) 

    filename_in = filename 
    filename = filename_in.split(".")[0] 

    if __name__ == filename: 
     output_module = filename 
    else: 
     output_module = __name__ 

    print ("\n" + (80 * "#")) 
    print (" runtime ~= {0} ms".format(round(((timeit.default_timer() - start_time)*1000),3))) 
    print (" source file --> '{0}'".format(filename_in)) 
    print (" output via --> '{0}'".format(output_module)) 
    print (" modules used in '{0}':".format(filename)) 
    print (" "+"\n ".join(twrap.wrap(l_to_str(src_modules(filename)), 75))) 
    print (80 * "#") 

    return "" 


if __name__ == "__main__": 
    src_info(os.path.basename(__file__)) 


## how to use in X file: 
# 
# import print_src_info 
# import os 
# 
# < ... your code ... > 
# 
# if __name__ == "__main__": 
#  print_src_info.src_info(os.path.basename(__file__)) 


## example output: 
# 
# ################################################################################ 
# runtime ~= 0.049 ms 
# source file --> 'print_src_info.py' 
# output via --> '__main__' 
# modules used in 'print_src_info': 
# 'importlib':=implib 'inspect':=inspect 'os':=os 'textwrap':=twrap 
# 'timeit':=timeit 
# ################################################################################ 
1

我知道這篇文章很老,但我找到了一個理想的解決方案。 我想出了這個主意:

def find_modules(code): 
    modules = [] 
    code = code.splitlines() 
    for item in code: 
     if item[:7] == "import " and ", " not in item: 
      if " as " in item: 
       modules.append(item[7:item.find(" as ")]) 
      else: 
       modules.append(item[7:]) 
     elif item[:5] == "from ": 
      modules.append(item[5:item.find(" import ")]) 

     elif ", " in item: 
      item = item[7:].split(", ") 
      modules = modules+item 

     else: 
      print(item) 
    return modules 

code = """ 
import foo 
import bar 
from baz import eggs 
import mymodule as test 
import hello, there, stack 
""" 
print(find_modules(code)) 

它,確實是,逗號和正常的import語句。它不需要依賴關係,可以和其他代碼行一起工作。

上面的代碼打印:

['foo', 'bar', 'baz', 'mymodule', 'hello', 'there', 'stack'] 

只要把你的代碼在find_modules功能。