Python学习笔记37:属性描述符

概览

Python学习笔记36:动态属性和特性中我们介绍了如何使用特性来“代理”对实例属性的访问,事实上特性是一种特殊的属性描述符

所谓的属性描述符,是一种实现了描述符协议的特殊类,这个关于属性访问的协议包括__set__\__get__\delete

下面我们看下如何实现属性描述符。

实现

我们假设有这么一个订单类:

class Order:
    def __init__(self, quantity, price) -> None:
        self.quantity = quantity
        self.price = price

    def total(self):
        return self.quantity*self.price

order = Order(1.5, 5)
print(order.total())
# 7.5

显然,订单中的数量(quantity)和单价(price)必须是大于零的数,当然我们可以像Python学习笔记36:动态属性和特性中那样使用特性,但这里我们使用标准的属性描述符。

其实现也很简单,只要实现前面提到的描述符协议,或者部分协议即可。

class PositiveNumber:
    def __init__(self, name) -> None:
        self.name = name

    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if value > 0:
            instance.__dict__[self.name] = value
        else:
            raise ValueError('vlaue mast > 0')


class Order:
    quantity = PositiveNumber('quantity')
    price = PositiveNumber('price')

    def __init__(self, quantity, price) -> None:
        self.quantity = quantity
        self.price = price

    def total(self):
        return self.quantity*self.price


order = Order(1.5, 5)
print(order.total())
order2 = Order(0, 3)
print(order2.total())
# 7.5
# Traceback (most recent call last):
#   File "D:\workspace\python\python-learning-notes\note37\test.py", line 29, in <module>
#     order2 = Order(0, 3)
#   File "D:\workspace\python\python-learning-notes\note37\test.py", line 20, in __init__
#     self.quantity = quantity
#   File "D:\workspace\python\python-learning-notes\note37\test.py", line 12, in __set__
#     raise ValueError('vlaue mast > 0')
# ValueError: vlaue mast > 0

具体的描述符协议包括下面三个协议方法:

  • __get__(self, instatnce, owner)
  • __set__(self, instance, value)
  • __delete__(self, instance)

这里仅实现了__get____set__,属性描述符通常会实现这两个方法,或者其中之一,__delete__并不常用。

这里是实现协议,并非是基类的抽象方法,所以不需要继承,更不需要强制实现所有协议方法。有关协议的灵活性我们在Python学习笔记28:从协议到抽象基类中有过讨论。

协议方法中的self和普通的类没有区别,就是指属性描述符实例本身。instance为属性描述符绑定的目标类的实例,owner为属性描述符绑定的类的引用。

这里或许会觉得__get__中会持有一个绑定类引用owner很突兀,而且好像没什么用,但后面会说明其用途。

这里我们创建了一个属性描述符PositiveNumber,其用途和我们在Python学习笔记36:动态属性和特性中创建的特性工厂函数极为相似,后者是创建一个只能设置为正数的特性,这里是创建一个只能设置为正数的属性描述符实例。

属性描述符的内部定义很简单,这里不过多赘述。

  • 需要注意的是,在读和写的时候,我们都是将真实的数据存储在instance.__dict__中,而非是属性描述符实例self.__dict__中,这是因为虽然在这个具体示例中我们的属性描述符PositiveNumber的两个实例PositiveNumber('quantity')PositiveNumber('price')仅仅绑定到了Order类,但PositiveNumber这个属性描述符创建后其实是可以绑定到任意需要使用类似的访问控制的类中的,所以从这点来说,属性描述符只是用于对绑定到的具体类实例的具体属性的访问控制,当然具体读写的属性存储在绑定的目标实例中,而非属性描述符自己的实例。
  • 这里使用instance.__dict__而非getattr(instance,name)是因为后者会再次触发属性描述符,陷入无限递归。

将属性描述符绑定到目标类和在类中用特性工厂函数创建特性也没有区别,都是在类定义中给相应的类属性进行赋值。

绑定好属性描述符以后,所有对Order类实例的quantityprice属性的读写操作都将由绑定的属性描述符处理,这点和特性没有区别,因为特性就是特殊的属性描述符。

既然特性是特殊的属性描述符,那属性描述符和特性有什么区别?

和特性的区别

总的来说,特性可以看做是Python定义好的一个特定用途的属性描述符,而用户自定义的属性描述符比特性相对更为灵活,可以根据需要进行抽象和继承,构建灵活用途的属性描述符。

假设我们现在需要给订单添加一个属性用于描述物品信息,并且需要验证这个字符串不能是空字符串。

import abc
from typing import ValuesView


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值