不用描述符,不算懂 Python
在日常的编码中,我们应该使用 Python 的描述符,来使代码更具有单一职责原则,也就是 SRP(Single Responsibility Principle)原则,如果你还没有用过描述符,那快来看看怎么用吧,不然就不好意思说自己懂Python。
那么什么是描述符?
描述符是控制对象属性访问的一种方式。它的好处是,让我们把类中设置和检索属性的任务抽离出来,并将这一任务交给另一个只有一个目的的类,帮助我们遵循 SRP 原则,也让代码更 Pythonic。
让我给你举个例子。你们大多数人都会知道 @property 装饰器,它基本上与描述符的作用相同。现在,让我们编写一个验证汽车油箱容量的类:
在上面的代码示例中,定义了一个有油箱的汽车类,我们希望限制油箱的容量不能小于 0 或高于 60 升。
@fuel_amount.setter 对赋值做检查,如果小于 0 或大于 60,就会引发 ValueError。为了获取油箱的容量,我们使用 @property,从私有属性 _fuel_amount 返回。
这看起来简单,好像也没什么问题,不过你细品一下,就会发现当属性越来越多的时候,你会看到满天的 property 和一堆业务逻辑代码的 setter,最后就是一个非常臃肿的 Car 类,看到你想吐,这与 Python 的设计哲学简单优雅相去甚远。
幸运的是,Python 有描述符。
现在来看看下面使用了描述符的代码,是不是清爽了很多:
这里的类 SixtyLitresCapacity 就是一个描述符类,作用就是限制大小为 0 到 60,具体代码如下:
其实也很简单,一个类定义了一个 __get__ 和 __set__ 方法,就可以作为一个描述符类(还要注意参数列表)。
需要注意的是,只有为类属性设置描述符时才有效。如果将描述符用于实例属性,Python 会忽略它。
我们还可以做的更好,比如说将描述符类做得更加通用:
这里的描述符类 IsBetween 代码如下:
在 IsBetween 里,我们添加了一些新东西。
1、添加了__init__方法,以启用上下边界的初始化(min_value 和 max_value),这样,不仅可以有 60 升的油箱,还可以在油箱只剩下 5 升燃料时发出告警的功能。为了抛出不同的异常,将异常通过__init__方法的参数传进去。
2、添加了__set_name__(self, owner, name)方法。因为私有属性不一定是_fuel_amount,可以是你喜欢的任何属性。这种个方法打开了将属性名传递给给描述符类的大门。没有这个方法,描述符将无法从类中获得任何信息。
可以看到,IsBetween 这个描述符类更加通用,可以描述诸如电池电量、年龄属性、温度等属性。
你看,描述符类是不是非常有用?同时也帮助我们的代码遵循 SRP。
最后的话
本文分享了 Python 中描述符的使用,有没有学到新技能呢?