¿Desea devolver una lista de los módulos Python importados utilizados en un script?

Estoy escribiendo un progtwig que categoriza una lista de archivos de Python por los módulos que importan. Como tal, necesito escanear la colección de archivos .py y devolver una lista de los módulos que importan. Como ejemplo, si uno de los archivos que importo tiene las siguientes líneas:

import os import sys, gtk 

Me gustaría que volviera:

 ["os", "sys", "gtk"] 

Jugué con modulefinder y escribí:

 from modulefinder import ModuleFinder finder = ModuleFinder() finder.run_script('testscript.py') print 'Loaded modules:' for name, mod in finder.modules.iteritems(): print '%s ' % name, 

pero esto devuelve más que solo los módulos utilizados en el script. Como ejemplo en un script que simplemente tiene:

 import os print os.getenv('USERNAME') 

Los módulos devueltos desde el script ModuleFinder devuelven:

 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 

… mientras que solo quiero que devuelva ‘os’, ya que ese fue el módulo usado en el script.

¿Alguien puede ayudarme a lograr esto?

ACTUALIZACIÓN : solo quiero aclarar que me gustaría hacer esto sin ejecutar el archivo Python que se está analizando, y solo escanear el código.

OMI, la mejor manera de hacerlo es usar el paquete http://furius.ca/snakefood/ . El autor ha realizado todo el trabajo necesario para obtener no solo los módulos importados directamente, sino que utiliza el AST para analizar el código de las dependencias en tiempo de ejecución que un análisis más estático no tendría en cuenta.

Se elaboró ​​un ejemplo de comando para demostrar:

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

Eso generará un archivo de dependencia básico de cada módulo único. Para un uso aún más detallado:

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

Para caminar por un árbol y encontrar todas las importaciones, también puede hacer esto en el código: NOTA: los fragmentos de AST de esta rutina se extrajeron de la fuente de comida de serpiente que tiene este derecho de autor: Copyright (C) 2001-2007 Martin Blais. Todos los derechos reservados.

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

Depende de lo minucioso que quieras ser. Los módulos usados ​​son un problema completo: algunos códigos de Python utilizan la importación diferida para importar solo las cosas que realmente usan en una ejecución en particular, algunos generan cosas para importar dinámicamente (por ejemplo, sistemas de complementos).

python -v rastreará las declaraciones de importación, es posiblemente la cosa más simple de verificar.

Es posible que desee probar dis (pun por el juego):

 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) 

salidas

 defaultdict(, {'IMPORT_FROM': ['absolute_import', 'division', 'open', 'check_output', 'Popen'], 'IMPORT_NAME': ['__future__', 'os', 'collections', 'itertools', 'math', 'gzip', 'subprocess'], 'IMPORT_STAR': [None]}) 

Sus módulos importados se grouped['IMPORT_NAME'] .

Bueno, siempre se puede escribir un script simple que busque declaraciones de import el archivo. Este encuentra todos los módulos y archivos importados, incluidos los importados en funciones o clases:

 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) 

Esto no hace nada por from module import something estilo de importación from module import something , aunque eso podría agregarse fácilmente, dependiendo de cómo quiera tratarlos. Tampoco realiza ninguna comprobación de syntax, por lo que si tiene algún negocio divertido como import sys gtk, os pensará que ha importado los tres módulos a pesar de que la línea es un error. Tampoco se ocupa de las declaraciones de tipo try / except con respecto a la importación; si puede importarse, esta función lo incluirá en la lista. Tampoco funciona bien con múltiples importaciones por línea si usa la palabra clave as . El verdadero problema aquí es que tendría que escribir un analizador completo para hacer esto correctamente. El código dado funciona en muchos casos, siempre que entienda que hay casos de esquinas definidas.

Un problema es que las importaciones relativas fallarán si esta secuencia de comandos no está en el mismo directorio que el archivo dado. Es posible que desee agregar el directorio del script dado a sys.path .

Esto funciona: usar importlib para importar el módulo e inspeccionar para obtener los miembros:

 #! /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 !!!!" 

Salida:

 tony@laptop .../~:$ ./test.py path sys 

Entiendo que esta publicación es MUY antigua, pero he encontrado una solución ideal. Se me ocurrió esta idea:

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

Lo hace a partir de, como, comas y sentencias normales de importación. no requiere dependencias y trabaja con otras líneas de código.

El código anterior se imprime:

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

Simplemente ponga su código en la función find_modules.

Para la mayoría de los scripts que solo importan módulos en el nivel superior, es suficiente cargar el archivo como un módulo y escanear sus miembros en busca de módulos:

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

Esto simula el módulo que se está ejecutando como un script, configurando el nombre del módulo en '__main__' . Por lo tanto, también debe capturar la carga del módulo dynamic funky. Los únicos módulos que no capturará son aquellos que se importan solo a ámbitos locales.

Estaba buscando algo similar y encontré una gem en un paquete llamado PyScons . El Escáner hace exactamente lo que quiere (en 7 líneas), usando un import_hook. Aquí hay un ejemplo abreviado:

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

En realidad está funcionando bastante bien con

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

Gracias a Tony Suffolk por inspeccionar, muestras importlib … Construí este pequeño módulo y todos pueden usarlo si lo ayudan. Devolviendo, 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 # ################################################################################ 

Sé que esto es antiguo, pero también estaba buscando una solución como la que hizo OP. Así que escribí este código para encontrar módulos importados por scripts en una carpeta. Funciona con import abc y from abc import cde format. Espero que ayude a alguien más.

 import re import os def get_imported_modules(folder): files = [f for f in os.listdir(folder) if f.endswith(".py")] imports = [] for file in files: with open(os.path.join(folder, file), mode="r") as f: lines = f.read() result = re.findall(r"(? 

Estoy editando mi respuesta original para decir esto. Esto es factible con un fragmento de código como el que se muestra a continuación, pero analizar la AST puede ser la mejor manera de hacerlo.

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

Se necesitará más análisis de cadenas para capturar solo los nombres de los módulos. Esto no detecta los casos en los que las cadenas de varias líneas o las cadenas de documentos contienen las palabras “importar” o “desde X importar”. Es por esto que sugerí analizar el AST.