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

2024-04-15

我刚刚遇到了一种情况pseudo- 使用时私有类成员名称不会被破坏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}

我试过了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}

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

有没有另一种方法可以动态创建这些psuedo-私有类成员(没有在名称修改中进行硬编码)?


更好地表达我的问题:

当 python 遇到双下划线时,它会在“幕后”做什么(self.__x) 属性被设置?是否有一个神奇的函数可以用来进行重整?


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

  • 特别是字符串常量不会被破坏,这就是为什么你的setattr(self, "__X", x)被独自留下。

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

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

  • 这个过程实际上对于破坏来说相当不精致 - 如果你尝试访问__X对象上的属性isn't该函数在词法上定义的类的实例,它仍然会对该类进行修改...让您将私有类属性存储在其他对象的实例中! (我几乎认为最后一点是一个功能,而不是一个错误)

因此,变量重整必须手动完成,以便您计算重整属性应该是什么,以便调用setattr.


关于损坏本身,它是由_Py_Mangle https://hg.python.org/cpython/file/a10d37f04569/Python/compile.c#l211函数,它使用以下逻辑:

  • __X获取一个下划线并在前面添加类名。例如。如果它是Test,损坏的属性是_Test__X.
  • 唯一的例外是,如果类名以下划线开头,这些下划线将被删除。例如。如果班级是__Test,损坏的 attr 仍然是_Test__X.
  • 类名中的尾随下划线是not被剥夺了。

要将这一切包装在一个函数中......

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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python 如何使用 setattr 或 exec 创建私有类变量? 的相关文章