如何将模块从根移动到子包以保持向后兼容性

How to move a module from root to a subpackage maintinaing backwards compatability

这是项目的原始结构:

project/
  mypackage
    __init__.py
    moduleA.py

我想将moduleA.py移动到子包中:

project/
  mypackage
    __init__.py
    subpackage
      __init__.py
      moduleA.py

我需要保持向后兼容性以便使用import mypackage.moduleA的旧代码仍然有效。

我尝试将此添加到 project/mypackage/__init__.py:

from subpackage import moduleA

这让我可以 import mypackage.moduleA。但不幸的是,它会在导入 mypackage 后立即强制导入 moduleA,这不是我想要的(我将 moduleA 设为可选包,因此无法保证依赖关系)。

import mypackage 运行时,我仍然可以启用使用惰性模块加载的 mypackage.moduleA 导入吗?用户应显式导入 mypackage.moduleA 以触发导入。

您可以使用 module level getattr 挂钩在首次使用时延迟加载“moduleA”。

# in mypackage/__init__.py

some_other_name = 123

def __getattr__(name):
    global moduleA
    if name == "moduleA":
        from mypackage.subpackage import moduleA
        return moduleA
    raise AttributeError(name)

需要 Python-3.7+。请注意,getattr 仅针对命名空间中未找到的名称调用,因此 from mypackage import some_other_name 不会调用该挂钩,也不会触发子包的提前导入。

这会起作用:

from mypackage import moduleA

这也有效:

import mypackage
mypackage.moduleA

虽然请注意保持向后兼容性的一个重要警告。直接子模块导入将工作,因为子模块不是其实直接导入系统就能找到:

import mypackage.moduleA

为避免破坏这种形式的导入语句,您仍可以包含一个 mypackage/moduleA.py 文件(可以作为弃用垫片)

# in mypackage/moduleA.py
import warnings
warnings.warn(
    message="mypackage.moduleA has moved to mypackage.subpackage.moduleA",
    category=DeprecationWarning,
    stacklevel=2,
)

from mypackage.subpackage.moduleA import *

弃用通知应该会持续几个版本,然后您可以在下一个中断版本中完全删除 mypackage/moduleA.py,确保 mention the change in your release notes.