Поддельные пакеты пространства имен в Python 2
PEP 302. В новых хуках импорта указаны способы подключения механизма импорта 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
,
Искатель получит звонок .find_module(fullname, path)
с 'mypackage'
но никогда с '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
Изменить: я обнаружил, что правильные слова, чтобы описать то, что я хочу достичь, это "пакет пространства имен"
1 ответ
По-видимому, __path__
атрибут должен быть установлен, иначе механизм импорта не будет вызывать искатели / загрузчики в sys.meta_path
, Поэтому, когда модуль был загружен, просто сделайте
# __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)