web-dev-qa-db-fra.com

Renvoyer une liste des modules Python importés dans un script?

J'écris un programme qui catégorise une liste de fichiers Python en fonction des modules qu'ils importent. En tant que tel, je dois analyser la collection de fichiers .py et renvoyer une liste des modules qu'ils importent. Par exemple, si l’un des fichiers que j’importe a les lignes suivantes:

import os
import sys, gtk

Je voudrais qu'il revienne:

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

J'ai joué avec modulefinder et j'ai écrit:

from modulefinder import ModuleFinder

Finder = ModuleFinder()
Finder.run_script('testscript.py')

print 'Loaded modules:'
for name, mod in Finder.modules.iteritems():
    print '%s ' % name,

mais cela retourne plus que les modules utilisés dans le script. Comme exemple dans un script qui a simplement:

import os
print os.getenv('USERNAME')

Les modules renvoyés par le script ModuleFinder renvoient:

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

... alors que je veux juste qu'il renvoie 'os', car c'était le module utilisé dans le script.

Quelqu'un peut-il m'aider à atteindre cet objectif?

UPDATE: Je veux juste préciser que je voudrais le faire sans exécuter le fichier Python en cours d'analyse, mais simplement en scannant le code.

27
Jono Bacon

Le meilleur moyen de le faire, à l’OMI, est d’utiliser le paquet http://furius.ca/snakefood/ . L’auteur a effectué tout le travail requis pour obtenir non seulement les modules directement importés, mais il utilise le AST pour analyser le code des dépendances d’exécution qui manqueraient à une analyse plus statique.

Travaillé un exemple de commande pour démontrer:

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

Cela générera un fichier de dépendance de base de chaque module unique. Pour encore plus de détails, utilisez:

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

Pour parcourir un arbre et trouver toutes les importations, vous pouvez également le faire dans le code suivant: Remarque: les AST morceaux de cette routine ont été retirés de la source snakefood qui dispose du droit d'auteur: Copyright (C) 2001-2007 Martin Blais. Tous les droits sont réservés.

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

Cela dépend de la profondeur de votre travail. Les modules utilisés sont un problème complexe: certains codes python utilisent l’importation paresseuse pour n’importer que les éléments qu’ils utilisent réellement lors d’une exécution particulière, d’autres génèrent des éléments à importer de manière dynamique (systèmes de plug-in, par exemple).

python -v tracera les instructions d'importation - c'est sans doute la chose la plus simple à vérifier.

5
lifeless

Vous voudrez peut-être essayer dis (jeu de mots):

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)

les sorties

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

Vos modules importés sont grouped['IMPORT_NAME'].

3
Guilherme Freitas

Cela fonctionne - en utilisant importlib d'importer réellement le module et d'inspecter pour obtenir les membres:

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

if __== "__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 __== "__main__":
    print "Hello World !!!!"

Sortie:

tony@laptop .../~:$ ./test.py
path
sys
2
Tony Suffolk 66

Eh bien, vous pouvez toujours écrire un script simple qui recherche dans le fichier les instructions import. Celui-ci trouve tous les modules et fichiers importés, y compris ceux importés dans des fonctions ou des classes:

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)

Cela ne fait rien pour les importations de style from module import something, bien que cela puisse être facilement ajouté, en fonction de la manière dont vous souhaitez les gérer. De plus, il ne vérifie pas la syntaxe. Par conséquent, si vous avez une activité amusante comme import sys gtk, os, vous penserez que vous avez importé les trois modules, même si la ligne est une erreur. De plus, elle ne traite pas les instructions de type try/except en ce qui concerne l'importation - si elle peut être importée, cette fonction la listera. De plus, il ne gère pas bien les importations multiples par ligne si vous utilisez le mot clé as. Le vrai problème ici est que je devrais écrire un analyseur complet pour vraiment le faire correctement. Le code donné fonctionne dans de nombreux cas, à condition que vous sachiez qu'il existe des cas précis.

Un problème est que les importations relatives échoueront si ce script ne se trouve pas dans le même répertoire que le fichier donné. Vous voudrez peut-être ajouter le répertoire du script donné à sys.path.

2
Daniel G

Je comprends que ce post est très vieux, mais j’ai trouvé une solution idéale. Je suis venu avec cette idée:

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

il utilise des virgules et des instructions d'importation normales. il ne nécessite aucune dépendance et fonctionne avec d'autres lignes de code.

Le code ci-dessus imprime:

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

Il suffit de mettre votre code dans la fonction find_modules.

1
lgwilliams

Ça marche vraiment bien avec

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

Pour la majorité des scripts qui importent uniquement des modules au niveau supérieur, il suffit de charger le fichier en tant que module et d'analyser ses membres pour rechercher des modules:

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

Cela simule le module en cours d'exécution en tant que script en définissant le nom du module sur '__main__'. Il convient donc également de capturer le chargement du module dynamique funky. Les seuls modules qu'il ne capture pas sont ceux qui ne sont importés que dans des portées locales.

0
Marcin

Je cherchais quelque chose de similaire et j'ai trouvé un petit bijou dans un paquet nommé PyScons . Le scanner fait exactement ce que vous voulez (en 7 lignes), en utilisant un import_hook. Voici un exemple abrégé:

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 __== '__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())
0
Jeffeb3

Je sais que c'est vieux, mais je cherchais aussi une solution comme celle de OP. J'ai donc écrit ce code pour trouver les modules importés par des scripts dans un dossier. Cela fonctionne avec les formats import abc et from abc import cde. J'espère que ça aide quelqu'un d'autre.

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
Sinan Çetinkaya

Merci Tony Suffolk pour les échantillons inspectés, importlibs ... J'ai construit ce module et vous êtes tous les bienvenus pour l'utiliser s'il vous aide. Redonner, 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 __== 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 __== "__main__":
    src_info(os.path.basename(__file__))


## how to use in X file:
#
# import print_src_info
# import os
#
# < ... your code ... >
#
# if __== "__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
Goran B.