python как создать переменные частного класса с помощью setattr или exec?
Я только что столкнулся с ситуацией, когда псевдо - имена членов частного класса не искажаются при использовании setattr
или 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}
Я пробовал exec("self.__%s = %s" % (k, v))
а также с тем же результатом:
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}
делаешь self.__dict__["_%s__%s" % (self.__class__.__name__, k)] = v
будет работать, но __dict__
является атрибутом только для чтения.
есть ли другой способ, что я могу динамически создавать эти psuedo-частные члены класса (без жесткого кодирования имя калечить)?
лучший способ сформулировать мой вопрос:
что делает python "под капотом" , когда он сталкивается с двойным подчеркиванием (self.__x
атрибут) создается? Есть ли магическая функция, которая используется для калечения?
3 ответов
я считаю, что Python делает частные атрибуты во время компиляции... в частности, это происходит на этапе, когда он только что проанализировал источник в абстрактное синтаксическое дерево и отображает его в байтовый код. Это единственный раз во время выполнения, когда виртуальная машина знает имя класса, в пределах (лексической) области которого определена функция. Затем он искажает psuedo-частные атрибуты и переменные и оставляет все остальное неизменным. Это пару последствия...
строковые константы, в частности, не искажаются, поэтому ваш
setattr(self, "__X", x)
остается в покое.поскольку mangling полагается на лексическую область функции внутри источника, функции, определенные вне класса, а затем "вставленные", не имеют никакого искажения, поскольку информация о классе, к которому они "принадлежат", не была известна во время компиляции.
насколько я знаю, существует не простой способ определить (во время выполнения), в каком классе была определена функция... По крайней мере, не без много
inspect
вызовы, которые полагаются на отражение источника для сравнения номеров строк между функцией и источниками классов. Даже этот подход не является 100% надежным, есть пограничные случаи, которые могут вызвать ошибочные результаты.процесс на самом деле довольно неделикатен в отношении калечения - если вы попытаетесь получить доступ к
__X
атрибут объекта, который не экземпляр класса, в котором лексически определена функция, все равно будет искажать ее для этого класса... позволяя хранить частный класс attrs в экземплярах других объектов! (Я бы почти сказал, что этот последний пункт является функцией, а не ошибкой)
таким образом, переменное искажение должно быть сделано вручную, так что вы вычисляете, каким должен быть искореженный attr, чтобы вызвать setattr
.
по поводу коверкания сам по себе, это сделано _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)
я знаю, что это несколько "жестко кодирует" имя mangling, но оно, по крайней мере, изолировано от одной функции. Затем его можно использовать для искажения строк для 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
обращаясь к этому:
что делает python "под капотом" , когда он сталкивается с двойным подчеркивание (
self.__x
атрибут) создается? Есть волшебная функция что используется для калечить?
AFAIK, это в основном специальный корпус в компиляторе. Поэтому, как только он находится в байт-коде, имя уже искажено; интерпретатор никогда не видит незамутненного имени вообще и не имел представления о какой-либо специальной обработке. Вот почему ссылки через setattr
, exec
, или строку __dict__
не работал;компилятор видит все это как строки и не знает, что они имеют какое-либо отношение к доступу к атрибутам, поэтому он передает их без изменений. The переводчик ничего не знает о коверкая имя, поэтому он просто использует их напрямую.
раз, когда мне нужно было обойти это, я просто вручную сделал то же имя, что и mangling, как это. Я обнаружил, что с помощью этих "частные" имена, как правило, плохая идея, если это не тот случай, когда вы знаете, что они нужны для их предназначения: разрешить иерархии наследования классов использовать одно и то же имя атрибута, но иметь копию для каждого класса. Перечеркивание имен атрибутов с двойными подчеркиваниями только потому, что они должны быть частными деталями реализации, кажется, причиняет больше вреда, чем пользы; я просто использовал одно подчеркивание как намек на то, что внешний код не должен касаться его.
вот хак, который у меня есть до сих пор. Предложения по улучшению приветствуются.
class T(object):
def __init__(self, **kwds):
for k, v in kwds.items():
d = {}
cls_name = self.__class__.__name__
eval(compile(
'class dummy: pass\n'
'class {0}: __{1} = 0'.format(cls_name, k), '', 'exec'), d)
d1, d2 = d['dummy'].__dict__, d[cls_name].__dict__
k = next(k for k in d2 if k not in d1)
setattr(self, k, v)
>>> t = T(x=1, y=2, z=3)
>>> t._T__x, t._T__y, t._T__z
(1, 2, 3)