我正在尝试使用__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
此示例显示了为Class
的每个实例创建自动递增ID的方案。行__class__.__dict__['x'] += 1
无法替换为setattr(__class__, 'x', __class__.x + 1)
,因为x
是property
,Meta
中没有设置者。它只会将TypeError
从mappingproxy
更改为AttributeError
中的property
。
我曾尝试弄乱__prepare__
,但这没有效果。 type
中的实现已经为命名空间返回了一个可变的dict
。不可变的mappingproxy
似乎设置在type.__new__
中,我不知道如何避免。
我还试图将整个__dict__
引用重新绑定到一个可变版本,但也失败了:https://ideone.com/w3HqNf,暗示mappingproxy
可能未在{{1}中创建}}
如何直接修改类type.__new__
值,即使被元类属性遮蔽了?虽然可能实际上是不可能的,dict
能够以某种方式做到这一点,所以我希望有一个解决方案。
我的主要要求是拥有一个看似只读的类属性,并且不会在任何地方使用其他名称。我并非完全不习惯使用元类setattr
和同名类property
条目,但这通常是我在常规实例中隐藏只读值的方式。
修改
我终于找到了类dict
变为不可变的位置。 "Creating the Class Object"引用的Data Model部分的最后一段中描述了它:
当
__dict__
创建新类时,作为命名空间参数提供的对象将复制到新的有序映射,并丢弃原始对象。新副本包装在只读代理中,该代理成为类对象的type.__new__
属性。
答案 0 :(得分:6)
可能是最好的方法:选择另一个名字。调用属性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中的实际条目。
超级hacky方式可能会彻底破坏你的Python:通过gc
模块访问底层字典。
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。这是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]
而且here's a case结果错误且没有嘈杂的错误消息提醒您事情出错:
import gc
class Foo(object):
x = 'foo'
print(Foo().x)
gc.get_referents(Foo.__dict__)[0]['x'] = 'bar'
print(Foo().x)
输出:
foo
foo
我绝对不保证使用它的任何安全方式,即使事情恰好在一个Python版本上运行,它们也可能无法在未来版本上运行。摆弄它可能很有趣,但它不是实际使用的东西。说真的,不要这样做。您是否希望向您的老板解释您的网站发生故障或者您需要撤回已发布的数据分析,因为您采用了这个糟糕的想法并使用它?
答案 1 :(得分:3)
这可能算作你不想要的“附加名称”,但是我使用了元类中的字典来实现它,其中键是类。元类的__next__
方法使类本身可迭代,这样您就可以next()
来获取下一个ID。 dunder方法还使方法无法通过实例使用。存储下一个id的字典的名称以双下划线开头,因此不容易从任何使用它的类中发现它。因此,递增ID功能完全包含在元类中。
我将id的赋值隐藏在基类的__new__
方法中,因此您不必在__init__
中担心它。这也允许你del Meta
所以所有的机器都有点难以到达。
class Meta(type):
__ids = {}
@property
def id(cls):
return __class__.__ids.setdefault(cls, 0)
def __next__(cls):
id = __class__.__ids.setdefault(cls, 0)
__class__.__ids[cls] += 1
return id
class Base(metaclass=Meta):
def __new__(cls, *args, **kwargs):
self = object.__new__(cls)
self.id = next(cls)
return self
del Meta
class Class(Base):
pass
class Brass(Base):
pass
c0 = Class()
c1 = Class()
b0 = Brass()
b1 = Brass()
assert (b0.id, b1.id, c0.id, c1.id) == (0, 1, 0, 1)
assert (Class.id, Brass.id) == (2, 2)
assert not hasattr(Class, "__ids")
assert not hasattr(Brass, "__ids")
请注意,我在类和对象上使用了相同的名称。这样Class.id
是您创建的实例数,而c1.id
是该特定实例的ID。
答案 2 :(得分:-1)
我的主要要求是拥有一个看似只读的类属性,并且不会在任何地方使用其他名称。我并非完全不习惯使用元类
property
和同名类dict
条目,但这通常是我在常规实例中隐藏只读值的方式。
你所要求的是一个矛盾:如果你的例子有效,那么__class__.__dict__['x']
将是一个额外的名字"对于属性。显然,我们需要更具体的定义"附加名称。"但是要想出这个定义,我们需要知道你想要完成什么(注意:以下目标不相互排斥,所以你可能想要做所有这些事情):
Class.__init__()
方法(以及任何子类的相同方法)之外,您希望使值完全不可触及:这是unPythonic并且非常不可能。如果__init__()
可以修改该值,那么其他人也可以。如果修改代码存在于Class.__new__()
中,可能能够完成类似的操作,元类在Meta.__new__()
中动态创建,但这非常难看和困难了解。property
。