Python descriptor

从attribute到property, 现在又出现了descriptor. 刚读到Fluent Python里的这一章(Chapter 20)时, 内心阴影面积着实很大, 要理清它们的关系确实不那么容易.


什么是descriptor?

一种协议, 协议方法有仨: __get__, __set__, __delete__. 如果一个class实现了其中一个或多个, 那这个class就是一个descriptor. 大部分情况下只需要关注前两个方法.

有什么用?

用作class attribute时可控制instance attribute的读写操作, 可用于控制读写, 属性值合法性验证.

与property是什么关系?

它的作用明显与property重合. 其实, 它们就是一家人: property实现了descriptor的所有方法. 说白了, property是为便于使用而实现buit-in descriptor.

在实例属性解析顺序中处于什么地位?

首位. 之前在Python对象的属性访问过程一文中描述过没有property/descriptor时的解析顺序. 如果将descriptor也考虑进去, descriptor 的优先级永远最高. (有点无语了, 这么个大Boss, 居然放到倒数第二章才讲)

overiding, non-overriding descriptor

实现__set__的descriptor

仍然使用从attribute到property的场景:

class Quantity:
    def __init__(self, storage_name):
        self.storage_name = storage_name
    def __get__(self, instance, cls):
        print('\t{}.__get__ is called'.format(self.__class__.__name__))
        return instance.__dict__[self.storage_name]
    def __set__(self, instance, value):
        print('\t{}.__set__ is called'.format(self.__class__.__name__))
        if value > 0:
            instance.__dict__[self.storage_name] = value
        else:
            raise ValueError('value must be > 0')

class Item:
    count = Quantity('count')
    price = Quantity('price')
    def __init__(self, category, count, price):
        self.category = category
        print('Setting attributes...')
        self.count = count
        self.price = price
        print('Setting attributes OK.')
    def subtotal(self):
        return self.count * self.price

item = Item('Book', 10, 20)
print('\nGetting attributes.')
print(item.count, item.price)
print('Getting attributes OK.')

try:
    item = Item('Book', -10, 20)
    item = Item('Book', 10, -20)
except ValueError:
    print('Invalid items')

输出:

Setting attributes...
    Quantity.__set__ is called
    Quantity.__set__ is called
Setting attributes OK.

Getting attributes.
    Quantity.__get__ is called
    Quantity.__get__ is called
10 20
Getting attributes OK.
Setting attributes...
    Quantity.__set__ is called
Invalid items

可以看到, 因为countprice被设置为Quantity, 它的读写操作都被Quantity__get__, __set__代理了.

当上面的storage_name与属性名相同时, 只实现__get__实现与否都可正常获取属性值.

class D:
    def __init__(self, storage_name):
        self.storage_name = storage_name
    def __set__(self, instance, value):
        instance.__dict__[self.storage_name] = value

class Foo:
    name = D('name')
    def __init__(self, name):
        self.name = name
foo = Foo('foo')
print(foo.name)

输出:

foo

没实现__set__的descriptor

上两种情况里, __set__方法都被实现了, 这时, 对应的class attribute是无法通过instance attribute覆盖的, 因为对应属性的所有赋值操作都会被这个__set__拦截下来. 但如果没有__set__, 那么可以将instance的这个descriptor attribute 覆盖掉, 当然, class的对应属性仍然为descriptor:

class D:
    def __get__(self, instance, cls):
        if instance is not None:
            return 'name from descriptor'
        else: 
            return self
class Foo:
    name = D()
foo = Foo()
print('descriptor works by now:',foo.name)
foo.name = 3
print('no descriptor in the instance:', foo.name)
print('descriptor in the class:', Foo.name)

输出:

descriptor works by now: name from descriptor
no descriptor in the instance: 3
descriptor in the class: <__main__.D object at 0x7f4b14e76400>

所以, 实现了__set__的descriptor也称为overriding descriptor, 反之则称为non-overriding descriptor.

类的方法也是descriptor

了解到这的时候着实感叹了一把, 连类的method也实现了descriptor协议, 可见在python底层中, descriptor的用途是极为广泛的. 不过呢, 对框架和库的纯使用者而言, 不知道它的存在也不会影响使用Python.

回到正题, method也是descriptor:

class Foo:
    def method(self):
        pass
print(hasattr(Foo.method, '__get__'))
print(hasattr(Foo.method, '__set__'))

输出:

True
False

method有__get____set__, 也就是说, 它是non-overriding descriptor, 是可以被覆盖的:

class Foo:
    def method(self):
        pass
foo = Foo()
print(foo.method)
foo.method = 'overrided'
print(foo.method)

输出:

<bound method Foo.method of <__main__.Foo object at 0x7f4b14601320>>
overrided

总结

  1. descriptor用途很广
  2. descriptor在attribute 解析过程中优先级最高.
  3. 实现了__set__方法的为overriding descriptor, 不会被instance attribute覆盖. 反之则为non-overriding descriptor, 可覆盖.
  4. 类的方法是non-overriding descriptor.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值