被属性遮盖时修改类__dict__


问题内容

我试图__dict__直接使用来修改类中的值X.__dict__['x'] += 1。不可能像这样进行修改,因为类__dict__实际上是mappingproxy不允许直接修改值的对象。尝试直接修改或等效操作的原因是,我试图将class属性隐藏在具有相同名称的元类上定义的属性后面。这是一个例子:

class Meta(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        attrs['x'] = 0
        return super().__new__(cls, name, bases, attrs)
    @property
    def x(cls):
        return cls.__dict__['x']

class Class(metaclass=Meta):
    def __init__(self):
        self.id = __class__.x
        __class__.__dict__['x'] += 1

此示例显示了为的每个实例创建自动递增ID的方案Class。该行__class__.__dict__['x'] += 1不能被替换,setattr(__class__, 'x', __class__.x + 1)因为x是中property没有设置器的Meta。它将只是TypeError从amappingproxy变为AttributeErrorfrom
property

我曾尝试与混淆__prepare__,但这没有任何效果。中的实现type已返回dict名称空间的可变项。不可变mappingproxy似乎设置在type.__new__,我不知道如何避免。

我还尝试将整个__dict__引用重新绑定到可变版本,但这也失败了:https :
//ideone.com/w3HqNf,这暗示可能mappingproxy不是在中创建的type.__new__

dict即使被元类属性遮盖,我如何直接修改类值?尽管实际上 可能
是不可能的,但是setattr能够以某种方式做到这一点,所以我希望有一个解决方案。

我的主要要求是要具有一个类属性,该属性似乎是只读的,并且在任何地方都不使用其他名称。我不是绝对不赞成property使用带有同名类dict条目的元类的想法,但是通常这就是我在常规实例中隐藏只读值的方式。

编辑

我终于弄清楚了班级__dict__变得不可变的地方。在数据模型参考的“创建类对象”部分的最后一段中对此进行了描述:

当通过创建新类时type.__new__,作为名称空间参数提供的对象将被复制到新的有序映射中,而原始对象将被丢弃。新副本包装在只读代理中,该代理成为__dict__类对象的属性。


问题答案:

可能是最好的方法:选择另一个名称。调用属性x和dict键'_x',以便您可以通过常规方式访问它。

另一种方法:添加另一层间接:

class Meta(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        attrs['x'] = [0]
        return super().__new__(cls, name, bases, attrs)
    @property
    def x(cls):
        return cls.__dict__['x'][0]

class Class(metaclass=Meta):
    def __init__(self):
        self.id = __class__.x
        __class__.__dict__['x'][0] += 1

这样,您不必修改类dict中的实际条目。

可能会彻底隔离您的Python的超级骇客方式:通过gc模块访问基础dict 。

import gc

class Meta(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        attrs['x'] = 0
        return super().__new__(cls, name, bases, attrs)
    @property
    def x(cls):
        return cls.__dict__['x']

class Class(metaclass=Meta):
    def __init__(self):
        self.id = __class__.x
        gc.get_referents(__class__.__dict__)[0]['x'] += 1

这绕过type.__setattr__了维护内部不变性的关键工作,尤其是在诸如CPython的类型属性缓存之类的事情中。这是一个糟糕的主意,我只在提及它,以便在此处提出此警告,因为如果有人提出了此警告,那么他们可能不知道弄乱基础的命令是合法的危险。

以悬挂的引用完成此操作非常容易,而且我已经多次尝试使用Python进行段隔离。这是一个在Ideone崩溃的简单案例:

import gc

class Foo(object):
    x = []

Foo().x
gc.get_referents(Foo.__dict__)[0]['x'] = []

print(Foo().x)

输出:

*** Error in `python3': double free or corruption (fasttop): 0x000055d69f59b110 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x70bcb)[0x2b32d5977bcb]
/lib/x86_64-linux-gnu/libc.so.6(+0x76f96)[0x2b32d597df96]
/lib/x86_64-linux-gnu/libc.so.6(+0x7778e)[0x2b32d597e78e]
python3(+0x2011f5)[0x55d69f02d1f5]
python3(+0x6be7a)[0x55d69ee97e7a]
python3(PyCFunction_Call+0xd1)[0x55d69efec761]
python3(PyObject_Call+0x47)[0x55d69f035647]
... [it continues like that for a while]

这里的情况与错误的结果并没有嘈杂的错误消息,提醒您的是什么地方出了错误的事实:

import gc

class Foo(object):
    x = 'foo'

print(Foo().x)

gc.get_referents(Foo.__dict__)[0]['x'] = 'bar'

print(Foo().x)

输出:

foo
foo

我绝对不保证使用此方法的任何安全方法,即使在一个Python版本上发生了问题,它们也可能在将来的版本上不起作用。弄弄它可能很有趣,但是实际上并没有用。说真的,不要这样做。您是否
向老板解释您的网站已关闭,或者因为您接受并使用了这个错误的主意而需要撤消已发布的数据分析?