我正在尝试实现PEP 302中指定的导入器协议来创建Java样式的包导入器机制。这样做的好处是,包命名空间可以合并虚拟,但实际上并没有物理合并。
假设sys.path
上列出的两个目录名都包含文件夹org
。但是两个文件夹都有不同的内容。
文件夹1
org/
a_module.py
文件夹2
org/
a_package/
__init__.py
tools.py
现在,从Python代码开始,我想编写以下内容,以便能够将a_module
和a_package.tools
导入到我的Python程序中。
sys.meta_path.append(PackageImporter('org'))
import org.a_module
import org.a_package.tools as tools
现在,我遇到的一个奇怪的问题是,从通过我的自定义加载程序加载的包中导入os
模块,我得到ImportError: no module name path
。不过我可以毫无问题地导入很多其他软件包!
例如,org/a_module.py
# file: org/a_module.py
import os
test.py
# file: test.py
sys.meta_path.append(PackageImporter())
import org.a_module # ImportError: no module named path
我该如何解决这个问题?
遗憾的是,代码有点冗长。
import os
import sys
import imp
class PackageImporter(object):
def trace_module(self, dirname, parts):
r""" Traces a module by the specified import *parts*. Returns
the full path to the module that is to be imported. Directories
will later by imported by the EmptyModuleLoader class and files
by the ModuleLoader class. """
for part in parts[:-1]:
dirname = os.path.join(dirname, part)
if not os.path.isdir(dirname):
return None
directory = os.path.join(dirname, parts[-1])
modulefile = os.path.join(dirname, '%s.py' % parts[-1])
initfile = os.path.join(directory, '__init__.py')
if os.path.isfile(modulefile):
return modulefile
elif os.path.isfile(initfile):
return initfile
elif os.path.isdir(directory):
return directory
else:
return None
def find_module(self, fullname, path=None):
if path is None:
path = sys.path
parts = fullname.split('.')
fullpath = None
for dirname in path:
fullpath = self.trace_module(dirname, parts)
if fullpath:
break
if fullpath:
if os.path.isdir(fullpath):
return EmptyModuleLoader()
else:
return ModuleLoader(fullpath)
return None
class EmptyModuleLoader(object):
def load_module(self, fullname):
mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
mod.__file__ = '<lazy package>'
mod.__package__ = fullname
mod.__loader__ = self
mod.__path__ = sys.path
return mod
class ModuleLoader(object):
def __init__(self, filename):
self.filename = filename
def load_module(self, fullname):
mod = sys.modules.get(fullname)
if mod:
return mod
description = ('.py', 'r', imp.PY_SOURCE)
with open(self.filename) as fp:
mod = imp.load_module(fullname, fp, self.filename, description)
mod.__file__ = self.filename
mod.__loader__ = self
if os.path.basename(self.filename) == '__init__.py':
mod.__package__ = fullname
mod.__path__ = sys.path
else:
mod.__package__ = fullname.rpartition('.')[0]
return mod
sys.meta_path.append(PackageImporter())
import org
import org.a_module
import org.a_package.tools as tools
以上代码是GitHub上当前实现的缩写形式。提出这个问题的国家是c77c1d4e。
对我来说最令人困惑的事实是,我的导入器甚至没有被os.path
查询,而是我得到了org.posixpath
和org.genericpath
的全名值??
def find_module(self, fullname, path=None):
if 'path' in fullname:
print "="*40
print fullname
print "="*40
Niklass-MacBook-Air:Desktop niklas$ python test.py
========================================
org.posixpath
========================================
========================================
org.genericpath
========================================
Traceback (most recent call last):
File "test.py", line 94, in <module>
import org.a_module
File "test.py", line 79, in load_module
mod = imp.load_module(fullname, fp, self.filename, description)
File "/Users/niklas/Desktop/org/a_module.py", line 2, in <module>
import os
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py", line 120, in <module>
from os.path import (curdir, pardir, sep, pathsep, defpath, extsep, altsep,
ImportError: No module named path
目前,我已添加了声明
from __future__ import absolute_import
在执行修复问题的Python模块之前进入源代码。但这听起来不是一个很好的方法。