导语:本文章记录了本人在学习Python基础之元编程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。
本文重点:
1、了解描述符的定义,功能,协议和用法;
2、了解覆盖型描述符和非覆盖型描述符的概念和区别;
3、了解描述符的用法建议。
一、描述符
描述符:是实现了特定协议的类。
描述符功能:是对多个属性运用相同存取逻辑的一种方式。
描述符协议:包括__get__、__set__、和__delete__方法。通常只实现部分协议。大多数描述符只实现了__get__和__set__方法,但是property类实现了完整的描述符协议。
下面我们用描述符来实现Python中的动态属性和特性中提及的订单结算代码:
第四版:使用描述符实现订单结算功能
class Quantity:#描述符基于协议实现,无需创建子类。
def __init__(self,storage_name):
self.storage_name=storage_name#storage_name是托管实例中存储值的属性的名称。
def __set__(self, instance, value):#重要!instance是LineItem实例,self是描述符实例。
if value > 0:
instance.__dict__[self.storage_name]=value#此处必须直接存入__dict__,否则使用setattr函数会导致无限递归。
else:
raise ValueError('Value must be > 0')
class LineItem:
weight = Quantity('weight')#将描述符实例绑定到weight属性。
price = Quantity('price')#同上。
def __init__(self,description,weight,price):
self.description=description
self.weight=weight
self.price=price
def subtotal(self):
return self.weight*self.price
小结:描述符类的实例能用作托管类的属性
,这一点很重要!
不过在上文中的托管类定义体中,实例化描述符如果能按照weight = Quantity()这种格式声明就更好了。为此我们需要写一版自动获取存取属性名称的代码。
第五版:改进描述符类——自动获取存取属性名称
class Quantity:#改进版描述符类
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index) #每个描述符实例的属性名称都是独一无二的。
cls.__counter += 1
def __get__(self, instance, owner): #此处owner参数是托管类LineItem。
return getattr(instance, self.storage_name) #从instance中获取储存属性的值。
def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value) #使用setattr把值储存在instance中。
else:
raise ValueError('value must be > 0')
class LineItem:#托管类
weight = Quantity()
price = Quantity()
def __init__(self,description,weight,price):
self.description=description
self.weight=weight
self.price=price
def subtotal(self):
return self.weight*self.price
Tips:通常,我们不会在使用描述符的模块中定义描述符,而是在一个单独的实用工具模块中定义,以便在整个应用中使用。
二、特性工厂函数与描述符类比较
我们在Python中的动态属性和特性中提到过,抽象定义特性的方式有两种,一是使用特性工厂函数,二是使用描述符类。
现在来对两种方式的优点进行对比辨析:
- 特性工厂函数:模式简单。
描述符类:模式可拓展。可通过子类共享代码,构建具有部分相同功能的专用描述符,应用更广泛。
个人建议:当两种模式均能实现目标时,推荐使用描述类。
三、覆盖型描述符与非覆盖型描述符对比
覆盖型描述符:实现__set__方法的描述符属于覆盖型描述符。
特性是覆盖型描述符。
非覆盖型描述符:没有实现__set__方法的描述符属于非覆盖型描述符。
类中定义的方法是非覆盖型描述符。
小结:如果设置了同名实例属性,对于非覆盖型描述符而言会被覆盖;对于没有实现__get__方法的覆盖型描述符而言,在读操作时描述符对象也会被覆盖。
四、描述符用法建议
-
使用特性以保持简单
:创建只读属性最简单的方式是使用特性。 -
只读描述符必须有__set__方法
:只读描述符必须定义__get__和__set__两个方法,只读属性的__set__方法只需抛出AttributeError异常,并提供合适的错误信息。 -
用于验证的描述符可以只有__set__方法
:描述符用于验证属性时可以不实现__get__,这样从实例中读取同名属性的速度很快。 仅有__get__方法的描述符可以实现高效缓存。
非特殊的方法可以被实例属性覆盖。