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

cho*_*own 8 python attributes name-mangling private-members

我刚刚遇到 -private类成员名称在使用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}
Run Code Online (Sandbox Code Playgroud)

我也试过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}
Run Code Online (Sandbox Code Playgroud)

self.__dict__["_%s__%s" % (self.__class__.__name__, k)] = v会工作,但是__dict__只读属性.

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


一个更好的方式来表达我的问题:

当python遇到双下划线(self.__x)属性时,它会做什么"引擎盖" ?是否有用于修剪的魔法功能?

Eli*_*ins 6

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

  • 特别是字符串常量不会受到损坏,这就是为什么你setattr(self, "__X", x)会被孤立的原因.

  • 由于修改依赖于源中函数的词法范围,因此在类外部定义然后"插入"的函数不会进行任何修改,因为在编译时不知道它们"属于"类的信息.

  • 据我所知,没有一种简单的方法可以确定(在运行时)函数定义的类...至少没有很多inspect调用依赖源反射来比较函数和函数之间的行数.阶级来源.即使这种方法不是100%可靠,也存在可能导致错误结果的边界情况.

  • 该过程实际上是相当不雅的约是混淆-如果您尝试访问__X的对象上的属性是不是在函数中定义词法的类的实例,它仍然会裂伤它是类......让你在其他对象的实例中存储私有类attrs!(我几乎认为这最后一点是一个功能,而不是一个bug)

所以变量修改必须手动完成,这样你才能计算出受控制的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)
Run Code Online (Sandbox Code Playgroud)

我知道这有点"硬编码"这个名称,但它至少是孤立于一个函数.然后它可以用于修剪字符串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)
Run Code Online (Sandbox Code Playgroud)

或者,以下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
Run Code Online (Sandbox Code Playgroud)