首页 > 解决方案 > python3在元类中实现数据描述符

问题描述

在元类中实现数据描述符的正确方法是什么?在下面的(简单的)示例中,我希望始终在设置所需的值之前附加一个问号:

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 给我"RecursionError: maximum recursion depth exceeded while calling a Python object"

标签: python-3.xmetaclasspython-descriptors

解决方案


正如我在问题中评论的那样,这很棘手 - 因为 Python 代码无法__dict__直接更改类的属性 - 必须调用setattr并让 Python 设置类属性 - 并且,setattr将“看到”元类中的描述符, 并调用它__set__而不是修改类__dict__本身的值。所以,你得到一个无限递归循环。

因此,如果你真的要求descriptor所代理的属性在class'dict中同名“活”,你就不得不求助于:在设置值的时候,暂时从metaclass中移除descriptor,调用setattrset值,然后恢复它。

此外,如果您希望通过描述符处理类主体中设置的值,则必须在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, 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)

如果您不需要将值保存在类中' __dict__,我建议将其存储在其他地方-例如描述符实例中的字典,将类作为键就足够了-并且不会那么奇怪。


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

推荐阅读