如何在 Python 包中安全地 "fake" 模块

How to "fake" a module safely in a Python package

目前我在 master git 分支中有以下目录结构:

/dir1
    __init__.py
    module.py

这将更改为(在我的分支中):

/dir1
    __init__.py
    /dir2
        module1.py # With 70% of code of module.py
        module2.py # With 30% of code of module.py

问题:

  1. 我知道不可能让 git 跟踪两个新文件,但由于 git 识别重命名(并且它考虑将文件夹组织为重命名)我将能够跟踪从主分支到我分支的 module1.pymodule.py 的更改,至少对于 70% 的代码(我必须手动更新 module2.py)。那么有没有更好的方法来处理这个问题呢?

  2. 为了 API 的一致性,我希望使用旧版本我的包的人仍然使用 from dir1.module import abc(没有 module.pydir1) 这可以像 here 中描述的那样完成,但这样做会带来混淆 sys 路径变量的危险,出于稳定性和安全考虑,不建议这样做。有没有更好的方法可以让 API 向后兼容并且仍然 safe/stable?


不过,我的情况比较复杂。对于更具代表性的示例,请考虑从:

/dir1
    __init__.py
    module_2.py
        def add2(a, b):
            return a + b
        def sub2(a, b):
            return a - b
    module_3.py
        def add3(a, b, c):
            return a + b + c

至:

/dir1
    __init__.py
    /dir2
        __init__.py
        module_add.py
            # ... Constitutes 70% of code from `dir1/module_2.py`
            def add2(a, b):
                return a + b
            # A few more additional lines added from `dir1/module_3.py`
            def add3(a, b, c):
                return a + b + c
        module_sub.py
            # Constitutes the 30% from /dir1/module2.py
            def sub2(a, b):
                return a - b

所以本质上我将 dir1/module_2.pydir1/module_3.py 的不同功能分开,并将它们重新组合成单​​独的 module_add.pymodule_sub.py 并将其放在 /dir1/dir2

但是,获取版本 2 包的版本 1 用户应该仍然可以执行以下操作:

from module_2 import add2, sub2
from module_3 import add3

我不能做的事情:

注意以下设置:

/dir1
    __init__.py
        from module import abc
    module.py
        abc = None

从外部(几乎)无法区分:

/dir1
    __init__.py
        from module import abc
    /module
        __init__.py
           from module1 import abc
        module1.py  # this is the moved and renamed module.py, with git history
            abc = None
        module2.py  # this is the 30% you've factored out
            # whatever's in here

外部 module.py/module,旧的导入from module import abc(和from dir1.module import abc等)继续工作。


对于更复杂的示例,您仍然可以从:

/dir1
    __init__.py
        from module_2 import add2, sub2
        from module_3 import add3
    module_2.py
    module_3.py

至:

/dir1
    __init__.py
        from dir2.module_add import add2, add3
        from dir2.module_sub import sub2
    /dir2
        __init__.py
        module_add.py  # module_2.py moved & renamed
        module_sub.py  # module_3.py moved & renamed or new file
    /module_2
        __init__.py
           from ..dir2.module_add import add2
           from ..dir2.module_sub import sub2
    /module_3
        __init__.py
           from ..dir2.module_add import add3

旧代码(例如 from dir1.module_2 import add2)仍然可以正常工作,但用户现在可以开始访问新位置(例如 from dir1.dir2.module_add import add2, add3)。


您还可以添加例如:

import warnings
warnings.warn("deprecated", DeprecationWarning)

/dir1/module_2/dir1/module_3 中的 __init__.py 文件,以向用户发出警告,告知这些导入内容现在即将退出。例如:

>>> import warnings
>>> warnings.simplefilter('always')
>>> from dir1.dir2.module_sub import sub2
>>> sub2(1, 2)
-1
>>> from dir1.module_3 import add3

Warning (from warnings module):
  File "dir1\module_3\__init__.py", line 2
    warnings.warn("deprecated", DeprecationWarning)
DeprecationWarning: deprecated
>>> add3(1, 2, 3)
6