深入理解Python描述符

最近在看《流畅的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_get
9
>>> obj.over_no_get = 7
-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)
>>> obj.over_no_get
9

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值