python如何使用setattr或exec创建私有类变量?

python如何使用setattr或exec创建私有类变量?,python,attributes,private-members,name-mangling,Python,Attributes,Private Members,Name Mangling,我刚刚遇到了这样一种情况:在使用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]:

我刚刚遇到了这样一种情况:在使用
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.\uu%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.\uuuuu dict.\uu[“\ uu%s\uuuu%s”%(self.\uuuuuu class\uuuuuuuuuuu name\uuuuuuuuu,k)]=v
将起作用,但
\uuuuu dict\uuuuuu
是一个只读属性

有没有其他方法可以动态创建这些psuedo私有类成员(无需名称mangling中的硬编码)


更好地表达我的问题:


当python遇到正在设置的双下划线(
self.\ux
)属性时,它在“引擎盖下”做什么?有没有一个神奇的功能可以用来进行破坏呢?

这是我到目前为止的一个技巧。欢迎提出改进建议

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)

我相信Python在编译过程中会破坏私有属性。。。特别是,它发生在将源代码解析为抽象语法树并将其呈现为字节码的阶段。这是执行期间VM唯一一次知道在其(词法)范围内定义函数的类的名称。然后,它会破坏psuedo私有属性和变量,并保持其他所有内容不变。这有几个含义

  • 特别是字符串常量没有被损坏,这就是为什么您的
    setattr(self,“\uux”,X)
    被单独保留的原因

  • 由于损坏依赖于源代码中函数的词法范围,因此在类外定义的函数,然后“插入”的函数不会进行任何损坏,因为它们“所属”的类的信息在编译时是未知的

  • 据我所知,没有一种简单的方法可以(在运行时)确定函数在哪个类中定义。。。至少在没有大量的
    inspect
    调用的情况下是这样,这些调用依赖于源代码反射来比较函数和类源代码之间的行号。即使这种方法不是100%可靠,也有可能导致错误结果的边界案例

  • 这个过程实际上是相当不雅的,如果你试图访问一个对象上的
    \uuux
    属性,而这个对象不是该函数在其中定义的类的实例,它仍然会为该类破坏它。。。允许您在其他对象的实例中存储私有类属性!(我几乎认为最后一点是一个特性,而不是一个bug)

因此,必须手动执行变量mangling,以便计算被损坏的属性应该是什么,以便调用
setattr


关于损坏本身,它是由函数完成的,该函数使用以下逻辑:

  • \uuux
    获取下划线和前置的类名。例如,如果是
    测试
    ,则损坏的属性是
    \u测试\u X
  • 唯一的例外是,如果类名以任何下划线开头,这些下划线都会被去掉。例如,如果类是
    \uuuu Test
    ,则损坏的属性仍然是
    \uuux
  • 类名中的尾随下划线不会被去除
要将这一切都封装在一个函数中

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逻辑(尽管我认为上面列出的逻辑从未改变过)

解决这一问题:

当python遇到双重攻击时,它在“引擎盖下”做什么 正在设置下划线(
self.\ux
)属性?有神奇的功能吗 那是用来弄脏的吗

好吧,这基本上是编译器中的特殊情况。因此,一旦进入字节码,名称就已经被破坏;解释器根本看不到未混合的名称,也不知道需要任何特殊处理。这就是为什么通过
setattr
exec
,或通过在
\uuuu dict\uuuu
中查找字符串进行引用不起作用;编译器将所有这些都视为字符串,并且不知道它们与属性访问有任何关系,因此它将它们原封不动地传递。解释器对名称mangling一无所知,所以它只是直接使用它们


在我需要解决这个问题的时候,我只是手工做了相同的名字弄乱,就像那样。我发现使用这些“私有”名称通常是一个坏主意,除非您知道需要它们来达到预期目的:允许类的继承层次结构使用相同的属性名称,但每个类都有一个副本。仅仅因为属性名应该是私有的实现细节,就在属性名中加上双下划线似乎弊大于利;我已经习惯使用一个下划线作为提示,提示外部代码不应该触及它。

这是一种不寻常的情况,因为您允许构造函数分配任意私有变量。如果构造函数可以给这些变量任何值,为什么还要使它们私有?你可以使用带有默认值的命名关键字参数来分配这些参数吗?@MichaelAaronSafyan我最初就是这么做的,但后来我想扩展T以获取任何kwarg,并考虑了一个子类(称为S),它在初始化期间将**kwargs传递给它的super,但不允许S访问任何这些成员(因为S在调用T之前可以看到kwargs.。uuu init_uuuuuuu(self,**kwargs)S可能会破坏东西)。在这一点上,我仍在编写代码,所以我想看看这是否可行,如果不可行,我可能会继续使用类似于
def_uuinit_uuuuuuself(x=1,y=2):
,从文档中可以看出:“不可行
_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