Вернуть список импортированных модулей Python, используемых в script?

Я пишу программу, которая классифицирует список файлов 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,

но это возвращает больше, чем только модули, используемые в script. В качестве примера в script, который просто имеет:

import os
print os.getenv('USERNAME')

Модули, возвращенные из модуля ModuleFinder script return:

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", поскольку это был модуль, используемый в script.

Может ли кто-нибудь помочь мне достичь этого?

UPDATE. Я просто хочу уточнить, что я хотел бы сделать это, не анализируя файл Python и просто сканируя код.

Ответ 1

IMO лучший способ сделать это - использовать http://furius.ca/snakefood/. Автор выполнил всю необходимую работу, чтобы получить не только непосредственно импортированные модули, но и использует АСТ для анализа кода для зависимостей времени выполнения, который мог бы пропустить более статический анализ.

Разработал пример команды, чтобы продемонстрировать:

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

Это создаст базовый файл зависимостей каждого уникального модуля. Для более детального использования:

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

Чтобы пройти дерево и найти весь импорт, вы также можете сделать это в коде: Пожалуйста, ЗАМЕЧАНИЕ - Атомные куски этой процедуры были сняты с источника змеиного напитка, у которого есть это авторское право: Copyright (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)) 

Ответ 2

Это зависит от того, насколько тщательно вы хотите быть. Используемые модули - полная проблема: некоторый код python использует ленивый импорт только для импорта, который они фактически используют для определенного запуска, некоторые из них генерируют вещи для динамического импорта (например, систем плагинов).

python -v будет отслеживать операторы импорта - это, возможно, самая простая вещь для проверки.

Ответ 3

Ну, вы всегда можете написать простой script, который ищет файл для операторов 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 он не справляется с несколькими импортами на строку. Реальная проблема здесь в том, что я должен написать полный парсер, чтобы действительно сделать это правильно. Данный код работает во многих случаях, если вы понимаете, что есть определенные угловые случаи.

Одна из проблем заключается в том, что относительный импорт не удастся, если этот script не находится в том же каталоге, что и данный файл. Возможно, вы захотите добавить каталог данного script в sys.path.

Ответ 4

Это работает - с помощью importlib для фактического импорта модуля и проверки для получения членов:

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

Ответ 5

Я понимаю, что этот пост очень старый, но я нашел идеальное решение. Я придумал эту идею:

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

он выполняет, как, запятые и нормальные операторы импорта. он не требует никаких зависимостей и работает с другими строками кода.

Вышеприведенный код печатает:

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

Просто поместите свой код в функцию find_modules.

Ответ 6

Для большинства скриптов, которые импортируют только модули на верхнем уровне, достаточно загрузить файл в виде модуля и проверить его члены для модулей:

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

Это моделирует модуль, выполняемый как script, путем установки имени модуля '__main__'. Поэтому он должен также захватывать загрузку динамического модуля. Единственными модулями, которые он не будет захвачен, являются те, которые импортируются только в локальные области.

Ответ 7

Я искал что-то подобное, и я нашел драгоценный камень в пакете под названием 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())

Ответ 8

Возможно, вы захотите попробовать 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'].

Ответ 9

Он действительно работает неплохо с

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

Ответ 10

Спасибо Tony Suffolk за проверку, образцы importlib... Я построил этот модуль, и вы можете использовать его, если он вам поможет. Отдаю, 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
# ################################################################################

Ответ 11

Я редактирую свой оригинальный ответ, чтобы сказать это. Это можно сделать с помощью фрагмента кода, подобного приведенному ниже, но разбор 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())

Дальнейший синтаксический анализ строк понадобится, чтобы захватить только имена модулей. Это не улавливает случаи, когда многострочные строки или строки документа содержат слова "import" или "from X import". Вот почему я предложил разбор АСТ.