Python描述符深入理解

Python的描述符乍眼看去简单,但是细节方面如果不注意容易掉坑,总结以下几个坑,以作备忘,先看代码:

class D:
	def __get__(self, inst, owner):
		if inst is None:
			return self
		else:
			print('I am in the D.__get__')
			return inst.__dict__['d']    # 返回实例的d属性
	def __set__(self, inst, value):
		if not isinstance(value, str):
			raise AttributeError('Value must be str')
		print('I am in the D.__set__')
		inst.__dict__['d'] = value # 设置实例自身d属性,只能使用__dict__形式,否则又会调用__get__,从而陷入无限循环

class C:
	d = D()
	def __init__(self, value):
		self.d = value # 在初始化的时候设置d属性,注意此时调用__get__函数,并不是设置实例本身的属性

>>> c = C('shy')
I am in the D.__set__
>>> c.__dict__
{'d': 'shy'}
>>> c.d
I am in the D.__get__
'shy'

总结:

  1. 描述符只能做类属性,不能作为实例属性,当一个属性是描述符时,实例查找这个属性会直接在类里面查找而忽略实例自身的空间,如上,实例自身有同名的d属性,但是当通过c.d调用的时候,调用的是描述符d。
  2. 在描述符里获取或者设置实例的同名属性时,需要用inst.__dict__形式访问,上面如果写成inst.d,则会陷入无限循环。

再来看一段代码:

class D:
	def __get__(self, inst, owner):
		if inst is None:
			return self
		else:
			print('I am in D.__get__')
			return self.value
	def __set__(self, inst, value):
		print('I am in D.__set__')
		self.value = value

class C:
      d = D()

>>> c1 = C()
>>> c2 = C()
>>> c1.d = 2
I am in D.__set__
>>> c2.d = 3
I am in D.__set__
>>> c1.d
I am in D.__get__
3
>>> c2.d
I am in D.__get__
3

class D:
	def __get__(self, inst, owner):
		if inst is None:
			return self
		else:
			print('I am in D.__get__')
			return inst.value
	def __set__(self, inst, value):
		print('I am in D.__set__')
		inst.value = value

class C:
	d = D()

>>> c1 = C()
>>> c2 = C()
>>> c1.d = 2
I am in D.__set__
>>> c2.d = 3
I am in D.__set__
>>> c1.d
I am in D.__get__
2
>>> c2.d
I am in D.__get__
3

总结:
属性可以保存在描述符内部,也可以保存在实例,但是如果保存为描述符内部,则为所有实例共享,所以一般把描述符的状态的信息保存在描述符内部,而把实例相关的信息保存在实例侧。

2018年12月27日
之前的一个错误,并非只要访问描述符就一定忽略其实例查找,如果只设置了__get__,优先实例查找,如果只设置了__set__函数,访问属性优先在实例字典里面查找,设置属性仍然优先类里面查找,代码如下:

# 只设置__get__,实例设置同名属性,未触发__get__
>>> class D:
	def __get__(self, inst, cls):
		print('I am in the D.__get__')
		return inst.name

>>> class C:
	name = D()
	def __init__(self, value):
		self.name = value

>>> c = C(2)
>>> c.name
2

#只设置__set__,实例设置同名属性,获取属性时优先实例查找,设置属性时优先类查找
>>> class D:
	def __set__(self, inst, value):
		print('I am in D __set__')
		inst.__dict__['name'] = value

		
>>> class C:
	name = D()

	
>>> c = C()
>>> c.name = 2
I am in D __set__
>>> c.__dict__
{'name': 2}
>>> c.name
2
>>> c.name = 3
I am in D __set__
posted @ 2018-12-24 15:31  中华坚果  阅读(511)  评论(0编辑  收藏  举报