在 Python 3.6+ 中实现描述符的正确方法是什么?

What is the right way to implement descriptors in Python 3.6+?

在雷蒙德·赫廷格斯 Mental Game on YouTube:

class Validator:

    def __set_name__(self, owner, name):
        self.private_name = f'_{name}'    

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.private_name)

    def __set__(self, obj, value):
        self.validate(value)
        setattr(obj, self.private_name, value)

Daw-运行 Liou 在 Writing descriptors in Python 3.6+ 中指出:

[...] instead of using builtin function getattr and setattr, we need to reach into the dict object directly, because the builtins would be intercepted by the descriptor protocols too and cause the RecursionError.

class Validator:

    def __set_name__(self, owner, name):
        self.name= name
    
    def __get__(self, obj, objtype=None):
        return obj.__dict__[self.name]

    def __set__(self, obj, value):
        self.validate(value)
        obj.__dict__[self.name] = value

但是 Matthew Egans Describing Descriptors on YouTube 说:

from weakref import WeakKeyDictionary

class Validator:
    def __init__(self):
        self.data = WeakKeyDictionary()

    def __get__(self, obj, owner):
        return self.data[obj]

    def __set__(self, obj, value):
        self.validate(value)
        self.data[obj] = value

实现描述符的正确方法是什么?

第一个例子很好。这三个都是有效的实现。

不知道为什么第二位作者说你不能那样使用 getattr。是的,getattr 调用了描述符协议,但是描述符被分配给了 type(obj).__dict__[name] 但你将 private_name 设置为 f'_{name}' 所以不会有任何无限递归......如果你在 __set_name__ 中使用 self.private_name = name 而不是 self.private_name = f'_{name}',它 ,但这不是前两个正在做的事情......

编辑:阅读link,这就是作者正在做的...

也就是说,第二个解决方案 不正确

至于第三种解决方案,我推测这是一个替代方案,它根本不会污染实例名称空间,保留一个单独的名称空间 -- WeakKeyDictionary。不错的主意,但它并不比其他两个更“正确”。请注意,它 确实 假设 class 基于身份的哈希值,但事实并非如此。您可以在 class 中实现 __hash__ 以基于其他内容进行散列,如果您的 class “在概念上”是不可变的,例如一些 Point(x, y) class 不公开任何增变器方法,并且基于 xy 的值进行散列。因此,这会使这种方法更具限制性,但除此之外,这是一个不混淆实例命名空间的聪明解决方案。

我认为第一个是最符合 Python 风格的,因为它是惯用的。但同样,这三个都是有效的解决方案。