如何将 Python 脚本从一个子包移动到另一个 directory/package,保持向后兼容性

How to move a Python script from one subpackage to another directory/package, maintaining backwards compatibility

我有一个共享的 Python 代码库,我负责其他人依赖的代码。我需要将模块从一个子包移动到另一个 directory/package 以重新组织它。我该如何以最安全的方式做到这一点?

如果我只是移动代码,我不得不担心使用它的其他人可能没有重定向他们的导入。如果它被移动并且代码的用户不更改他们的导入,他们的代码将在导入失败时意外失败。

如何确保无缝过渡?我是否只复制代码并将旧代码留在原处,直到导入被更改?有什么注意事项需要注意吗?如果我将 import *__all__ 结合使用会怎样?在什么情况下我必须无限期地支持从旧位置导入?

我最近在工作中被要求将代码从一个子包移动到另一个子包,而我使用的方法对其他开发人员来说似乎并不明显,所以我在这里为其他人记录下来。

我不建议在老地方留一份。如果您有同一个脚本的两个副本,一个很可能会在没有另一个的情况下发生变化。相反,我推荐以下多步骤过程。

第一步涉及两个部分,如果您控制代码的两个位置,则可以同时实现这两个部分。

第一步:实施移动

  1. 首先,在版本控制下将文件从旧位置移动到新位置。我使用一个简化的 CVS 接口,所以它是一个版本控制副本。在大多数其他版本控制系统(如 mercurial、subversion 和 git)中,您应该使用 mv 来移动文件,例如git:

     git mv /location/old/script.py /location/new/script.py
    

重要提示:

不要忘记也移动单元测试,如果旧的单元测试中有代码需要保留,也请移动 __init__.py。否则,如果 __init__.py 尚未到位或

,请确保将其提交到那里
  1. 接下来,在旧代码的位置,从新位置导入所有名称,

    因此 /location/old/script.py:

     from location.new.script import *
    

    并留下评论解释为什么需要这样做,并将更改提交到版本控制。如果您移动了 __init__.py,只需确保提交一个新的空 __init__.py.

这里有一个主要警告import *__all__ 影响。如果你有一个 __all__ 声明,你有两种方法来提供缺失的名字。您可以将它们全部显式导入:

from location.new.script import *
# names not in the new.script's __all__:
from location.new.script import foo, bar, baz 

您可以删除文件并改为在__init__.py中导入模块,然后将路径添加到sys.modules,如下所示:

from location.new import script
import sys
sys.modules['location.old.script'] = script

此代码将初始化包并将模块添加到 sys.modules,以便进口商在那里及时查找。这与在 Python source 中创建 os.path 的方式相同。但是,大多数人会回避修改 sys.modules。事实上,我犹豫是否要在这里留下这个建议,如果它不在 Python 标准库中,我也不会。

这两个部分可以一起投入生产,并且移动已经无缝实施。如果您无法控制代码的用户,为了向后兼容,这可能需要无限期保留。

可选:然后我会删除 head 的旧脚本(就在 head,不要推送它!)以便其他开发人员可以看到即将发生的变化并及时解决变化。

第二步:实施重新引用

如果您可以对依赖于您的代码的所有代码进行正则表达式搜索,我建议在代码中搜索以下正则表达式:

(import|from).*location\.old.*script

如果您使用的是 Unix(或 Cygwin),您可以使用正则表达式搜索它:

grep -rEe "(import|from).*location\.old.*script" .

或者大多数 IDE 都有正则表达式搜索。

如果您确实可以控制使用它的代码,或者您对使用它的其他人有看法,那么将导入从旧的更改为新的是相当简单的,例如来自 :

import location.old.script

import location.new.script

来自

from location.old import script

from location.new import script 

以此类推

重要提示:

所有这些更改都需要实施并发布到生产环境中。如果在没有完成这些操作的情况下仍然存在任何生产安装,如果您删除旧位置,它们将失败。

第三步:删除生产中的旧脚本

这是危险的一步。如果您遗漏了任何 users/importers,他们的代码将失败,直到他们将导入修复程序投入生产。您可以选择无限期推迟这一步,但我更愿意及时完成它,前提是我能证明所有更改都已投入生产。

如果您在进行更改后立即将其删除,以便其他人可以看到开发中的更改,您可能就不用担心了。

不过,在您可以证明没有其他用户在生产环境中引用旧包位置之前,请不要删除它。不能证明就别删

chuckmove 可能适合您。 chuckmove 是一种工具,可让您递归地重写整个源代码树中的导入以引用模块的新位置。

chuckmove --old sound.utils --new media.sound.utils src

...这会下降到 src,并将导入 sound.utils 的语句重写为导入媒体。sound.utils。它支持整个范围的 Python 导入格式。 IE。 from x import yimport x.y.z as w