第十七章:运行时特性-sys:系统特定配置-模块和导入-从shelf导入

本文探讨了Python中使用shelf进行模块和包导入的过程。通过定制的导入工具,如ShelveFinder和ShelveLoader,代码可以从shelf数据库中加载。文章详细介绍了如何填充shelf、实现查找和加载工具,以及如何使用这些工具进行导入。

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加载的源代码。它将这个新模块作为命名空间,所以源代码中定义的名字可以作为模块级属性保留。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值