I believe Python does private attribute mangling during compilation... in particular, it occurs at the stage where it has just parsed the source into an abstract syntax tree, and is rendering it to byte code. This is the only time during execution that the VM knows the name of the class within whose (lexical) scope the function is defined. It then mangles psuedo-private attributes and variables, and leaves everything else unchanged. This has a couple of implications...
String constants in particular are not mangled, which is why your setattr(self, "__X", x)
is being left alone.
Since mangling relies on the lexical scope of the function within the source, functions defined outside of the class and then "inserted" do not have any mangling done, since the information about the class they "belong to" was not known at compile-time.
As far as I know, there isn't an easy way to determine (at runtime) what class a function was defined in... At least not without a lot of inspect
calls that rely on source reflection to compare line numbers between the function and class sources. Even that approach isn't 100% reliable, there are border cases that can cause erroneous results.
The process is actually rather indelicate about the mangling - if you try to access the __X
attribute on an object that isn't an instance of the class the function is lexically defined within, it'll still mangle it for that class... letting you store private class attrs in instances of other objects! (I'd almost argue this last point is a feature, not a bug)
So the variable mangling is going to have to be done manually, so that you calculate what the mangled attr should be in order to call setattr
.
Regarding the mangling itself, it's done by the _Py_Mangle function, which uses the following logic:
__X
gets an underscore and the class name prepended. E.g. if it's Test
, the mangled attr is _Test__X
.
- The only exception is if the class name begins with any underscores, these are stripped off. E.g. if the class is
__Test
, the mangled attr is still _Test__X
.
- Trailing underscores in a class name are not stripped.
To wrap this all up in a function...
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)
I know this somewhat "hardcodes" the name mangling, but it is at least isolated to a single function. It can then be used to mangle strings for 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)
Alternately, the following mangle_attr
implementation uses an eval so that it always uses Python's current mangling logic (though I don't think the logic laid out above has ever changed)...
_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