流畅的 Python 阅读记录九:成员属性加条件
本篇文章再谈 Python 中的约束属性, 之前在第七节提到过如何把属性设为只读, 那么如果属性有更多的要求呢, 比如要求为非负,如何处理?
当然可以通过 __setattr__
中进行约束, 使用 if 判断, 但是如果每个属性的要求很复杂怎么办, 以后修改起来也不方便.
本篇文章通过写一个程序来逐步讲解如何使得属性约束可以比较好管理, 程序是一个购物程序, 用户按照重量订购物品.
版本一
最原始的版本, 不考虑约束属性, 那么代码如下.
class LineItem:
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
"""
>>> raisins = LineItem('商品一', 10, 6.95)
>>> raisins.subtotal()
69.5
"""
版本二
现在需要让重量和价格都是非负数, 那么代码如下, 使用 @xx.setter
来完成.
class LineItem:
def __init__(self, description, weight, price):
self.description = description
# 执行这句的时候, 会去执行 weight.setter 那里, self.weight 它不是一个值, 可以理解为一个指引
# 真正的在 self.__weight 中(看 weight.setter 那里)
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# 用于读值
@property
def weight(self):
return self.__weight
@property
def price(self):
return self.__price
# 用于设值
@weight.setter
def weight(self, value):
if value > 0:
self.__weight = weight
else:
raise ValueError('value must be > 0')
@price.setter
def price(self, value):
if value > 0:
self.__price = price
else:
raise ValueError('value must be > 0')
版本三
显然, 放在一个类中太冗余了吧, 能否把这些约束抽取出来单独实现, 然后在类中直接引用呢, 可以.
下面的改进需要理解 @property
和 @xxx.setter
, 请查看菜鸟教程中关于 property
函数的介绍, 下面的代码看不懂时要记得看一下这个介绍。
def quantity(storage_name):
# 这里的 instance 是引用这个方法的实例, 在本例中即为 LineItem 的某个实例
def qty_getter(instance):
print(instance.__dict__)
return instance.__dict__[storage_name]
def qty_setter(instance, value):
if value > 0:
instance.__dict__[storage_name] = value
else:
raise ValueError('value must be > 0')
# 这里用了 property,是关键
return property(qty_getter, qty_setter)
class LineItem:
weight = quantity('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
>>> now = LineItem('test', 1, 2)
>>> now.weight
{'description': 'test', 'weight': 1, 'price': 2}
从上面执行结果可以看,执行 quantity
中的 instance
是 LineItem
的实例,这是 python 可视化网站上的结果:
其实没有太理解,但是我感觉这个可能就是 python 内部的实现方式,记住就好。
版本四
上个版本使用 property
约束了属性已经非常好了, 这个版本是声明其他的类, 从而完成这一功能. PS: 这种类叫托管类.
class Quantity:
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if value > 0:
self.value = value
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