我相信 Python 在编译期间会进行私有属性修改......特别是,它发生在它刚刚将源解析为抽象语法树并将其呈现为字节码的阶段。这是在执行过程中,VM 唯一知道函数在其(词法)范围内定义的类的名称。然后它会破坏伪私有属性和变量,并保持其他所有内容不变。这有几个含义......
特别是字符串常量不会被破坏,这就是为什么你setattr(self, "__X", x)
被单独留下的原因。
由于修改依赖于源中函数的词法范围,因此在类外部定义然后“插入”的函数不会进行任何修改,因为关于它们“所属”类的信息在编译时是未知的.
据我所知,没有一种简单的方法可以确定(在运行时)函数在哪个类中定义......至少在没有大量inspect
调用依赖源反射来比较函数之间的行号和类来源。即使这种方法也不是 100% 可靠,也有可能导致错误结果的边界情况。
这个过程实际上是相当不雅的修饰 - 如果您尝试访问不是__X
该函数在其中定义函数的类的实例的对象上的属性,它仍然会为该类修饰它......让你将私有类属性存储在其他对象的实例中!(我几乎认为最后一点是一个特性,而不是一个错误)
因此,变量重整必须手动完成,以便您计算重整的 attr 应该是什么才能调用setattr
.
关于 mangling 本身,它是由_Py_Mangle函数完成的,它使用以下逻辑:
__X
得到一个下划线和前置的类名。例如,如果它是Test
,则损坏的 attr 是_Test__X
。
- 唯一的例外是如果类名以任何下划线开头,则这些下划线将被删除。例如,如果类是
__Test
,则损坏的 attr 仍然是_Test__X
。
- 类名中的尾随下划线不会被剥离。
把这一切都包装在一个函数中......
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