importlib.reload() 重新加载模块时提示 AttributeError: ‘NoneType‘ object has no attribute ‘name‘ 解决办法

在做插件式框架开发时,需要用到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

你可能感兴趣的:(python,python,开发语言)