Вернуть список импортированных модулей Python, используемых в скрипте? - PullRequest
28 голосов
/ 04 апреля 2010

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

Ответы [ 12 ]

15 голосов
/ 04 апреля 2010

IMO лучший способ сделать это - использовать пакет 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. Все права защищены.

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

5 голосов
/ 19 февраля 2016

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

5 голосов
/ 04 апреля 2010

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

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

2 голосов
/ 16 марта 2014

Это работает - используя 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 !!!!"

Выход:

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

2 голосов
/ 04 апреля 2010

Ну, вы всегда можете написать простой скрипт, который ищет в файле операторы 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.

1 голос
/ 26 сентября 2017

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

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.

0 голосов
/ 24 октября 2018

Я знаю, что это старо, но я также искал такое решение, как OP. Поэтому я написал этот код, чтобы найти импортированные модули по сценариям в папке. Работает с форматами import abc и from abc import cde. Я надеюсь, что это помогает кому-то еще.

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"(?<!from)import (\w+)[\n.]|from\s+(\w+)\s+import", lines)
            for imp in result:
                for i in imp:
                    if len(i):
                        if i not in imports:
                            imports.append(i)

    return imports
0 голосов
/ 28 августа 2017

Спасибо Тони Саффолку за проверку, примеры importlib ... Я создал этот крошечный модуль, и вы все можете его использовать, если он вам поможет. Отдай, ааааа!

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
# ################################################################################
0 голосов
/ 03 марта 2016

На самом деле он работает довольно хорошо с

print [key for key in locals().keys()
   if isinstance(locals()[key], type(sys)) and not key.startswith('__')]
0 голосов
/ 05 сентября 2014

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