I’ve just run into a situation where pseudo-private class member names aren’t getting mangled when using setattr or exec.
In [1]: class T:
...: def __init__(self, **kwargs):
...: self.__x = 1
...: for k, v in kwargs.items():
...: setattr(self, "__%s" % k, v)
...:
In [2]: T(y=2).__dict__
Out[2]: {'_T__x': 1, '__y': 2}
I’ve tried exec("self.__%s = %s" % (k, v)) as well with the same result:
In [1]: class T:
...: def __init__(self, **kwargs):
...: self.__x = 1
...: for k, v in kwargs.items():
...: exec("self.__%s = %s" % (k, v))
...:
In [2]: T(z=3).__dict__
Out[2]: {'_T__x': 1, '__z': 3}
Doing self.__dict__["_%s__%s" % (self.__class__.__name__, k)] = v would work, but __dict__ is a readonly attribute.
Is there another way that I can dynamically create these psuedo-private class members (without hard-coding in the name mangling)?
A better way to phrase my question:
What does python do “under the hood” when it encounters a double underscore (self.__x) attribute being set? Is there a magic function that is used to do the mangling?
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
inspectcalls 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
__Xattribute 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:
__Xgets an underscore and the class name prepended. E.g. if it’sTest, the mangled attr is_Test__X.__Test, the mangled attr is still_Test__X.To wrap this all up in a function…
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:Alternately, the following
mangle_attrimplementation 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)…