mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
	
	
		
			391 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			391 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | """New import scheme with package support.
 | ||
|  | 
 | ||
|  | 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() |