17.2.6.5 从shelf导入
查找工具找到一个模块时,它要负责返回一个能够哦导入该模块的加载工具(loader)。这一节的例子展示了一个定制导入工具,它会将它的模块内容保存到由shelve创建的一个数据库中。
首先,使用一个脚本以一个包(其中包含一个子模块和子包)来填充这个shelf。
import shelve
import os
filename = '/tmp/pymotw_import_example.shelve'
if os.path.exists(filename + '.db'):
os.unlink(filename + '.db')
with shelve.open(filename) as db:
db['data:README'] = b"""
==============
package README
==============
This is the README for "package".
"""
db['package.__init__'] = b"""
print('package imported')
message = 'This message is in package.__init__'
"""
db['package.module1'] = b"""
print('package.module1 imported')
message = 'This message is in package.module1'
"""
db['package.subpackage.__init__'] = b"""
print('package.subpackage imported')
message = 'This message is in package.subpackage.__init__'
"""
db['package.subpackage.module2'] = b"""
print('package.subpackage.nodule2 imported')
message = 'This message is in package.subpackage.module2'
"""
db['package.with_error'] = b"""
print('package.with_error being imported')
raise ValueError('raising exception to break import')
"""
print('Created {} with:'.format(filename))
for key in sorted(db.keys()):
print(' ',key)
真正的打包脚本会从文件系统读取内容,不过对于像这样一个简单的例子来说,使用硬编码的值就足够了。
这个定制导入工具需要提供查找工具和加载工具,它们要知道如何在shelf中查找模块或包的源代码。
import imp
import os
import shelve
import sys
def _mk_init_name(fullname):
"""return the name of the __init__ module
for a given package name.
"""
if fullname.endswith('.__init__'):
return fullname
return fullname + '.__init__'
def _get_key_name(fullname,db):
"""Look in an open shelf for fullname or
fullname.__init__,and return the name found.
"""
if fullname in db:
return fullname
init_name = _mk_init_name(fullname)
if init_name in db:
return init_name
return None
class ShelveFinder:
"""Find modules collected in a shelve archive."""
_maybe_recursing = False
def __init__(self,path_entry):
# Loading shelve creates an import recursive loop when it
# imports dbm,and we know we will not load the
# module being imported. Thus,when we seem to be
# recursing,just ignore the request so another finder
# will be used.
if ShelveFinder._maybe_recursing:
raise ImportError
try:
# Test the path_entry to see if it is a valid shelf.
try:
ShelveFinder._maybe_recursing = True
with shelve.open(path_entry,'r'):
pass
finally:
ShelveFinder._maybe_recursing = False
except Exception as e:
print('shelf added to import from {}:{}'.format(
path_entry,e))
raise
else:
print('shelf added to import path:',path_entry)
self.path_entry = path_entry
return
def __str__(self):
return '<{} for {!r}'.format(self.__class__.__name__,
self.path_entry)
def find_module(self,fullname,path=None):
path = path or self.path_entry
print('\nlooking for {!r}\n in {}'.format(
fullname,path))
with shelve.open(self.path_entry,'r') as db:
key_name = _get_key_name(fullname,db)
if key_name:
print(' found it as {}'.format(key_name))
print(' not found')
return None
class ShelveLoader:
"""Load source for modules from shelve databases."""
def __init__(self,path_entry):
self.path_entry = path_entry
return
def _get_filename(self,fullname):
# Make up a fake filename that starts with the path entry
# so pkgutil.get_data() works correctly.
return os.path.join(self.path_entry,fullname)
def get_source(self,fullname):
print('loading source for {!r} from shelf'.format(
fullname))
try:
with shelve.open(self.path_entry,'r') as db:
key_name = _get_key_name(fullname,db)
if key_name:
return db[key_name]
raise ImportError(
'could not find source for {}'.format(
fullname)
)
except Exception as e:
print('couold not load source:',e)
raise ImportError(str(e))
def get_code(self,fullname):
source = self.get_source(fullname)
print('compiling code for {!r}'.format(fullname))
return compile(source,self._get_filename(fullname),
'exec',dont_inherit=True)
def get_data(self,path):
print('looking for data\n in {}\n for {!r}'.format(
self.path_entry,path))
if not path.startswith(self.path_entry):
raise IOError
path = path[len(self.path_entry) + 1:]
key_name = 'data:' + path
try:
with shelve.open(self.path_entry,'r') as db:
return db[key_name]
except Exception:
# Convert all errors to IOError.
raise IOError()
def is_package(self,fullname):
init_name = _mk_init_name(fullname)
with shelve.open(self.path_entry,'r') as db:
return init_name in db
def load_module(self,fullname):
source = self.get_source(fullname)
if fullname in sys.modules:
print('reusing module from import of {!r}'.format(
fullname))
mod = sys.modules[fullname]
else:
print('creating a new module object for {!r}'.format(
fullname))
mod = sys.modulessetdefault(
fullname,
imp.new_module(fullname)
)
# Set a few properties required by PEP 302.
mod.__file__ = self._get_filename(fullname)
mod.__name__ = fullname
mod.__path__ = self.path_entry
mod.__loader__ = self
# PEP-366 specifies that packages set __packaeg__ to
# their name,and modules have it set to their parent
# package (if any).
if self.is_package(fullname):
mod.__package__ = fullname
else:
mod.__package__ = '.'.join(fullname,split('.')[:-1])
if self.is_package(fullname):
print('adding path for package')
# Set __path__ for packages
# so we can find the submodules.
mod.__path__ = [self.path_entry]
else:
print('imported as regular module')
print('execing source...')
exec(source,mod.__dict__)
print('done')
return mod
现在可以用shelveFinder和ShelveLoader从一个shelf导入代码。例如,可以用以下代码导入刚创建的package。
import sys
import sys_shelve_importer
def show_module_details(module):
print(' message :',module.message)
print(' __name__ :',module.__name__)
print(' __package__ :',module.__package__)
print(' __file__ :',module.__file__)
print(' path__ :',module.__path__)
print(' __loader__ :',module.__loader__)
filename = '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0,filename)
print('Import of "package":')
import package
print()
print('Examine package details:')
show_module_details(package)
print()
print('Global settings:')
print('sys.modules entry:')
print(sys.modules['package'])
修改路径之后,第一次出现导入时会把这个shelf增加到导入路径。查找工具找到这个shelf,并返回一个加载工具,这个加载工具将用于完成来自该shelf的所有导入。初始的包级别导入会创建一个新的模块对象,然后使用exec来运行从shelf加载的源代码。它将这个新模块作为命名空间,所以源代码中定义的名字可以作为模块级属性保留。