【问题标题】:python3 implementing a data descriptor in metaclasspython3在元类中实现数据描述符
【发布时间】:2020-12-03 05:04:09
【问题描述】:

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

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' 对象不支持项目分配" 并且使用 setattr 给我 "RecursionError: 超出最大递归深度同时调用 Python 对象”

【问题讨论】:

  • 我无法正确回答您的问题,但您可以查看 Python 文档中的 property 类或 Descriptor howto guide。不过我可以说的是,在python class C (metaclass=Meta): var = 5 中,您将元类中的属性“var”替换为完全不同的属性,这是正常的:继承是关于覆盖父类的行为。
  • 是的 - 这很棘手 - 我在轨道上,但现在没有时间。如果您不需要将属性实际存储在具有相同名称的 class.__dict__ 中,这将更容易 - 这对于普通对象来说很容易。但是,如果名称可以是一个错位的名称(如“_var”),或者如果数据存储可以在描述符实例中,而不是在类中,那就很简单了。
  • 然而,能够在类主体中指定一个初始值可能是一个有趣的问题。我不知道 itf type.__new__,它将设置这些初始属性,将使用描述符协议 - 稍后会检查。
  • (事实证明,type.__new__ 忽略元类中的描述符以分配初始类属性 - 我的答案包括解决方法)

标签: python-3.x metaclass python-descriptors


【解决方案1】:

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

因此,如果你真的要求descriptor所代理的属性会在class'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, 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

【讨论】:

    猜你喜欢
    • 2021-08-06
    • 1970-01-01
    • 2014-10-11
    • 2013-03-15
    • 2012-04-29
    • 2016-02-26
    • 2018-09-17
    • 2015-05-11
    • 1970-01-01
    相关资源
    最近更新 更多