Python学习---描述符

描述符可能是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=name
	def __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=a
		self.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>
 






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值