问题描述
在元类内部实现数据描述符的正确方法是什么?在下面的示例中,我希望始终在设置所需值之前附加一个问号:
class AddQDesc:
def __init__ (self,name):
self.name = name
def __get__ (self,instance,owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self,value):
# What should go here ?
#setattr(instance,self.name,"{}?".format(value)) <- This gives me recursion error
#instance.__dict__[self.name] = "{}?".format(value) <- This gives me proxymapping error
pass
class Meta (type):
var = AddQDesc("var")
class C (Metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
首先,当我将var初始化为5时似乎没有使用描述符。我还能以某种方式在此处应用描述符协议吗? (设为“ 5?”) 其次,应如何在__set__方法中更新值?更新__dict__给我 “ TypeError:'mappingproxy'对象不支持项目分配” ,使用setattr给我 “ RecursionError:超过最大递归深度同时调用Python对象” 。
解决方法
正如我在问题中评论的那样,这很棘手-因为Python代码无法直接更改类的__dict__
属性-必须调用setattr
并让Python设置类属性-并且setattr
将“看到”元类中的描述符,并调用其__set__
而不是修改类__dict__
本身的值。因此,您将获得无限递归循环。
因此,如果您真的要求由描述符代理的属性将在类的“ dict”中以相同的名称“生效”,则您必须采取以下措施:设置值时,暂时删除来自元类的描述符,调用setattr
来设置值,然后将其还原。
此外,如果要处理在类主体中设置的值,
通过描述符,必须在{
类已创建-setattr
将不检查描述符
在构建初始类type.__new__
时。
__dict__
如果您不需要将值保留在类的from threading import Lock
class AddQDesc:
def __init__ (self,name):
self.name = name
self.lock = Lock()
def __get__ (self,instance,owner=None):
obj = instance if instance != None else owner
return obj.__dict__[self.name]
def __set__ (self,value):
owner_metaclass = type(instance)
with self.lock:
# Temporarily remove the descriptor to avoid recursion problems
try:
# Note that a metaclass-inheritance hierarchy,where
# the descriptor might be placed in a superclass
# of the instance's metaclass,is not handled here.
delattr(owner_metaclass,self.name)
setattr(instance,self.name,value + 1)
finally:
setattr(owner_metaclass,self)
class Meta (type):
def __new__(mcls,name,bases,namespace):
post_init = {}
for key,value in list(namespace.items()):
if isinstance(getattr(mcls,key,None),AddQDesc):
post_init[key] = value
del namespace[key]
cls = super().__new__(mcls,namespace)
for key,value in post_init.items():
setattr(cls,value)
return cls
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
中,建议将其存储在其他位置-例如,在描述符实例中以类为键的字典就足够了-而且会变得更不可思议。
__dict__