Skip to content

流畅的 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 中的 instanceLineItem 的实例,这是 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

Comments