python描述符 descriptor

本文深入探讨Python中的描述符(descriptor),包括其基本概念、工作原理及应用场景。通过具体示例介绍了如何利用描述符来控制类属性的读取、设置与删除,并对比了描述符与property的不同之处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor。descriptor通常用来改变默认的属性访问(attribute lookup), 前提是descriptor的实例是类的属性(class attribute)。下面的代码展示了简单的用法
 
复制代码
 1 # -*- coding: utf-8 -*-
 2 class Des(object):
 3     def __init__(self, init_value):
 4         self.value = init_value
 5  
 6     def __get__(self, instance, typ):
 7         print('call __get__', instance, typ)
 8         return self.value
 9  
10     def __set__(self, instance, value):
11         print ('call __set__', instance, value)
12         self.value = value
13  
14     def __delete__(self, instance):
15         print ('call __delete__', instance)
16  
17 class Widget(object):
18     t = Des(1)
19  
20 def main():
21     w = Widget()
22     print type(w.t)
23     w.t = 1
24     print w.t, Widget.t
25     del w.t
26     print w.t
27  
28  
29 if __name__=='__main__':
30     main()
复制代码

 

这三个特殊的函数签名是这样的:
object.__get__(selfinstanceowner):return value
object.__set__(selfinstancevalue):return None
object.__delete__(selfinstance): return None
 
形参中,instance是类的实例(w), owner是类(widget)
w.t 等价于 Pro.__get__(t, w, Widget).而Widget.t 等价于 Pro.__get__(t, None, Widget)
 
descriptor主要用于控制属性的访问(读、写、删除)。python doc里面有写到,property()就是一个data descriptor实现(可参见这个文档)。 python2.2中,大量新式类的实现都基于descriptor  
They are the mechanism behind properties, methods, static methods, class methods, and super(). They are used throughout Python itself to implement the new style classes introduced in version 2.2.
 
在实践中,我们有可能需要监控或者限制对属性的访问。比如,对象的一个属性被“莫名其妙”地修改了,但搜索所有文件有找不到可以的地方,那么我们可以通过__setattr__(self, k, v)方法,对于我们关心的 k 打印出调用栈。另外,也可以用property,示例代码如下:
复制代码
 1 class TestProperty(object):
 2     def __init__(self):
 3         self.__a = 1
 4  
 5     @property
 6     def a(self):
 7         return self.__a
 8  
 9     @a.setter
10     def a(self, v):
11         print('output call stack here')
12         self.__a = v
13  
14 if __name__=='__main__':
15     t = TestProperty()
16     print t.a
17     t.a = 2
18     print t.a
复制代码

 

如果需要禁止对属性赋值,或者对新的值做检查,也很容易修改上面的代码实现
 
既然有了property,那什么时候还需要descriptor呢?property最大的问题在于不能重复使用,即对每个属性都需要property装饰,代码重复冗余。而使用descriptor,把相同的逻辑封装到一个单独的类,使用起来方便多了。详细的示例可以参见这篇文章。
 
笔者之前看bottle.py源码的时候,看到这么一个descriptor使用,部分源代码和测试代码如下:
 
复制代码
 1 import functools, time
 2 class cached_property(object):
 3     """ A property that is only computed once per instance and then replaces
 4         itself with an ordinary attribute. Deleting the attribute resets the
 5         property. """
 6 
 7     def __init__(self, func):
 8         functools.update_wrapper(self, func)
 9         self.func = func
10 
11     def __get__(self, obj, cls):
12         if obj is None: return self
13         value = obj.__dict__[self.func.__name__] = self.func(obj)
14         return value
15 
16 class TestClz(object):
17     @cached_property
18     def complex_calc(self):
19         print 'very complex_calc'
20         return sum(range(100))
21 
22 if __name__=='__main__':
23     t = TestClz()
24     print '>>> first call'
25     print t.complex_calc
26     print '>>> second call'
27     print t.complex_calc
复制代码

 

运行结果如下:
     >>> first call
    very complex_calc
    4950
    >>> second call
    4950
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值