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.
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))
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.
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']
.
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
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
.
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.
Ça marche vraiment bien avec
print [key for key in locals().keys()
if isinstance(locals()[key], type(sys)) and not key.startswith('__')]
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.
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())
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
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
# ################################################################################