如何设计一个库公共api避免暴露内部?

Gio*_*nni 6 python

我正在学习python.我试图了解如何设计一个公开api的库.我希望避免暴露将来可能发生变化的内部方法.我正在寻找一种简单而pythonic的方式来做到这一点.我有一个包含一堆类的库.这些类的一些方法在类内部使用.我不想将这些方法暴露给客户端代码.

假设我的库(fe mylib)包含一个C带有两个方法的类,一个C.public()方法被认为是从客户端代码和C.internal()方法中使用的方法,用于对库代码进行一些工作.我想将自己提交给公共api(C.public()),但我希望C.internal()将来可以更改方法,例如添加或删除参数.

以下代码说明了我的问题:

mylib/c.py:

class C:
    def public(self):
        pass

    def internal(self):
        pass
Run Code Online (Sandbox Code Playgroud)

mylib/f.py:

class F:
    def build():
        c = C()
        c.internal()
        return c
Run Code Online (Sandbox Code Playgroud)

mylib/__init__.py:

from mylib.c import C
from mylib.f import F
Run Code Online (Sandbox Code Playgroud)

client/client.py:

import mylib
f = mylib.F()
c = f.build()
c.public()
c.internal()  # I wish to hide this from client code
Run Code Online (Sandbox Code Playgroud)

我想到了以下解决方案:

  • 仅记录公共API,警告文档中的用户不要使用私有库api.和平相处,希望客户只使用公共API.如果下一个库版本中断客户端代码是客户端错误:).

  • 使用某种形式的命名约定,fe为每个方法添加前缀"_",(它保留用于受保护的方法并向ide引发警告),也许我可以使用其他前缀.

  • 使用对象组合来隐藏内部方法.例如,库可以返回仅PC嵌入C对象的客户端对象.

mylib/pc.py:

class PC:
    def __init__(self, c):
        self.__c__

    def public(self):
        self.__cc__.public()
Run Code Online (Sandbox Code Playgroud)

但这看起来有点做作.

任何建议表示赞赏:-)

更新

有人提出这个问题是重复的,Python在类中有"私有"变量吗?

这是类似的问题,但我对范围有点不同.我的范围是一个库而不是一个类.我想知道是否有一些关于标记(或强制)的约定,它们是库的公共方法/类/函数.例如,我使用__init__.py导出公共类或函数.我想知道是否有一些关于导出类方法的约定,或者我是否只能依赖文档.我知道我可以使用"_"前缀来标记受保护的方法.据我所知,protected方法是可以在类层次结构中使用的方法.

我发现了一个关于使用装饰器@api Sphinx Public API文档标记公共方法的问题,但它大约在3年前.有一个普遍接受的解决方案,所以如果有人正在阅读我的代码,了解什么是库公共API的方法,以及打算在库内部使用的方法?希望我澄清了我的问题.谢谢大家!

ale*_*eum 2

您无法真正隐藏对象的方法和属性。如果你想确保你的内部方法不被暴露,包装是最好的方法:

class PublicC:
    def __init__(self):
        self._c = C()

    def public(self):
        self._c.public()
Run Code Online (Sandbox Code Playgroud)

据我所知,通常不鼓励使用双下划线作为前缀,以防止与 python 内部发生冲突。

不鼓励的是__myvar__带有双下划线前缀+后缀的名称...这种命名风格被许多 python 内部使用,应该避免 - Anentropic

如果您更喜欢子类化,则可以覆盖内部方法并引发错误:

class PublicC(C):
    def internal(self):
        raise Exception('This is a private method')
Run Code Online (Sandbox Code Playgroud)

如果你想使用一些Python魔法,你可以看看__getattribute__。在这里,您可以检查用户尝试检索的内容(函数或属性),并AttributeError在客户端想要使用内部/黑名单方法时提出。

class C(object):
    def public(self):
        print "i am a public method"

    def internal(self):
        print "i should not be exposed"

class PublicC(C):
    blacklist = ['internal']

    def __getattribute__(self, name):
        if name in PublicC.blacklist:
            raise AttributeError("{} is internal".format(name))
        else: 
            return super(C, self).__getattribute__(name) 

c = PublicC()
c.public()
c.internal()

# --- output ---

i am a public method
Traceback (most recent call last):
  File "covering.py", line 19, in <module>
    c.internal()
  File "covering.py", line 13, in __getattribute__
    raise AttributeError("{} is internal".format(name))
AttributeError: internal is internal
Run Code Online (Sandbox Code Playgroud)

我认为这会导致最少的代码开销,但也需要一些维护。您还可以反转检查和白名单方法。

...
whitelist = ['public']
def __getattribute__(self, name):
    if name not in PublicC.whitelist:
...
Run Code Online (Sandbox Code Playgroud)

这可能更适合您的情况,因为白名单可能不会像黑名单那样频繁更改。

最终,这取决于你。正如您自己所说:一切都与文档有关。

另一条评论:

也许您还想重新考虑您的班级结构。您已经有一个工厂类FC。让我们F拥有所有的内部方法。

class F:
    def build(self):
        c = C()
        self._internal(c)
        return c

    def _internal(self, c):
        # work with c
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您不必包装或子类化任何内容。如果没有硬性设计限制使这成为不可能,我会推荐这种方法。

  • “据我所知,通常不鼓励使用双下划线作为前缀,以防止与 python 内部发生冲突。” 这不是真的...双下划线前缀_is_是Python处理私有成员的方式...在你的类之外,名称被混淆并且难以访问。不鼓励的是带有双下划线 _prefix+suffix_ 的 __myvar__` 名称...这种命名风格被许多 python 内部使用,应该避免 (2认同)