38、Python之面向对象:再聊一下property,及泛化后的属性描述符

引言

上一篇文章中,我们通过使用property特性,从而以一种兼容性更好的方式对有缺陷的类型设计的代码进行了优化。

在本文中,我们将继续对property做一个补充,同时看这种解决方案在Python中更抽象意义上的泛化之后的特性——属性描述符的用法。

再看property

其实,关于property的使用,对应着面向对象中对实例属性的三种操作:

1、@property修饰的方法:将相关的私有属性封装为只读的特性,对应get操作。

2、@方法名.setter修饰的同名方法:提供对应的私有属性的设置接口,对应set操作。

3、@方法名.deleter修饰的同名方法:提供对应的私有属性的删除接口,对应delete操作。

前两点在上一篇的文章中其实已经有所提及,下面以一个完整的代码来演示一下property的完整使用:

class DaGongRen:
    def __init__(self, name, age):
        self.name = name
        self.__age = age

    @property
    def age(self):
        print("age特性的getter被调用")
        return self.__age

    @age.setter
    def age(self, new_age):
        print("age特性的setter被调用")
        if new_age <= 0 or new_age > 200:
            raise ValueError('异常的年龄,必须在(0, 200)之间')
        self.__age = new_age

    @age.deleter
    def age(self):
        print("age特性的deleter被调用")
        raise TypeError('年龄属性不允许被删除')

    def work(self):
        print(f"{self.__age}岁的打工人{self.name}在努力工作")


if __name__ == '__main__':
    zs = DaGongRen('张三', 66)
    zs.work()
    # 获取age属性
    print(f"年龄为:{zs.age}")
    # 修改age属性
    zs.age = 80
    zs.work()
    del zs.age

执行结果:

a120720eff16d9e05dfe4ec6784198d0.jpeg

可以看到我们分别执行对age特性的访问、设置、删除时,会调用对应的property修饰的方法。

通过property修饰的方法对属性的封装,我们可以对属性的各种操作进行自定义的控制。但是,如果我们有多个属性,都要进行类似的自定义控制的功能,每个属性都要写3个同样的封装方法,似乎有点麻烦。

根据DRY(Don't Repeat Yourself)原则,以及Python的设计理念,必然有更加便捷的方法,帮我们快速实现对一批属性进行类似property特性的封装,这就是“属性描述符”。

属性描述符

property这种属性封装的方式进一步泛化,就得到了Python中的属性描述符的概念。而所谓的属性描述符就是一个代表属性值的对象,它通过实现一个或者多个魔术方法__get__()、__set__()和__delete__(),可以将描述符与属性访问机制进行挂钩,从而自定义对属性的访问控制。

简单看一下,上面通过property封装age属性的方式,使用描述符的方式的实现代码:

class IntProperty:
    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        if value <= 0 or value > 200:
            raise ValueError('异常的年龄,必须在(0, 200)之间')
        self.value = value

    def __delete__(self, instance):
        raise TypeError('年龄属性不允许被删除')


class DaGongRen:
    age = IntProperty()

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def work(self):
        print(f"{self.age}岁的打工人{self.name}在努力工作")


if __name__ == '__main__':
    zs = DaGongRen('张三', 66)
    zs.work()
    # 获取age属性
    print(f"年龄为:{zs.age}")
    # 修改age属性
    zs.age = 80
    zs.work()
    print(zs.__dict__)
    # 尝试删除,会同样报错
    del zs.age

执行结果:

2a526153d6031f2eb6cafb3987a44ae2.jpeg

我们简单分析一下,可以得出以下结论:

1、从执行效果来看,使用描述符的方式跟property的方式效果是一致的。

2、属性描述符的方式,需要定义一个类,实现__get__()、__set__()、__delete__()这三个魔术方法。

3、属性描述符的方式中,将属性描述符要控制的属性,以类属性的形式定义,类属性指向的为属性描述类的实例对象,无需再定义私有属性了,这种方式似乎更加彻底,从类型实例对象的__dict__中可以看到,并不存在该属性。

此外,property本身是描述符的一种特殊形式,它的背后也是使用了描述符机制。

但是,如果只是上面的这个代码示例,似乎没有看出使用描述符的好处,而且代码变得更加难以阅读。如果在类型中新增了几个属性,也需要进行同样的访问控制呢?

此时,如果使用property方式,对应的代码,有几个属性需要同样的控制,就要写几次。而如果使用描述符的方式,只需要在类型的定义中添加几个同样的类属性即可。假如我们要添加两个属性:工龄、司龄,使用同样的访问控制,直接来看实现代码:

class IntProperty:
    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        if value <= 0 or value > 200:
            raise ValueError('异常的取值,必须在(0, 200)之间')
        self.value = value

    def __delete__(self, instance):
        raise TypeError('该属性不允许被删除')


class DaGongRen:
    age = IntProperty()
    work_age = IntProperty()
    team_age = IntProperty()

    def __init__(self, name, age, work_age, team_age):
        self.name = name
        self.age = age
        self.work_age = work_age
        self.team_age = team_age

    def work(self):
        print(f"{self.age}岁的打工人{self.name}在努力工作")


if __name__ == '__main__':
    zs = DaGongRen('张三', 66, 5, 3)
    zs.work()
    # 获取属性
    print(f"年龄为:{zs.age}")
    print(f"工龄为:{zs.work_age}")
    print(f"司龄为:{zs.team_age}")
    # 修改age属性
    zs.age = 80
    zs.work_age += 1
    zs.team_age += 1
    zs.work()
    print(zs.__dict__)
    # 尝试删除,会同样报错
    del zs.team_age

执行结果:

bf83bd46df9a8774825e22bdf5cb3b08.jpeg

可以看到,添加了两个属性,我们只需要在类中添加两个类属性,指向描述符实例对象,即自动完成了同样的属性访问控制。

总结

本文完整地介绍了property的用法及其对应的get、set、delete的三种操作的方法的封装。并介绍了相较于property更加通用的属性封装的方法——属性描述符。需要说明的是:

1、property是描述符的一种特殊形式。

2、描述符提供了更大的灵活性和访问控制力,但是property提供的接口一般来说更加直观易读,且更加简洁。

3、在使用中,如果只有少量简单的属性需要加访问控制,可以选择使用property来实现,从而更加直观易读;但是,如果涉及到多个属性需要进行访问控制,则可以考虑使用描述符。

此外,常见的ORM框架,也是基于描述符的特性来实现的,感兴趣的可以查阅相关的源码。

感谢您的拨冗阅读,本文的内容如果对您学习Python有所帮助,欢迎点赞、收藏。

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值