【问题标题】:What is the right way to implement descriptors in Python 3.6+?在 Python 3.6+ 中实现描述符的正确方法是什么?
【发布时间】:2020-07-25 11:03:48
【问题描述】:

在雷蒙德·赫廷斯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-Ran Liu 在Writing descriptors in Python 3.6+ 中表示:

[...] 而不是使用内置函数 getattr 和 setattr,我们需要直接访问 dict 对象,因为内置函数也会被描述符协议拦截并导致 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

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

【问题讨论】:

    标签: python python-3.6 python-descriptors


    【解决方案1】:

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

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

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

    话虽如此,第二种解决方案并非不正确

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

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

    【讨论】:

    • 我试图将所有作者的概念应用于同一个示例(来自 Raymond Hettinger)并写了self.private_name = f'_{name}' 而不是self.name = name。我会改的。
    • 在我看来,这并没有污染实例的命名空间,它属于实例。是否有任何路径可以通过点分符号从实例中手动查找存储在 WeakKeyDictionary() 中的数据?谢谢。
    • @Agent49 嗯?我说重点是避免污染实例命名空间,就像_some_attribute 的正常方式一样。请注意,它不属于实例,描述符属于类。无论如何,要获得描述符,您需要类似 type(instance).__dict__['Validator'].data
    • 我知道。正如您所指出的,(1) 没有 WeakKeyDictionary 属性的存储在实例的命名空间中,并可通过obj_instance.__dict__.[_my_attrib] 访问。 (2) 对于 WeakKeyDictionary,它们不是。但是,由于 my_attrib 属于该实例,我还没有看到为什么在此处使用 WeakKeyDictionary 的任何原因。我知道描述符属于类而不是实例。虚线查找顺序(也在实例上)表示查找 type(instance).__dict__['my_attrib'] 如果它是数据描述符。
    猜你喜欢
    • 1970-01-01
    • 2017-04-17
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 2011-11-06
    • 2018-08-12
    • 1970-01-01
    • 2017-01-16
    相关资源
    最近更新 更多