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

时间:2016-03-15 10:27:50

标签: 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

mylib/f.py

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

mylib/__init__.py

from mylib.c import C
from mylib.f import F

client/client.py

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

我想到了以下解决方案:

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

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

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

mylib/pc.py

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

    def public(self):
        self.__cc__.public()

但这看起来有点做作。

任何建议都表示赞赏: - )

更新

有人建议此问题与Does Python have “private” variables in classes?

重复

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

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

2 个答案:

答案 0 :(得分:2)

您无法真正隐藏对象的方法和属性。如果您想确保不暴露内部方法,可以选择包装:

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

    def public(self):
        self._c.public()

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

  

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

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

class PublicC(C):
    def internal(self):
        raise Exception('This is a private method')

如果你想使用一些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

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

...
whitelist = ['public']
def __getattribute__(self, name):
    if name not in PublicC.whitelist:
...

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

最终,这取决于你。正如你自己说的:这都是关于文档的。

另一句话:

也许你也想重新考虑你的班级结构。您已为F拥有工厂类C。让F拥有所有内部方法。

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

    def _internal(self, c):
        # work with c

在这种情况下,您不必包装或子类化任何东西。如果没有硬设计约束来实现这一点,我建议采用这种方法。

答案 1 :(得分:2)

  

我想到了以下解决方案:

     
      
  • 仅记录公共API,警告文档中的用户不要使用   私人图书馆api。希望客户只使用   公众api。如果下一个库版本中断客户端代码是   客户过错:)。

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

  •   
  • 使用对象组合来隐藏内部方法。比如说   库可以只返回客户端嵌入C的PC对象   对象

  •   

前两分你的表现非常正确。

Pythonic的方法是命名以单下划线“_”开头的内部方法,这样所有的Python开发人员都知道这个方法就在那里,但不建议使用它,也不会使用它。 (直到他们决定做一些猴子修补,但你不应该关心这种情况。)对于新手开发人员,你可能想明确提到不使用以下划线开头的方法。另外,不要为您的“私人”方法提供公共文档,仅将其用于内部参考。

您可能需要查看“name mangling”,但不太常见。

在Python中,通常不鼓励使用对象组合或类似__getattribute__等方法隐藏内部。

您可能需要查看一些常用库的源代码,以了解他们如何管理这些内容,例如: Django,Twisted等。