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

时间:2020-07-25 11:03:48

标签: python python-3.6 python-descriptors

在Raymond Hettingers 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 Liou在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

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

1 个答案:

答案 0 :(得分:1)

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

不确定第二作者为什么说您不能以这种方式使用get_graph。是的,getattr调用描述符协议,但是描述符已分配给getattr,但是您将type(obj).__dict__[name]设置为private_name,所以不会有无限递归...如果您在f'_{name}'中使用self.private_name = name而不是__set_name__,将会 ,但这不是前两个方法中的任何一个...

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

话虽这么说,第二个解决方案不是不正确

关于第三个解决方案,我想这是一个替代方案,它根本不会污染实例名称空间,而是保留一个单独的名称空间-self.private_name = f'_{name}'。这不是一个坏主意,但是与其他两个相比,它“没有更多的”正确性。请注意,它确实假定基于身份的类散列不是这种情况。您可以在类中实现WeakKeyDictionary以便基于其他内容进行哈希处理,如果您的类“在概念上”是不可变的,例如一些__hash__类,它不公开任何增变方法,并且基于Point(x, y)x的值进行散列。因此,这将使该方法更具限制性,但是,否则,这是一个不弄乱实例名称空间的聪明解决方案。

我认为第一个是Python语言中最惯用的。但同样,这三个都是有效的解决方案。