| """New import scheme with package support. |
| |
| Quick Reference |
| --------------- |
| |
| - To enable package support, execute "import ni" before importing any |
| packages. Importing this module automatically installs the relevant |
| import hooks. |
| |
| - To create a package named spam containing sub-modules ham, bacon and |
| eggs, create a directory spam somewhere on Python's module search |
| path (i.e. spam's parent directory must be one of the directories in |
| sys.path or $PYTHONPATH); then create files ham.py, bacon.py and |
| eggs.py inside spam. |
| |
| - To import module ham from package spam and use function hamneggs() |
| from that module, you can either do |
| |
| import spam.ham # *not* "import spam" !!! |
| spam.ham.hamneggs() |
| |
| or |
| |
| from spam import ham |
| ham.hamneggs() |
| |
| or |
| |
| from spam.ham import hamneggs |
| hamneggs() |
| |
| - Importing just "spam" does not do what you expect: it creates an |
| empty package named spam if one does not already exist, but it does |
| not import spam's submodules. The only submodule that is guaranteed |
| to be imported is spam.__init__, if it exists. Note that |
| spam.__init__ is a submodule of package spam. It can reference to |
| spam's namespace via the '__.' prefix, for instance |
| |
| __.spam_inited = 1 # Set a package-level variable |
| |
| |
| |
| Theory of Operation |
| ------------------- |
| |
| A Package is a module that can contain other modules. Packages can be |
| nested. Package introduce dotted names for modules, like P.Q.M, which |
| could correspond to a file P/Q/M.py found somewhere on sys.path. It |
| is possible to import a package itself, though this makes little sense |
| unless the package contains a module called __init__. |
| |
| A package has two variables that control the namespace used for |
| packages and modules, both initialized to sensible defaults the first |
| time the package is referenced. |
| |
| (1) A package's *module search path*, contained in the per-package |
| variable __path__, defines a list of *directories* where submodules or |
| subpackages of the package are searched. It is initialized to the |
| directory containing the package. Setting this variable to None makes |
| the module search path default to sys.path (this is not quite the same |
| as setting it to sys.path, since the latter won't track later |
| assignments to sys.path). |
| |
| (2) A package's *import domain*, contained in the per-package variable |
| __domain__, defines a list of *packages* that are searched (using |
| their respective module search paths) to satisfy imports. It is |
| initialized to the list cosisting of the package itself, its parent |
| package, its parent's parent, and so on, ending with the root package |
| (the nameless package containing all top-level packages and modules, |
| whose module search path is None, implying sys.path). |
| |
| The default domain implements a search algorithm called "expanding |
| search". An alternative search algorithm called "explicit search" |
| fixes the import search path to contain only the root package, |
| requiring the modules in the package to name all imported modules by |
| their full name. The convention of using '__' to refer to the current |
| package (both as a per-module variable and in module names) can be |
| used by packages using explicit search to refer to modules in the same |
| package; this combination is known as "explicit-relative search". |
| |
| The PackageImporter and PackageLoader classes together implement the |
| following policies: |
| |
| - There is a root package, whose name is ''. It cannot be imported |
| directly but may be referenced, e.g. by using '__' from a top-level |
| module. |
| |
| - In each module or package, the variable '__' contains a reference to |
| the parent package; in the root package, '__' points to itself. |
| |
| - In the name for imported modules (e.g. M in "import M" or "from M |
| import ..."), a leading '__' refers to the current package (i.e. |
| the package containing the current module); leading '__.__' and so |
| on refer to the current package's parent, and so on. The use of |
| '__' elsewhere in the module name is not supported. |
| |
| - Modules are searched using the "expanding search" algorithm by |
| virtue of the default value for __domain__. |
| |
| - If A.B.C is imported, A is searched using __domain__; then |
| subpackage B is searched in A using its __path__, and so on. |
| |
| - Built-in modules have priority: even if a file sys.py exists in a |
| package, "import sys" imports the built-in sys module. |
| |
| - The same holds for frozen modules, for better or for worse. |
| |
| - Submodules and subpackages are not automatically loaded when their |
| parent packages is loaded. |
| |
| - The construct "from package import *" is illegal. (It can still be |
| used to import names from a module.) |
| |
| - When "from package import module1, module2, ..." is used, those |
| modules are explicitly loaded. |
| |
| - When a package is loaded, if it has a submodule __init__, that |
| module is loaded. This is the place where required submodules can |
| be loaded, the __path__ variable extended, etc. The __init__ module |
| is loaded even if the package was loaded only in order to create a |
| stub for a sub-package: if "import P.Q.R" is the first reference to |
| P, and P has a submodule __init__, P.__init__ is loaded before P.Q |
| is even searched. |
| |
| Caveats: |
| |
| - It is possible to import a package that has no __init__ submodule; |
| this is not particularly useful but there may be useful applications |
| for it (e.g. to manipulate its search paths from the outside!). |
| |
| - There are no special provisions for os.chdir(). If you plan to use |
| os.chdir() before you have imported all your modules, it is better |
| not to have relative pathnames in sys.path. (This could actually be |
| fixed by changing the implementation of path_join() in the hook to |
| absolutize paths.) |
| |
| - Packages and modules are introduced in sys.modules as soon as their |
| loading is started. When the loading is terminated by an exception, |
| the sys.modules entries remain around. |
| |
| - There are no special measures to support mutually recursive modules, |
| but it will work under the same conditions where it works in the |
| flat module space system. |
| |
| - Sometimes dummy entries (whose value is None) are entered in |
| sys.modules, to indicate that a particular module does not exist -- |
| this is done to speed up the expanding search algorithm when a |
| module residing at a higher level is repeatedly imported (Python |
| promises that importing a previously imported module is cheap!) |
| |
| - Although dynamically loaded extensions are allowed inside packages, |
| the current implementation (hardcoded in the interpreter) of their |
| initialization may cause problems if an extension invokes the |
| interpreter during its initialization. |
| |
| - reload() may find another version of the module only if it occurs on |
| the package search path. Thus, it keeps the connection to the |
| package to which the module belongs, but may find a different file. |
| |
| XXX Need to have an explicit name for '', e.g. '__root__'. |
| |
| """ |
| |
| |
| import imp |
| import string |
| import sys |
| import __builtin__ |
| |
| import ihooks |
| from ihooks import ModuleLoader, ModuleImporter |
| |
| |
| class PackageLoader(ModuleLoader): |
| |
| """A subclass of ModuleLoader with package support. |
| |
| find_module_in_dir() will succeed if there's a subdirectory with |
| the given name; load_module() will create a stub for a package and |
| load its __init__ module if it exists. |
| |
| """ |
| |
| def find_module_in_dir(self, name, dir): |
| if dir is not None: |
| dirname = self.hooks.path_join(dir, name) |
| if self.hooks.path_isdir(dirname): |
| return None, dirname, ('', '', 'PACKAGE') |
| return ModuleLoader.find_module_in_dir(self, name, dir) |
| |
| def load_module(self, name, stuff): |
| file, filename, info = stuff |
| suff, mode, type = info |
| if type == 'PACKAGE': |
| return self.load_package(name, stuff) |
| if sys.modules.has_key(name): |
| m = sys.modules[name] |
| else: |
| sys.modules[name] = m = imp.new_module(name) |
| self.set_parent(m) |
| if type == imp.C_EXTENSION and '.' in name: |
| return self.load_dynamic(name, stuff) |
| else: |
| return ModuleLoader.load_module(self, name, stuff) |
| |
| def load_dynamic(self, name, stuff): |
| file, filename, (suff, mode, type) = stuff |
| # Hack around restriction in imp.load_dynamic() |
| i = string.rfind(name, '.') |
| tail = name[i+1:] |
| if sys.modules.has_key(tail): |
| save = sys.modules[tail] |
| else: |
| save = None |
| sys.modules[tail] = imp.new_module(name) |
| try: |
| m = imp.load_dynamic(tail, filename, file) |
| finally: |
| if save: |
| sys.modules[tail] = save |
| else: |
| del sys.modules[tail] |
| sys.modules[name] = m |
| return m |
| |
| def load_package(self, name, stuff): |
| file, filename, info = stuff |
| if sys.modules.has_key(name): |
| package = sys.modules[name] |
| else: |
| sys.modules[name] = package = imp.new_module(name) |
| package.__path__ = [filename] |
| self.init_package(package) |
| return package |
| |
| def init_package(self, package): |
| self.set_parent(package) |
| self.set_domain(package) |
| self.call_init_module(package) |
| |
| def set_parent(self, m): |
| name = m.__name__ |
| if '.' in name: |
| name = name[:string.rfind(name, '.')] |
| else: |
| name = '' |
| m.__ = sys.modules[name] |
| |
| def set_domain(self, package): |
| name = package.__name__ |
| package.__domain__ = domain = [name] |
| while '.' in name: |
| name = name[:string.rfind(name, '.')] |
| domain.append(name) |
| if name: |
| domain.append('') |
| |
| def call_init_module(self, package): |
| stuff = self.find_module('__init__', package.__path__) |
| if stuff: |
| m = self.load_module(package.__name__ + '.__init__', stuff) |
| package.__init__ = m |
| |
| |
| class PackageImporter(ModuleImporter): |
| |
| """Importer that understands packages and '__'.""" |
| |
| def __init__(self, loader = None, verbose = 0): |
| ModuleImporter.__init__(self, |
| loader or PackageLoader(None, verbose), verbose) |
| |
| def import_module(self, name, globals={}, locals={}, fromlist=[]): |
| if globals.has_key('__'): |
| package = globals['__'] |
| else: |
| # No calling context, assume in root package |
| package = sys.modules[''] |
| if name[:3] in ('__.', '__'): |
| p = package |
| name = name[3:] |
| while name[:3] in ('__.', '__'): |
| p = package.__ |
| name = name[3:] |
| if not name: |
| return self.finish(package, p, '', fromlist) |
| if '.' in name: |
| i = string.find(name, '.') |
| name, tail = name[:i], name[i:] |
| else: |
| tail = '' |
| mname = p.__name__ and p.__name__+'.'+name or name |
| m = self.get1(mname) |
| return self.finish(package, m, tail, fromlist) |
| if '.' in name: |
| i = string.find(name, '.') |
| name, tail = name[:i], name[i:] |
| else: |
| tail = '' |
| for pname in package.__domain__: |
| mname = pname and pname+'.'+name or name |
| m = self.get0(mname) |
| if m: break |
| else: |
| raise ImportError, "No such module %s" % name |
| return self.finish(m, m, tail, fromlist) |
| |
| def finish(self, module, m, tail, fromlist): |
| # Got ....A; now get ....A.B.C.D |
| yname = m.__name__ |
| if tail and sys.modules.has_key(yname + tail): # Fast path |
| yname, tail = yname + tail, '' |
| m = self.get1(yname) |
| while tail: |
| i = string.find(tail, '.', 1) |
| if i > 0: |
| head, tail = tail[:i], tail[i:] |
| else: |
| head, tail = tail, '' |
| yname = yname + head |
| m = self.get1(yname) |
| |
| # Got ....A.B.C.D; now finalize things depending on fromlist |
| if not fromlist: |
| return module |
| if '__' in fromlist: |
| raise ImportError, "Can't import __ from anywhere" |
| if not hasattr(m, '__path__'): return m |
| if '*' in fromlist: |
| raise ImportError, "Can't import * from a package" |
| for f in fromlist: |
| if hasattr(m, f): continue |
| fname = yname + '.' + f |
| self.get1(fname) |
| return m |
| |
| def get1(self, name): |
| m = self.get(name) |
| if not m: |
| raise ImportError, "No module named %s" % name |
| return m |
| |
| def get0(self, name): |
| m = self.get(name) |
| if not m: |
| sys.modules[name] = None |
| return m |
| |
| def get(self, name): |
| # Internal routine to get or load a module when its parent exists |
| if sys.modules.has_key(name): |
| return sys.modules[name] |
| if '.' in name: |
| i = string.rfind(name, '.') |
| head, tail = name[:i], name[i+1:] |
| else: |
| head, tail = '', name |
| path = sys.modules[head].__path__ |
| stuff = self.loader.find_module(tail, path) |
| if not stuff: |
| return None |
| sys.modules[name] = m = self.loader.load_module(name, stuff) |
| if head: |
| setattr(sys.modules[head], tail, m) |
| return m |
| |
| def reload(self, module): |
| name = module.__name__ |
| if '.' in name: |
| i = string.rfind(name, '.') |
| head, tail = name[:i], name[i+1:] |
| path = sys.modules[head].__path__ |
| else: |
| tail = name |
| path = sys.modules[''].__path__ |
| stuff = self.loader.find_module(tail, path) |
| if not stuff: |
| raise ImportError, "No module named %s" % name |
| return self.loader.load_module(name, stuff) |
| |
| def unload(self, module): |
| if hasattr(module, '__path__'): |
| raise ImportError, "don't know how to unload packages yet" |
| PackageImporter.unload(self, module) |
| |
| def install(self): |
| if not sys.modules.has_key(''): |
| sys.modules[''] = package = imp.new_module('') |
| package.__path__ = None |
| self.loader.init_package(package) |
| for m in sys.modules.values(): |
| if not m: continue |
| if not hasattr(m, '__'): |
| self.loader.set_parent(m) |
| ModuleImporter.install(self) |
| |
| |
| def install(v = 0): |
| ihooks.install(PackageImporter(None, v)) |
| |
| def uninstall(): |
| ihooks.uninstall() |
| |
| def ni(v = 0): |
| install(v) |
| |
| def no(): |
| uninstall() |
| |
| def test(): |
| import pdb |
| try: |
| testproper() |
| except: |
| sys.last_type, sys.last_value, sys.last_traceback = ( |
| sys.exc_type, sys.exc_value, sys.exc_traceback) |
| print |
| print sys.last_type, ':', sys.last_value |
| print |
| pdb.pm() |
| |
| def testproper(): |
| install(1) |
| try: |
| import mactest |
| print dir(mactest) |
| raw_input('OK?') |
| finally: |
| uninstall() |
| |
| |
| if __name__ == '__main__': |
| test() |
| else: |
| install() |