Я полагаю, что Python выполняет искажение закрытых атрибутов во время компиляции ... в частности, это происходит на этапе, когда он только что проанализировал источник в абстрактном синтаксическом дереве и отображает его в виде байтового кода.Это единственный раз, когда виртуальная машина знает имя класса, в пределах (лексической) области которого определена функция.Затем он искажает псевдо-приватные атрибуты и переменные и оставляет все остальное без изменений.Это имеет несколько последствий ...
Строковые константы, в частности, не искажены, поэтому ваш setattr(self, "__X", x)
остается один.
Поскольку искажение основывается на лексической области действия функции в источнике, функции, определенные вне класса и затем «вставленные», не выполняют никакого искажения, поскольку информация о классеони «принадлежат» не было известно во время компиляции.
Насколько я знаю, не существует простого способа определить (во время выполнения), какой класс был определен функциейin ... По крайней мере, без большого количества вызовов inspect
, которые полагаются на отражение источника для сравнения номеров строк между источниками функции и класса.Даже если этот подход не является надежным на 100%, существуют пограничные случаи, которые могут привести к ошибочным результатам.
Процесс на самом деле довольно деликатен в отношении искажения - если вы попытаетесь получить доступ к __X
атрибут для объекта, который не является экземпляром класса, в котором лексически определена функция, он все равно будет искажать его для этого класса ... позволяя вам хранить атрибуты частного класса в экземплярах других объектов!(Я бы почти утверждал, что этот последний пункт - это особенность, а не ошибка)
Таким образом, искажение переменной должно быть выполнено вручную, так что вы вычисляете, что искаженный атрибутдолжно быть для того, чтобы позвонить setattr
.
Что касается самого искажения, оно выполняется функцией _Py_Mangle , которая использует следующую логику:
__X
получает подчеркивание иимя класса добавлено.Например, если это Test
, искаженный атрибут равен _Test__X
. - Единственное исключение - если имя класса начинается с подчеркивания, оно удаляется.Например, если класс
__Test
, искаженный атрибут все еще _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