📄 modulegraph.py
字号:
"""Find modules used by a script, using bytecode analysis.Based on the stdlib modulefinder by Thomas Heller and Just van Rossum,but uses a graph data structure and 2.3 features"""import disimport impimport marshalimport osimport sysimport newfrom altgraph.Dot import Dotfrom altgraph.ObjectGraph import ObjectGraphfrom altgraph.GraphUtil import filter_stackfrom altgraph.compat import *READ_MODE = "U" # universal line endingsLOAD_CONST = dis.opmap['LOAD_CONST']IMPORT_NAME = dis.opmap['IMPORT_NAME']STORE_NAME = dis.opmap['STORE_NAME']STORE_GLOBAL = dis.opmap['STORE_GLOBAL']STORE_OPS = [STORE_NAME, STORE_GLOBAL]# Modulegraph does a good job at simulating Python's, but it can not# handle packagepath modifications packages make at runtime. Therefore there# is a mechanism whereby you can register extra paths in this map for a# package, and it will be honored.# Note this is a mapping is lists of paths.packagePathMap = {}def moduleInfoForPath(path, suffixes=imp.get_suffixes()): for (ext, readmode, typ) in imp.get_suffixes(): if path.endswith(ext): return os.path.basename(path)[:-len(ext)], readmode, typ return None# A Public interfacedef AddPackagePath(packagename, path): paths = packagePathMap.get(packagename, []) paths.append(path) packagePathMap[packagename] = pathsreplacePackageMap = {}# This ReplacePackage mechanism allows modulefinder to work around the# way the _xmlplus package injects itself under the name "xml" into# sys.modules at runtime by calling ReplacePackage("_xmlplus", "xml")# before running ModuleGraph.def ReplacePackage(oldname, newname): replacePackageMap[oldname] = newnameclass Node(object): def __init__(self, identifier): self.graphident = identifier self.identifier = identifier self.namespace = {} self.filename = None self.packagepath = None self.code = None # The set of global names that are assigned to in the module. # This includes those names imported through starimports of # Python modules. self.globalnames = set() # The set of starimports this module did that could not be # resolved, ie. a starimport from a non-Python module. self.starimports = set() def __contains__(self, name): return name in self.namespace def __getitem__(self, name): return self.namespace[name] def __setitem__(self, name, value): self.namespace[name] = value def get(self, *args): return self.namespace.get(*args) def __cmp__(self, other): return cmp(self.graphident, other.graphident) def __hash__(self): return hash(self.graphident) def infoTuple(self): return (self.identifier,) def __repr__(self): return '%s%r' % (type(self).__name__, self.infoTuple())class Alias(str): passclass AliasNode(Node): def __init__(self, name, node): super(AliasNode, self).__init__(name) for k in ['identifier', 'packagepath', 'namespace', 'globalnames', 'startimports']: setattr(self, k, getattr(node, k, None)) def infoTuple(self): return (self.graphident, self.identifier)class BadModule(Node): passclass ExcludedModule(BadModule): passclass MissingModule(BadModule): passclass Script(Node): def __init__(self, filename): super(Script, self).__init__(filename) self.filename = filename def infoTuple(self): return (self.filename,)class BaseModule(Node): def __init__(self, name, filename=None, path=None): super(BaseModule, self).__init__(name) self.filename = filename self.packagepath = path def infoTuple(self): return tuple(filter(None, (self.identifier, self.filename, self.packagepath)))class BuiltinModule(BaseModule): passclass SourceModule(BaseModule): passclass CompiledModule(BaseModule): passclass Package(BaseModule): passclass FlatPackage(BaseModule): passclass Extension(BaseModule): passclass ModuleGraph(ObjectGraph): def __init__(self, path=None, excludes=(), replace_paths=(), implies=(), graph=None, debug=0): super(ModuleGraph, self).__init__(graph=graph, debug=debug) if path is None: path = sys.path self.path = path self.lazynodes = {} # excludes is stronger than implies self.lazynodes.update(dict(implies)) for m in excludes: self.lazynodes[m] = None self.replace_paths = replace_paths def implyNodeReference(self, node, other): """ Imply that one node depends on another. other may be a module name or another node. For use by extension modules and tricky import code """ if not isinstance(other, Node): if not isinstance(other, tuple): other = (other, node) others = self.import_hook(*other) for other in others: self.createReference(node, other) elif isinstance(other, AliasNode): self.addNode(other) other.connectTo(node) else: self.createReference(node, other) def createReference(self, fromnode, tonode, edge_data='direct'): return super(ModuleGraph, self).createReference(fromnode, tonode, edge_data=edge_data) def findNode(self, name): """ Find a node by identifier. If a node by that identifier exists, it will be returned. If a lazy node exists by that identifier with no dependencies (excluded), it will be instantiated and returned. If a lazy node exists by that identifier with dependencies, it and its dependencies will be instantiated and scanned for additional dependencies. """ data = super(ModuleGraph, self).findNode(name) if data is not None: return data if name in self.lazynodes: deps = self.lazynodes.pop(name) if deps is None: # excluded module m = self.createNode(ExcludedModule, name) elif isinstance(deps, Alias): other = self._safe_import_hook(deps, None, None).pop() m = self.createNode(AliasNode, name, other) self.implyNodeReference(m, other) else: m = self._safe_import_hook(name, None, None).pop() for dep in deps: self.implyNodeReference(m, dep) return m return None def run_script(self, pathname, caller=None): """ Create a node by path (not module name). It is expected to be a Python source file, and will be scanned for dependencies. """ self.msg(2, "run_script", pathname) pathname = os.path.realpath(pathname) m = self.findNode(pathname) if m is not None: return m co = compile(file(pathname, READ_MODE).read()+'\n', pathname, 'exec') if self.replace_paths: co = self.replace_paths_in_code(co) m = self.createNode(Script, pathname) m.code = co self.createReference(caller, m) self.scan_code(co, m) return m def import_hook(self, name, caller=None, fromlist=None): """ Import a module """ self.msg(3, "import_hook", name, caller, fromlist) parent = self.determine_parent(caller) q, tail = self.find_head_package(parent, name) m = self.load_tail(q, tail) modules = set([m]) if fromlist and m.packagepath: modules.update(self.ensure_fromlist(m, fromlist)) for m in modules: self.createReference(caller, m) return modules def determine_parent(self, caller): """ Determine the package containing a node """ self.msgin(4, "determine_parent", caller) parent = None if caller: pname = caller.identifier if caller.packagepath: parent = self.findNode(pname) elif '.' in pname: pname = pname[:pname.rfind('.')] parent = self.findNode(pname) self.msgout(4, "determine_parent ->", parent) return parent def find_head_package(self, parent, name): """ Given a calling parent package and an import name determine the containing package for the name """ self.msgin(4, "find_head_package", parent, name) if '.' in name: head, tail = name.split('.', 1) else: head, tail = name, '' if parent: qname = parent.identifier + '.' + head else: qname = head q = self.import_module(head, qname, parent) if q: self.msgout(4, "find_head_package ->", (q, tail)) return q, tail if parent: qname = head parent = None q = self.import_module(head, qname, parent) if q: self.msgout(4, "find_head_package ->", (q, tail)) return q, tail self.msgout(4, "raise ImportError: No module named", qname) raise ImportError, "No module named " + qname def load_tail(self, q, tail): self.msgin(4, "load_tail", q, tail) m = q while tail: i = tail.find('.') if i < 0: i = len(tail) head, tail = tail[:i], tail[i+1:] mname = "%s.%s" % (m.identifier, head) m = self.import_module(head, mname, m) if not m: self.msgout(4, "raise ImportError: No module named", mname) raise ImportError, "No module named " + mname self.msgout(4, "load_tail ->", m) return m def ensure_fromlist(self, m, fromlist): fromlist = set(fromlist) self.msg(4, "ensure_fromlist", m, fromlist) if '*' in fromlist: fromlist.update(self.find_all_submodules(m)) fromlist.remove('*') for sub in fromlist: submod = m.get(sub) if submod is None: fullname = m.identifier + '.' + sub submod = self.import_module(sub, fullname, m) if submod is None: raise ImportError, "No module named " + fullname yield submod def find_all_submodules(self, m): if not m.packagepath: return # 'suffixes' used to be a list hardcoded to [".py", ".pyc", ".pyo"]. # But we must also collect Python extension modules - although # we cannot separate normal dlls from Python extensions. suffixes = [triple[0] for triple in imp.get_suffixes()] for path in m.packagepath: try: names = os.listdir(path) except os.error: self.msg(2, "can't list directory", path) continue for (path, mode, typ) in ifilter(None, imap(moduleInfoForPath, names)): if path != '__init__': yield path def import_module(self, partname, fqname, parent): self.msgin(3, "import_module", partname, fqname, parent) m = self.findNode(fqname) if m is not None: self.msgout(3, "import_module ->", m) if parent: self.createReference(m, parent) return m if parent and parent.packagepath is None: self.msgout(3, "import_module -> None") return None try: fp, pathname, stuff = self.find_module(partname, parent and parent.packagepath, parent) except ImportError: self.msgout(3, "import_module ->", None) return None m = self.load_module(fqname, fp, pathname, stuff) if parent: self.createReference(m, parent) parent[partname] = m self.msgout(3, "import_module ->", m) return m def load_module(self, fqname, fp, pathname, (suffix, mode, typ)): self.msgin(2, "load_module", fqname, fp and "fp", pathname) if typ == imp.PKG_DIRECTORY: m = self.load_package(fqname, pathname) self.msgout(2, "load_module ->", m) return m if typ == imp.PY_SOURCE: co = compile(fp.read()+'\n', pathname, 'exec') cls = SourceModule elif typ == imp.PY_COMPILED: if fp.read(4) != imp.get_magic():
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -