Python学习---描述符

来源:互联网 发布:人工智能 电影免费 编辑:程序博客网 时间:2024/05/16 19:38

描述符可能是Python最少用的技巧,很多文章介绍了特别多,反而看的云里雾里,我觉得描述符(descriptor)概念本身是非常简单的,但确实是比较难应用,特别是初学者。但是网上也没有特别好的应用例子,虽然我们常用的函数(方法)里都隐含了这一概念。

这两天看了些书和参考博客,觉得了解描述符对初学者加深对Python面向过程的实现是很有好处的。


我们知道,实现了__iter__,__next__的类可称为迭代器,而描述符也有自己的协议,即实现了以下三种:

__get__(self, instance, class) --> return value__set__(self, instance, value)__delete__(self, instance)
其中,只实现了__get__的类称为nondata-descriptor,类似只读,而 实现了__get__和__set__的称为data-descriptor,__del__可选

这里的instance和class是针对拥有者的,因为描述符是寄生在其他类的,这个类也就是拥有者(owner)

注意一点,__get__对cls和instance访问有效,而__set__仅对instance,也就是拥有者的实例,在下面的例子里有说明

class Descriptor(object):def __get__(self,instance,owner):print "__get__"def __set__(self,instance,owner):print "__set__"class A(object):x=Descriptor() # x字段是一个描述符对象def __init__(self,x):self.x=x # 对x进行赋值操作

>>> a=A(88)__set__        #初始化的时候,self.x在对象__dict__没找到x,但在__class__.__dict__里找到x是一个描述符对象,而且是data-descriptor,则调用__set__方法>>> a.x__get__>>> a.__dict__      # 可以看到,这时候88并没有成功赋值,因为调用了__set__方法,这里我们的实现只是一个简单的print,并没有赋值操作   {}>>> A.__dict__['x']<__main__.Descriptor object at 0x7fb2d142eb90> # x,描述符对象>>> a.x=100  #再次赋值,一样,调用__set____set__>>> A.x=100 #前面提到了,__set__对owner class访问无效,这里赋值成功,在A__dict__里创建字段'x':100,>>> a.x    #由于A.__dict__里x描述符被新创建的x:100覆盖了,a.x正常找到A.__dict__里的x字段100>>> a.__dict__  #这里,如果a.x=500,再次赋值,会在a.__dict__创建'x':500字段,这个x字段与类的x无关{}>>> A.__dict__['x']100


还需要注意的两点是,

1. 描述符只能在类定义里,如果放到实例方法里,会被忽略。

2. non-descriptor 没有__set__方法,所以在赋值操作时,其实是在对象__dict__里创建同名新字段,下次get的时候就会被覆盖。


通常来讲,我们对一个类操作,离不开这几种操作(set,get,del),如果我们对某个类的某种操作限定,只需要定义一个描述符,将逻辑写在对应的方法里即可。


class Charlength(object):def __init__(self,name):self.name=namedef __set__(self,instance,value): #只需要对set方法做限制,只允许长度小于10的字符串,所以这里只需要实现__set__if not isinstance(value,str):raise TypeError("Expected an string")elif len(value)>=10:raise TypeError("Expected no more than ten characters")instance.__dict__[self.name]=value #name是a,b,定义的account实例的字段,在这里赋值class Account(object):a = Charlength('a')b = Charlength('b')def __init__(self,a,b):self.a=aself.b=b

>>> myaccount=Account("tmac","kobe") # 初始化时就调用了__set__,将a,b字段赋值>>> >>> myaccount.a 'tmac'>>> myaccount.b'kobe'>>> >>> myaccount.b=88 #重新赋值,调用__set__,抛出异常Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 6, in __set__TypeError: Expected an string>>> >>> >>> myaccount.b="abcdefgthyrujk"  # 重新赋值,调用__set__,抛出异常Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 8, in __set__TypeError: Expected no more than ten characters>>> 

我们常见的property,staticmethod以及classmethod也是基于类似的描述符实现。


最后,说到__get__,其实函数(方法)中一直有__get__

class A(object):def test(self):print "test"
a.test相当于A.test.__get__(a,A)
A.test相当于A.test.__get__(None,A) 

>>> a=A()>>> A.test.__get__(a,A)<bound method A.test of <__main__.A object at 0x7fb2cb32a050>>>>> A.test.__get__(None,A)<unbound method A.test>






0 0