描述符的介绍
只要类中包含了__get__
, __set__
, __delete__
中的任意一个,那么这个类就是描述类
也就是对这个实例的取、设置和删除都会执行上面对应的方法。
必须作为类变量进行初始化。
同时包含__get__
和__set__
的被称为数据描述符,而只有__get__
被称为非数据描述符
对于一个类中的变量调用顺序参考下图:
从上图可以看到调用顺序为:__getattribute__
> 数据描述符__get__
> 非数据描述符__get__
> 兜底的__getattr__
Example:
class Price:
def __init__(self):
self._price = 0
def __get__(self, obj, klass):
print('call __get__ method, get: ', self._price)
return self._price
def __set__(self, obj, value):
print('call __set__ method, set: ', value)
if value < 0:
raise ValueError('price should not be negative')
self._price = value
def __delete__(self, obj):
print('call __delete__ method , price to 0')
self._price = 0
class Apple:
price = Price()
def __init__(self, price):
self.price = price
def __getattribute__(self, __name: str):
print("call __getattribute__ method, get: ", __name)
return super().__getattribute__(__name)
def __getattr__(self, __name: str):
print("call __getattr__ method, get: ", __name)
a1 = Apple(10)
a1.no_exist_attr
print(a1.price)
a1.price = 15
print(a1.price)
del a1.price
print(a1.price)
# output:
#call __set__ method, set: 10
#call __getattribute__ method, get: no_exist_attr
#call __getattr__ method, get: no_exist_attr
#call __getattribute__ method, get: price
#call __get__ method, get: 10
#10
#call __set__ method, set: 15
#call __getattribute__ method, get: price
#call __get__ method, get: 15
#15
#call __delete__ method , price to 0
#call __getattribute__ method, get: price
#call __get__ method, get: 0
#0
结合装饰器
我们以@property
的python简单实现为例。
class mproperty(object):
def __init__(self, fget, fset=None, fdel=None):
print("call mproperty.__init__ method, set getter func")
self._fget = fget
self._fset = fset
self._fdel = fdel
def __get__(self, obj, klass):
print('call __get__ method')
return self._fget(obj)
def __set__(self, obj, val):
if not hasattr(self._fset, '__call__'):
raise AttributeError("Readonly attribute!")
print('call __set__ method, set: ', val)
self._fset(obj, val)
def __delete__(self, obj):
if not hasattr(self._fdel, '__call__'):
raise AttributeError("Can't delete the attribute!")
print('call __delete__ method, price to 0')
self._fdel(obj)
def setter(self, fset):
print("call mproperty.setter method, set setter func")
self._fset = fset
return self
def deleter(self, fdel):
print("call mproperty.delete method, set delete func")
self._fdel = fdel
return self
class Apple(object):
def __init__(self, price=0):
self._price = price
@mproperty
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price must be greater than 0")
self._price = value
@price.deleter
def price(self):
print("delete price")
print("=== start process ===")
a1 = Apple(10)
a1.price
a1.price = 15
a1.price
del a1.price
# output:
#call mproperty.__init__ method, set getter func
#call mproperty.setter method, set setter func
#call mproperty.delete method, set delete func
#=== start process ===
#call __get__ method
#call __set__ method, set: 15
#call __get__ method
#call __delete__ method, price to 0
#delete price
如果还不清楚装饰器的原理,可以看我写的这篇
我们来一步一步分析:
在程序初始化阶段,将执行
@mproperty
,此时装饰的是price
,等价于mpropertry(price)
, 此时price
函数实际上就是mproperty
实例了@price.setter
装饰price
, 里面的@price
现在就是mproperty
实例,所以有setter
方法。在setter
中设置了self._fset
后返回self
,这样price
同样指向的是mproperty
实例。@price.delete
装饰price
, 同理price
指向了同一个mproperty
实例,保证了这几个方法都其实是个实例。既然都是一个实例,并且
mproperty
含有__get__
__set__
__delete
,是描述器,所以在调用price
的时候,就会去调用对应的方法。通过
self._get(obj)
这样的形式再回过来调用Apple.price
中的方法
其实这里面的关键点就是setter
和delete
中的return self
到底返回的是什么,成为了什么形式被调用。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 365433079@qq.com