Python 和 PEP8 中的私有属性

Private attributes in Python and PEP8

我刚刚重新阅读 PEP8 并偶然发现了这一行:

We don't use the term "private" here, since no attribute is really private in Python (without a generally unnecessary amount of work).

我只是想知道如果我想让一个方法真正私有化,这种“不必要的工作量”会带来什么?

在纯 python 中,最接近“真正”封装的是通过闭包中的变量(因为一旦函数返回,您就不能直接 access/assign 它们)。这是一个小草图:

def OneTruePrivate():
    """hax which uses a closure to make _truly_ private variables"""
    x = 1

    class OneTruePrivate:
        @property
        def val(self):
            return x

        def increment(self):
            nonlocal x
            x += 1

    return OneTruePrivate()


var = OneTruePrivate()
print('initial value')
print(var.val)
var.increment()
print('after incremnet')
print(var.val)
print('look ma, no object state!')
import pprint; pprint.pprint(vars(var))

输出:

initial value
1
after incremnet
2
look ma, no object state!
{}

说的,即使有一些 hackery,你也可以改变底层的闭包值

cell = var.increment.__func__.__closure__[0]
import ctypes
ctypes.pythonapi.PyCell_Set.argtypes = (ctypes.py_object, ctypes.py_object)
ctypes.pythonapi.PyCell_Set(cell, 9001)
print(var.val)

输出:

9001

这有一个范围。更多的隐私需要更多的工作,但更难绕过。 “真正私密”没有明确的界限。

前导双下划线是最不隐私的选项。在属性上添加前导双下划线很容易,但也很容易通过手动名称修改绕过。

代码和绕过:

class Private1:
    def __init__(self, data):
        self.__data = data
    def get_data(self):
        return self.__data

x = Private1(1)

# Direct access fails...
x.__data = 2

# but you can do the name mangling manually.
x._Private1__data = 2

接下来是闭包变量或隐藏槽之类的东西。您无法通过名称修改来访问它们,但您仍然可以手动访问闭包单元或找出插槽 getter 的位置。

闭包变量示例,绕过:

class Private2:
    def __init__(self, data):
        def get_data():
            return data
        self.get_data = get_data

x = Private2(1)

# It's not immediately obvious how you'd even try to access the data variable directly,
# but you can:
x.get_data.__closure__[0].cell_contents = 2

隐藏插槽示例,绕过:

class Private3:
    __slots__ = ('data',)
    def __init__(self, data):
        _hidden_slot.__set__(self, data)
    def get_data(self):
        return _hidden_slot.__get__(self, type(self))
_hidden_slot = Private3.data
del Private3.data

x = Private3(1)

# Direct access fails...
x.data = 2

# but you can directly access the slot getter the same way the actual class did:
_hidden_slot.__set__(x, 2)

之后的下一步是 C 扩展。手动编写 C 扩展需要大量工作,足以让我不再费心于示例(但这里有一个 tutorial link, and Cython 使它变得容易得多),但在 C 中实现的类型不会公开其内部默认情况下 Python 级别的数据。如果该类型没有做出特定努力来提供访问权限,则访问数据的唯一方法是使用更多 C,或者使用 ctypesgc.get_referents 之类的东西(如果隐藏数据是 GC-exposed Python 参考。 (正确使用gc.get_referents也可以绕过上述所有其他保护。)

之后的下一步是将数据保存在您自己的私人服务器上,并且只允许客户端通过互联网访问它 API。这比任何 private 关键字都更加私密,绕过它需要诸如漏洞利用、传票或身体暴力之类的东西。