被属性遮盖时修改类__dict__ [英] Modifying class __dict__ when shadowed by a property

查看:57
本文介绍了被属性遮盖时修改类__dict__的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am attempting to modify a value in a class __dict__ directly using something like X.__dict__['x'] += 1. It is impossible to do the modification like that because a class __dict__ is actually a mappingproxy object that does not allow direct modification of values. The reason for attempting direct modification or equivalent is that I am trying to hide the class attribute behind a property defined on the metaclass with the same name. Here is an example:

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

此示例显示为每个 Class 实例创建一个自动递增ID的方案。 __ class __.__ dict __ ['x'] + = 1 行不能替换为 setattr(__ class__,'x',__class __。x + 1 ),因为 x 属性,在 Meta中没有二传手。它将只是将 TypeError mappingproxy 更改为 AttributeError 来自属性

This is example shows a scheme for creating an auto-incremented ID for each instance of Class. The line __class__.__dict__['x'] += 1 can not be replaced by setattr(__class__, 'x', __class__.x + 1) because x is a property with no setter in Meta. It would just change a TypeError from mappingproxy into an AttributeError from property.

我尝试弄乱 __ prepare __ ,但没有效果。 类型的实现已经为命名空间返回了一个可变的 dict 。不变的 mappingproxy 似乎设置为 type .__ new __ ,我不知道该如何避免。

I have tried messing with __prepare__, but that has no effect. The implementation in type already returns a mutable dict for the namespace. The immutable mappingproxy seems to get set in type.__new__, which I don't know how to avoid.

我还试图将整个 __ dict __ 引用重新绑定到可变版本,但这也失败了: https://ideone.com/w3HqNf ,这可能意味着 mappingproxy 不是以类型创建的。__new __

I have also attempted to rebind the entire __dict__ reference to a mutable version, but that failed as well: https://ideone.com/w3HqNf, implying that perhaps the mappingproxy is not created in type.__new__.

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

How can I modify a class dict value directly, even when shadowed by a metaclass property? While it may be effectively impossible, setattr is able to do it somehow, so I would expect that there is a solution.

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

My main requirement is to have a class attribute that appears to be read only and does not use additional names anywhere. I am not absolutely hung up on the idea of using a metaclass property with an eponymous class dict entry, but that is usually how I hide read only values in regular instances.

EDIT

I最终弄清楚了 __ dict __ 类在哪里变得不可变。在创建数据模型参考的类对象 部分:

I finally figured out where the class __dict__ becomes immutable. It is described in the last paragraph of the "Creating the Class Object" section of the Data Model reference:


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

When a new class is created by type.__new__, the object provided as the namespace parameter is copied to a new ordered mapping and the original object is discarded. The new copy is wrapped in a read-only proxy, which becomes the __dict__ attribute of the class object.


推荐答案

可能是最好的方法:只需选择另一个名称即可。调用属性 x 和字典键'_ x',这样您就可以正常使用它了。

Probably the best way: just pick another name. Call the property x and the dict key '_x', so you can access it the normal way.

另一种方式:添加另一层间接寻址:

Alternative way: add another layer of indirection:

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中的实际条目。

That way you don't have to modify the actual entry in the class dict.

超级骇客的方式可能会彻底破坏您的Python:通过 gc 模块访问底层字典。

Super-hacky way that might outright segfault your Python: access the underlying dict through the gc module.

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的type属性缓存之类的东西中。这是一个糟糕的主意,我只在提及它,以便在此处提出此警告,因为如果有人提出了此警告,他们可能不知道弄乱基础dict是合法的危险。

This bypasses critical work type.__setattr__ does to maintain internal invariants, particularly in things like CPython's type attribute cache. It is a terrible idea, and I'm only mentioning it so I can put this warning here, because if someone else comes up with it, they might not know that messing with the underlying dict is legitimately dangerous.

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

It is very easy to end up with dangling references doing this, and I have segfaulted Python quite a few times experimenting with this. Here's one simple case that crashed on 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]

这是一个错误结果的案例,并且没有嘈杂的错误消息来提醒您事实出了问题:

And here's a case with wrong results and no noisy error message to alert you to the fact that something has gone wrong:

import gc

class Foo(object):
    x = 'foo'

print(Foo().x)

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

print(Foo().x)

输出:

foo
foo

我绝对不保证任何安全的使用方式,甚至如果事情碰巧在一个Python版本上可以解决,那么它们可能在将来的版本上无法解决。玩弄可能很有趣,但是实际上并没有用。认真地,不要这样做。您是不是要向老板解释您的网站已关闭,或者因为您接受并使用了这个坏主意而需要撤回已发布的数据分析?

I make absolutely no guarantees as to any safe way to use this, and even if things happen to work out on one Python version, they may not work on future versions. It can be fun to fiddle with, but it's not something to actually use. Seriously, don't do it. Do you want to explain to your boss that your website went down or your published data analysis will need to be retracted because you took this bad idea and used it?

这篇关于被属性遮盖时修改类__dict__的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆