深入理解Python描述符

来源:互联网 发布:北宋 纪录片 知乎 编辑:程序博客网 时间:2024/05/22 06:59

最近在看《流畅的Python》关于描述符的章节,平时也不经意间会接触到cached_property、sqlalchemy的Column、甚至内置的property都是描述符。在网上也看到过关于描述符的讲解,但是并没有区分覆盖型描述符和非覆盖型描述符,因为这两者获取属性的优先级链不一样,下面来具体说说。

为什么要区分覆盖型和非覆盖型?

《流畅的Python》中写到,“Python存取属性的方式特别不对等,通过实例读取属性时,通常返回的是实例中定义的属性;但是,如果实例中没有指定的属性,那么会获取类属性。而为实例中的属性赋值时,通常会在实例中创建属性,根本不影响类。这种 不对等的处理方式对描述符也有影响。”

根据描述符中是否定义了__set__方法可以把描述符分为覆盖型和非覆盖型,有__set__方法就是覆盖型描述符,覆盖型描述符还可以分为两类,有__get__方法的覆盖型和没有__get__方法的覆盖型。

1. 非覆盖型的获取属性的优先级链是,__getattribute__->找__dict__->找描述符,这条链的规则给了"缓存属性"理论支持,如下

class cached_property(object):    def __init__(self, func):        self.__doc__ = getattr(func, '__doc__')        self.func = func    def __get__(self, obj, cls):        if obj is None:            return self        value = obj.__dict__[self.func.__name__] = self.func(obj)        return value
当pbj.__dict__里有了self.func.__name__时,下次就不会再调用这个描述符了

2. 有__get__方法的覆盖型的获取属性的优先级链是,__getattribute__->找描述符,同时__set__方法也会覆盖对实例属性的赋值操作,就是说任何外部对属性的赋值都将被__set__捕获,同时获取属性也是通过__get__方法获取,__dict__不再起直接作用。

3. 没有__get__方法的覆盖型的获取属性优先级链是

在没有对属性赋值时是__getattribute__->找描述符(返回描述符对象本身),对属性赋值了之后是__getattribute__->找__dict__->找描述符,举个例子

class OverridingNoGet:      """an overriding descriptor without ``__get__``"""    def __set__(self, instance, value):        print_args('set', self, instance, value)class Managed:      over_no_get = OverridingNoGet()    def spam(self):         print('-> Managed.spam({})'.format(display(self)))>>> obj.over_no_get  # doctest: +ELLIPSIS<descriptorkinds.OverridingNoGet object at 0x...>>>> Managed.over_no_get  # doctest: +ELLIPSIS<descriptorkinds.OverridingNoGet object at 0x...>>>> obj.over_no_get = 7-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)>>> obj.over_no_get  # doctest: +ELLIPSIS<descriptorkinds.OverridingNoGet object at 0x...>>>> obj.__dict__['over_no_get'] = 9>>> obj.over_no_get9>>> obj.over_no_get = 7-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)>>> obj.over_no_get9

再来谈谈描述符的线程安全问题

多个线程,每个线程都会进入一次描述符,所以控制线程安全还得根据描述符__get__方法内是否存在临界区,但是__set__方法一般都要锁来保证线程安全了

原创粉丝点击