Publication帮助您通过防止通过内省无意访问私有实现细节来维护公共API友好的模块。
项目描述
在Python库中设定您可依赖的API的期望是非常困难的。
Publication使这变得容易。
问题
正如Hyrum的定律有些悲观地所说,
当一个API有足够多的用户时,你在合同中所承诺的都不重要你系统的所有可观察行为都将被某人依赖。
一般来说,Python在这方面有一个相当不同的哲学观点。我们假定自己是负责任的库使用者。篡改实现细节可能在每次升级时都会破坏,但鉴于它在测试、调试和实验中的足够有用性,保留这种能力是值得付出代价的。
但是,这个假设的关键是每个人都 知道 当他们进入图书馆界面的“私有”区域时。在这里,存在期望上的不匹配。
库作者 编写文档,然后认为用户会坐下来从头到尾阅读文档,通过这种方式了解“公共”接口。他们接着假设,如果用户偏离了这些记录的功能,他们就会知道他们使用了私有实现细节。
库用户 使用 pip install 安装某个东西,打开一个 REPL,导入模块,并通过在模块及其内容上执行 dir() 来发现库,假设只要他们从未在执行过程中键入过 library._something_private(),他们的程序就不会使用任何私有实现细节。如果他们遇到回溯,他们可能会简要查阅文档,直到问题解决。
发布可以使这些群体的期望得到一致,这样用户仍然可以从中受益,如果他们想使用内部细节,但他们会知道他们在这样做。这使得你的模块的运行时命名空间看起来就像你的库的公共文档。
在实践中,这看起来是怎样的呢?
你,一个潜在的库作者,想写一个使 zorf sprocket 变得容易的库。太棒了!你做到了,看起来像这样
# sprocket_zorfer.py
from sprocket import sprocket_with_name
from zorf import zorfable_thing
def zorf_sprocket_internal(sprocket, zorfulations):
...
def compute_zorfulations():
...
def zorf_sprocket_named(sprocket_name, how_much):
sprocket = sprocket_with_name(sprocket_name)
zorfulations = compute_zorfulations(how_much)
return zorf_sprocket_internal(sprocket, zorfulations)
__all__ = [
'zorf_sprocket_named'
]
当然,你的意图是公开一个模块,其中只有一个函数:zorf_sprocket_named,其他一切都是实现细节。你甚至通过 __all__ 明确地说出了这一点。你的 API 文档也是这样说的。
然而,阅读参考文档并干净地遵守约定并不是真正的工作程序员确定如何使用东西的方式。你的用户都做类似的事情
加载一个交互式 python 解释器并在你的模块上调用 dir()
安装 Jupyter 并使用 Tab 完成功能来查找他们想要的
在 PyCharm 中使用自动导入功能来获取一些私有实现细节
然后,你发现你的库有成千上万的用户,他们的代码如下
from sprocket_zorfer import compute_zorfulations, zorf_sprocket_internal, sprocket_with_name
sprocket = sprocket_with_name(name)
zorf_sprocket_internal(sprocket, compute_zorfulations(7) * 2)
现在你永远不能改变 任何 你的实现细节!更糟糕的是,sprocket_with_name 甚至不是你的代码;这是从库中得到的东西!但当有人在一个交互式外壳中执行 import sprocket_zorfer; sprocket_zorfer.<tab> 时,这些信息都没有传递过来。
下划线恐慌症
Python 中的约定是,我们使用 _ 来表示私有名称。所以当库作者注意到这个问题开始发生时,一个常见的反应是在 一切 之前开始放 _ —— 类名、函数名、模块名 —— 只显式导出那些应该是公共的,通过导入和在公共模块的 __all__ 中的条目“移动”它们。
然而,这有几个缺点
大多数代码检查工具和 IDE 都不会看到公共名称已经被“移动”,所以代码探索现在看起来就像 一切 都是实现细节,而不是看起来像没有任何东西。
你现在所有的 __repr__ 都包含难看且不准确的功能和类名,至少从用户的角度来看;他们该如何知道 zorf_sprocket_named 实际上定义在 zorf_sprocket._impl_details.funcs._zorf_sprocket_public 中呢?当他们看到古怪的内部名称时,他们该如何找到好的公共名称呢?
您需要不断记住,将所有代码放入这些以“_”开头的模块中,并教育新贡献者了解在您的包中创建未仔细隐藏的公共用户的新模块的风险。
更美好的世界
如果您能将所有代码都当作普通公共代码来编写,并且所有实现细节和导入都自动存储在下划线命名空间中,这样好奇的程序员就不会意外地找到您导入的每个模块以及您定义的每个临时辅助函数,并认为它们是您库的永久公共面孔的一部分怎么办?
这就是publication的作用。
publication使用现有的__all__约定和一些运行时黑客技巧来隐藏您未明确标记为公共的所有内容,如下所示
# sprocket_zorfer.py
from publication import publish
from sprocket import sprocket_with_name
from zorf import zorfable_thing
def zorf_sprocket_internal(sprocket, zorfulations):
...
def compute_zorfulations():
...
def zorf_sprocket_named(sprocket_name, how_much):
sprocket = sprocket_with_name(sprocket_name)
zorfulations = compute_zorfulations(how_much)
return zorf_sprocket_internal(sprocket, zorfulations)
__all__ = [
'zorf_sprocket_named'
]
publish()
就是这样!现在,from sprocket_zorfer import zorf_sprocket_named按照预期工作,但from sprocket_zorfer import compute_zorfulations会导致一个ImportError。
但关于……
我的包中的其他模块,例如测试模块,需要查看实现细节的问题?
不必担心,您的代码并没有消失。原始模块仍然可以通过一个特殊的伪模块
from sprocket_zorfer._private import compute_zorfulations
def test_compute_zorfulations():
assert compute_zorfulations(0) > 7
Mypy?
如果您期望用户需要了解它们,您的类型应该可能只是您发布的API的一部分。但是,如果有需要在您的库内部进行类型检查的情况,从Mypy的角度来看,所有您的私有类仍然存在。所以,在简单的情况下,您可以这样做
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from something import T
def returns_a() -> "T":
...
并且在可能非常罕见的需要同时访问不同模块私有细节的运行时和类型检查的情况下
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from something import T
else:
from something._private import T
def returns_a() -> A:
...
项目详情
publication-0.0.3.tar.gz的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4 |
|
MD5 | 046d10ae8616faefb949e2c9acded223 |
|
BLAKE2b-256 | 6b8e8c9fe7e32fdf9c386f83d59610cc819a25dadb874b5920f2d0ef7d35f46d |
publication-0.0.3-py2.py3-none-any.whl的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6 |
|
MD5 | 676c7a5bb6317f9699c4b224fcaf0006 |
|
BLAKE2b-256 | f8d36308debad7afcdb3ea5f50b4b3d852f41eb566a311fbcb4da23755a28155 |