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,或者使用 ctypes
或 gc.get_referents
之类的东西(如果隐藏数据是 GC-exposed Python 参考。 (正确使用gc.get_referents
也可以绕过上述所有其他保护。)
在 之后的下一步是将数据保存在您自己的私人服务器上,并且只允许客户端通过互联网访问它 API。这比任何 private
关键字都更加私密,绕过它需要诸如漏洞利用、传票或身体暴力之类的东西。
我刚刚重新阅读 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,或者使用 ctypes
或 gc.get_referents
之类的东西(如果隐藏数据是 GC-exposed Python 参考。 (正确使用gc.get_referents
也可以绕过上述所有其他保护。)
在 之后的下一步是将数据保存在您自己的私人服务器上,并且只允许客户端通过互联网访问它 API。这比任何 private
关键字都更加私密,绕过它需要诸如漏洞利用、传票或身体暴力之类的东西。