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