python3 在元类中实现数据描述符
python3 implementing a data descriptor in metaclass
在元类中实现数据描述符的正确方法是什么?在以下(简单的)示例中,我希望在设置之前始终在所需值后面附加一个问号:
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, instance, 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' object does not support item assignment" 并使用 setattr 给我 “递归错误:调用 Python 对象时超出了最大递归深度”.
正如我在问题中评论的那样,这很棘手 - 因为 Python 代码无法直接更改 class' __dict__
属性 - 必须调用 setattr
并让 Python 设置一个 class 属性 - setattr
将“看到”metaclass 中的描述符,并调用它的 __set__
而不是修改 class __dict__
本身的值。所以,你得到一个无限递归循环。
因此,如果您真的要求由描述符代理的属性将在class'dict 中以相同的名称“存在”,您必须求助于to: 设置值时,暂时从metaclass中移除描述符,调用setattr
设置值,然后恢复回来
此外,如果您希望处理 class 正文中设置的值
通过描述符,它们必须在 setattr
之后设置
class 已创建 - type.__new__
不会检查描述符
因为它构建了初始 class __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, instance, 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.name, 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, name, bases, namespace)
for key, value in post_init.items():
setattr(cls, key, value)
return cls
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
如果您不需要该值存在于 class' __dict__
中,我建议将其存储在别处 - 例如描述符实例中的字典,具有 classes作为键,就足够了——而且不会那么奇怪。
class AddQDesc:
def __init__ (self, name):
self.name = name
self.storage = {}
def __get__ (self, instance, owner):
if not instance: return self
return self.storage[instance]
def __set__ (self, instance, value):
self.storage[instance] = value + 1
在元类中实现数据描述符的正确方法是什么?在以下(简单的)示例中,我希望在设置之前始终在所需值后面附加一个问号:
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, instance, 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' object does not support item assignment" 并使用 setattr 给我 “递归错误:调用 Python 对象时超出了最大递归深度”.
正如我在问题中评论的那样,这很棘手 - 因为 Python 代码无法直接更改 class' __dict__
属性 - 必须调用 setattr
并让 Python 设置一个 class 属性 - setattr
将“看到”metaclass 中的描述符,并调用它的 __set__
而不是修改 class __dict__
本身的值。所以,你得到一个无限递归循环。
因此,如果您真的要求由描述符代理的属性将在class'dict 中以相同的名称“存在”,您必须求助于to: 设置值时,暂时从metaclass中移除描述符,调用setattr
设置值,然后恢复回来
此外,如果您希望处理 class 正文中设置的值
通过描述符,它们必须在 setattr
之后设置
class 已创建 - type.__new__
不会检查描述符
因为它构建了初始 class __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, instance, 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.name, 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, name, bases, namespace)
for key, value in post_init.items():
setattr(cls, key, value)
return cls
var = AddQDesc("var")
class C (metaclass=Meta):
var = 5
print(C.var)
C.var = 1
print(C.var)
如果您不需要该值存在于 class' __dict__
中,我建议将其存储在别处 - 例如描述符实例中的字典,具有 classes作为键,就足够了——而且不会那么奇怪。
class AddQDesc:
def __init__ (self, name):
self.name = name
self.storage = {}
def __get__ (self, instance, owner):
if not instance: return self
return self.storage[instance]
def __set__ (self, instance, value):
self.storage[instance] = value + 1