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