在做插件式框架开发时,需要用到importlib.reload()方法做插件重载,在重载存在__init__.py的模块时,出现了以下报错:AttributeError: 'NoneType' object has no attribute 'name'
逐语句定位至reload()方法,在堆栈信息中查看到以下信息:
File "xxxx\Python\Python36\lib\importlib\__init__.py", line 166, in reload
_bootstrap._exec(spec, module)
│ │ │ └ <module 'build.Plugins' from 'Scripts\\build\\Plugins\\__init__.py'>
│ │ └ None
│ └ <function _exec at 0x000002C073156AE8>
└ <module 'importlib._bootstrap' (frozen)>
File "" , line 600, in _exec
AttributeError: 'NoneType' object has no attribute 'name'
找到importlib._bootstrap报错位置
def reload(module):
"""Reload the module and return it.
The module must have been successfully imported before.
"""
if not module or not isinstance(module, types.ModuleType):
raise TypeError("reload() argument must be a module")
try:
name = module.__spec__.name
except AttributeError:
name = module.__name__
if sys.modules.get(name) is not module:
msg = "module {} not in sys.modules"
raise ImportError(msg.format(name), name=name)
if name in _RELOADING:
return _RELOADING[name]
_RELOADING[name] = module
try:
parent_name = name.rpartition('.')[0]
if parent_name:
try:
parent = sys.modules[parent_name]
except KeyError:
msg = "parent {!r} not in sys.modules"
raise ImportError(msg.format(parent_name),
name=parent_name) from None
else:
pkgpath = parent.__path__
else:
pkgpath = None
target = module
spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, target)
_bootstrap._exec(spec, module)
# The module may have replaced itself in sys.modules!
return sys.modules[name]
finally:
try:
del _RELOADING[name]
except KeyError:
pass
加断点调试,发现module.__spec__在_bootstrap._find_spec(name, pkgpath, target)
语句执行后变成了None,所以导致了_bootstrap._exec(spec, module)
执行的时候报错,spec为None。
这个函数是这样的:
def _find_spec(name, path, target=None):
"""Find a module's spec."""
meta_path = sys.meta_path
print('meta_path1',meta_path)
if meta_path is None:
# PyImport_Cleanup() is running or has been called.
raise ImportError("sys.meta_path is None, Python is likely "
"shutting down")
if not meta_path:
_warnings.warn('sys.meta_path is empty', ImportWarning)
# We check sys.modules here for the reload case. While a passed-in
# target will usually indicate a reload there is no guarantee, whereas
# sys.modules provides one.
is_reload = name in sys.modules
print('meta_path2',meta_path)
for finder in meta_path:
with _ImportLockContext():
try:
find_spec = finder.find_spec
except AttributeError:
spec = _find_spec_legacy(finder, name, path)
if spec is None:
continue
else:
spec = find_spec(name, path, target)
print(finder,name, path, target)
if spec is not None:
# The parent import may have already imported this module.
if not is_reload and name in sys.modules:
module = sys.modules[name]
try:
__spec__ = module.__spec__
except AttributeError:
# We use the found spec since that is the one that
# we would have used if the parent module hadn't
# beaten us to the punch.
return spec
else:
if __spec__ is None:
return spec
else:
return __spec__
else:
return spec
else:
return None
单独调试这个函数也是没问题的,从sys.meta中获取加载spec的类,实际上在
这个类中是可以正常加载返回spec的
>>> sys.meta_path[2].find_spec('build.Plugins',sys.modules['build.Plugins'.rpartition('.')[0]].__path__,build.Plugins)
ModuleSpec(name='build.Plugins', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000002890E81EFD0>, origin='\\build\\Plugins\\__init__.py', submodule_search_locations=['\\Scripts\\build\\Plugins'])
在_bootstrap._find_spec(name, pkgpath, target)
加个打印,发现问题出在pkgpath这个变量上,打印这个变量显示为
_NamespacePath(['C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python36\\lib\\site-packages\\build'])
原本应该指向插件目录的地址指向了site-packages,而之所以这样,是因为我在导入这个模块的时候是通过sys.path.insert方法,将插件路径插入到环境变量,再import插件,原代码如下:
def loadModuleByPath(testModule):
"""
函数功能: 从指定绝对路径加载模块
Args:
testModule (str): 模块路径
Returns:
module: 通过importlib.import_module方法加载的模块
"""
_path = Path(testModule)
sys.path.insert(0,str(_path.parent))
module = importlib.import_module(_path.name)
importlib.reload(module)
sys.path.remove(str(_path.parent))
return module
修改为以下代码,从绝对路径引入,就解决了这个问题。
def loadModuleByPath(testModule):
"""
函数功能: 从指定绝对路径加载模块
Args:
testModule (str): 模块路径
Returns:
module: 通过importlib.import_module方法加载的模块
"""
_path = Path(testModule)
module = importlib.import_module(_path.as_posix().replace('/','.'))
importlib.reload(module)
return module