【问题标题】:Fake Namespace Packages in Python 2Python 2 中的假命名空间包
【发布时间】:2014-10-02 16:41:46
【问题描述】:

PEP 302 -- New Import Hooks 指定了挂钩 Python 导入机制的方法。其中之一是创建一个模块查找器/加载器并将其添加到sys.meta_path

我试图创建一个能够重新路由子包导入的模块查找器。所以假设我写了import mypackage.sub,它实际上应该导入模块mypackage_sub

mypackage.sub                => mypackage_sub
mypackage.sub.another        => mypackage_sub.another
mypackage.sub.another.yikes  => mypackage_sub.another.yikes

我尝试实现这样的模块查找器没有成功。目前,我将 finder 类安装在 Python 主文件中,但我的目标是将其安装在根包 mypackage 中。

发现者会收到带有'mypackage'.find_module(fullname, path) 的呼叫,但绝不会带有'mypackage.sub' 的呼叫。因此,我只收到以下代码的以下错误。

Traceback (most recent call last):  
  File "test.py", line 63, in <module>  
    import mypackage.sub  
ImportError: No module named sub
import sys
sys.meta_path.append(_ExtensionFinder('mypackage'))
import mypackage.sub

这是_ExtensionFinder 类的代码。我很欣赏任何允许我将这个_ExtensionFinder 类放入根模块mypackage 的解决方案。

class _ExtensionFinder(object):
    """
    This class implements finding extension modules being imported by
    a prefix. The dots in the prefix are converted to underscores and
    will then be imported.

    .. seealso:: PEP 302 -- New Import Hooks
    """

    def __init__(self, prefix):
        super(_ExtensionFinder, self).__init__()
        self.prefix = prefix

    def _transform_name(self, fullname):
        if self.prefix == fullname:
            return fullname
        elif fullname.startswith(self.prefix + '.'):
            newpart = fullname[len(self.prefix) + 1:]
            newname = self.prefix.replace('.', '_') + '_' + newpart
            return newname
        return None

    def find_module(self, fullname, path=None):
        print "> find_module({0!r}, {1!r})".format(fullname, path)

        newname = self._transform_name(fullname)
        if newname:
            return self

    def load_module(self, fullname):
        print "> load_module({0!r})".format(fullname)

        # If there is an existing module object named 'fullname'
        # in sys.modules, the loader must use that existing module.
        if fullname in sys.modules:
            return sys.modules[fullname]

        newname = self._transform_name(fullname)
        if newname in sys.modules:
            sys.modules[fullname] = sys.modules[newname]
            return sys.modules[newname]

        # Find and load the module.
        data = imp.find_module(newname)
        mod = imp.load_module(newname, *data)

        # The __loader__ attribute must be set to the loader object.
        mod.__loader__ = self

        # The returned module must have a __name__, __file__
        # and __package__ attribute.
        assert all(hasattr(mod, n) for n in ['__name__', '__file__', '__package__'])

        return mod

编辑:我发现描述我想要实现的目标的正确词是“命名空间包”

【问题讨论】:

  • 你可以在mypackage.py(或__init__.py):import mypackage_sub as sub,如果这就是你所需要的。
  • @matsjoyce 应该可以动态添加子包,根本不用碰mypackage.py :)
  • 我刚刚发现 pkgutil.extend_path() 看起来已经非常有用了,不幸的是,这需要将每个单独的命名空间包保存在 sys.path 的不同目录中,这也不应该是必需的。
  • 您希望插入/元路径修改发生在哪里?在__main__.py?还是mypackage_sub
  • @matsjoyce mypackage 应该修改元路径,以便可以直接导入任何子包而无需进一步麻烦(因为 mypackage 将在尝试导入 mypackage.sub 之前导入)

标签: python-2.7 python-2.x import-hooks


【解决方案1】:

显然__path__属性必须设置,否则导入机制不会调用sys.meta_path中的finders/loaders。所以当模块被加载时,就做

        # __path__ must be set on the module, otherwise submodules
        # will not be loaded using the _ExtensionFinder.
        if not hasattr(mod, '__path__'):
            mod.__path__ = []

这也适用于根模块mypackage。这是完整的mypackage.py 文件。

__all__ = []

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Extension import hook
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

import sys
import imp
import importlib

# The __path__ variable must be set in the module, otherwise the
# _ExtensionFinder will not be invoked for sub-modules.
__path__ = []

class _ExtensionFinder(object):
    """
    This class implements finding extension modules being imported by
    a prefix. The dots in the prefix are converted to underscores and
    will then be imported.

    .. seealso:: PEP 302 -- New Import Hooks
    """

    def __init__(self, prefix):
        super(_ExtensionFinder, self).__init__()
        self.prefix = prefix

    def _check_name(self, fullname):
        if self.prefix == fullname:
            return 1
        elif fullname.startswith(self.prefix + '.'):
            return 2
        return 0

    def _transform_name(self, fullname):
        value = self._check_name(fullname)
        if value == 1:
            return fullname
        elif value == 2:
            newpart = fullname[len(self.prefix) + 1:]
            newname = self.prefix.replace('.', '_') + '_' + newpart
            return newname
        return None

    def find_module(self, fullname, path=None):
        if self._check_name(fullname):
            return self

    def load_module(self, fullname):
        # If there is an existing module object named 'fullname'
        # in sys.modules, the loader must use that existing module.
        if fullname in sys.modules:
            return sys.modules[fullname]

        # Get the name of the module that actually needs to be
        # imported for this extension.
        newname = self._transform_name(fullname)

        # If it already exists, just fill up the missing entry for
        # the extension name.
        if newname in sys.modules:
            sys.modules[fullname] = sys.modules[newname]
            return sys.modules[newname]

        # Load the module and add additional attributes.
        mod = importlib.import_module(newname)
        mod.__loader__ = self

        # __path__ must be set on the module, otherwise submodules
        # will not be loaded using the _ExtensionFinder.
        if not hasattr(mod, '__path__'):
            mod.__path__ = []

        # The returned module must have a __name__, __file__
        # and __package__ attribute.
        assert all(hasattr(mod, n) for n in ['__name__', '__file__', '__package__'])

        return mod

_ext_finder = _ExtensionFinder('mypackage')
sys.meta_path.append(_ext_finder)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-21
    • 2017-05-28
    • 1970-01-01
    • 2021-08-09
    • 2011-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多