如何在 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
问题:
我知道不可能让 git
跟踪两个新文件,但由于 git
识别重命名(并且它考虑将文件夹组织为重命名)我将能够跟踪从主分支到我分支的 module1.py
对 module.py
的更改,至少对于 70% 的代码(我必须手动更新 module2.py
)。那么有没有更好的方法来处理这个问题呢?
为了 API 的一致性,我希望使用旧版本我的包的人仍然使用 from dir1.module import abc
(没有 module.py
在 dir1
) 这可以像 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.py
和 dir1/module_3.py
的不同功能分开,并将它们重新组合成单独的 module_add.py
和 module_sub.py
并将其放在 /dir1/dir2
但是,获取版本 2 包的版本 1 用户应该仍然可以执行以下操作:
from module_2 import add2, sub2
from module_3 import add3
我不能做的事情:
- 在
dir1
中有module_2.py
或module_3.py
(我需要git来关联跟踪master分支的dir1/module_2.py
到dir1/dir2/module_2.py
的我的分支);
- 以任何减少 stability/safety 的方式改变或扰乱
sys.path
;或
- 将
dir2
重命名为例如module_2
.
注意以下设置:
/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
目前我在 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
问题:
我知道不可能让
git
跟踪两个新文件,但由于git
识别重命名(并且它考虑将文件夹组织为重命名)我将能够跟踪从主分支到我分支的module1.py
对module.py
的更改,至少对于 70% 的代码(我必须手动更新module2.py
)。那么有没有更好的方法来处理这个问题呢?为了 API 的一致性,我希望使用旧版本我的包的人仍然使用
from dir1.module import abc
(没有module.py
在dir1
) 这可以像 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.py
和 dir1/module_3.py
的不同功能分开,并将它们重新组合成单独的 module_add.py
和 module_sub.py
并将其放在 /dir1/dir2
但是,获取版本 2 包的版本 1 用户应该仍然可以执行以下操作:
from module_2 import add2, sub2
from module_3 import add3
我不能做的事情:
- 在
dir1
中有module_2.py
或module_3.py
(我需要git来关联跟踪master分支的dir1/module_2.py
到dir1/dir2/module_2.py
的我的分支); - 以任何减少 stability/safety 的方式改变或扰乱
sys.path
;或 - 将
dir2
重命名为例如module_2
.
注意以下设置:
/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