我相信Python在编译期间会进行私有属性修改……特别是,它发生在它刚刚将源代码解析为抽象语法树并将其呈现为字节代码的阶段。这是执行期间虚拟机唯一一次知道在其(词法)范围内定义函数的类的名称。然后,它会破坏伪私有属性和变量,并保持其他所有内容不变。这有几个含义......
特别是字符串常量不会被破坏,这就是为什么你的setattr(self, "__X", x)
被独自留下。
由于重整依赖于源中函数的词法范围,因此在类外部定义然后“插入”的函数不会进行任何重整,因为有关它们“所属”的类的信息在编译时未知。
据我所知,没有一种简单的方法可以确定(在运行时)函数是在哪个类中定义的......至少没有很多inspect
依赖源反射来比较函数源和类源之间的行号的调用。即使这种方法不是 100% 可靠,但有些边界情况可能会导致错误的结果。
这个过程实际上对于破坏来说相当不精致 - 如果你尝试访问__X
对象上的属性isn't该函数在词法上定义的类的实例,它仍然会对该类进行修改...让您将私有类属性存储在其他对象的实例中! (我几乎认为最后一点是一个功能,而不是一个错误)
因此,变量重整必须手动完成,以便您计算重整属性应该是什么,以便调用setattr
.
关于损坏本身,它是由_Py_Mangle https://hg.python.org/cpython/file/a10d37f04569/Python/compile.c#l211函数,它使用以下逻辑:
-
__X
获取一个下划线并在前面添加类名。例如。如果它是Test
,损坏的属性是_Test__X
.
- 唯一的例外是,如果类名以下划线开头,这些下划线将被删除。例如。如果班级是
__Test
,损坏的 attr 仍然是_Test__X
.
- 类名中的尾随下划线是not被剥夺了。
要将这一切包装在一个函数中......
def mangle_attr(source, attr):
# return public attrs unchanged
if not attr.startswith("__") or attr.endswith("__") or '.' in attr:
return attr
# if source is an object, get the class
if not hasattr(source, "__bases__"):
source = source.__class__
# mangle attr
return "_%s%s" % (source.__name__.lstrip("_"), attr)
我知道这在某种程度上“硬编码”了名称修改,但它至少被隔离到单个函数。然后它可以用来破坏字符串setattr
:
# you should then be able to use this w/in the code...
setattr(self, mangle_attr(self, "__X"), value)
# note that would set the private attr for type(self),
# if you wanted to set the private attr of a specific class,
# you'd have to choose it explicitly...
setattr(self, mangle_attr(somecls, "__X"), value)
或者,以下mangle_attr
实现使用 eval,以便它始终使用 Python 当前的修饰逻辑(尽管我认为上面列出的逻辑没有改变)...
_mangle_template = """
class {cls}:
@staticmethod
def mangle():
{attr} = 1
cls = {cls}
"""
def mangle_attr(source, attr):
# if source is an object, get the class
if not hasattr(source, "__bases__"):
source = source.__class__
# mangle attr
tmp = {}
code = _mangle_template.format(cls=source.__name__, attr=attr)
eval(compile(code, '', 'exec'), {}, tmp);
return tmp['cls'].mangle.__code__.co_varnames[0]
# NOTE: the '__code__' attr above needs to be 'func_code' for python 2.5 and older