前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OOP 三大特性:封装中的 property

OOP 三大特性:封装中的 property

作者头像
老齐
发布2022-01-04 21:05:12
4730
发布2022-01-04 21:05:12
举报
文章被收录于专栏:老齐教室

8.7.2 property 装饰器

或许,读者也认为,Python 不能实现真正意义上的对象封装,从上一节内容已经看到,以单下划线开始的命名是“君子约定”,以双下划线开始的命名是“虚晃一枪”。如果来“真”的,Python 能行吗?

Python 没有像 Java 等某些语言那样,以 publicprivate 等关键词定义类,可以说所有的类都是 pbulic 的,8.7.1节介绍的以命名“私有化”形式实现封装,也不是 Java 语言中的 private 。但是,Python 中有一种方法,能够让程序中的对象更接近“封装”。

在 IDE 中编写名为 mypassword.py 的程序文件,其代码如下:

代码语言:javascript
复制
#coding:utf-8
'''
filename: mypassword.py
'''
class User:
    def __init__(self):
        self.password = 'default'

    def get_pwd(self):
        return self.password

    def set_pwd(self, value):
        self.password = value
        print("you have set your password.")

然后从此文件所在目录进入到 Python 交互模式(参阅8.5.2节所示方法,这种调试方法以后会经常用到,请读者务必掌握)。

代码语言:javascript
复制
>>> from mypassword import *
>>> laoqi = User()
>>> laoqi.password
'default'
>>> laoqi.get_pwd()
'default'
>>> laoqi.set_pwd('123')
you have set your password.
>>> laoqi.password
'123'
>>> laoqi.password = '456'
>>> laoqi.get_pwd()
'456'

从上面的操作可知,实例 laoqi 的密码可以通过属性 password 或者方法 get_pwd() 读取,也可以通过属性 password 或者方法 set_pwd() 重置。显然,这样对密码的管理是非常不安全的——要进行适当的“封装”,基本要求是:密码只能通过属性读取,不能通过属性重置,即是只读的。

mypassword.py 中的文件按照下面方式进行修改。

代码语言:javascript
复制
#coding:utf-8
'''
filename: mypassword.py
'''
class User:
    def __init__(self):
        self.__password = 'default'
    
    @property                # (1)
    def password(self):      # (2) 
        return self.__password

    def set_pwd(self, value):
        self.__password = value
        print("you have set your password.")

为了实现密码只读的需求,使用了注释(1)所示的装饰器 @property ——这个装饰器是基于内置函数 property() ,并且将原来的方法 get_pwd() 更名为 password() (如注释(2)所示)。此外,将原来的实例属性 password 重命名为 __password 。重新载入 mypassword 模块(参阅8.5.2节,最简单的方法是在交互模式中执行 exit() 函数退出后,在进入交互模式),执行如下操作:

代码语言:javascript
复制
>>> from mypassword import *
>>> laoqi = User()
>>> laoqi.password
'default'
>>> laoqi.password = '123'           # (3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> laoqi.__password = '123'         # (4)
>>> laoqi.password
'default'

通过实例 laoqi 调用 password 属性——注意,不是 laoqi.password() 方法。虽然注释(2)定义的是 password() 方法,但是此方法被 @property 装饰之后,就可以用同名的属性形式调用,并得到了默认的密码值。

注释(3)试图通过赋值语句修改密码,结果失败。但,注释(4)貌似成功了,其实这也没有修改 laoqi.password 的值,只是为实例 laoqi 增加了一个名为 __password 的实例属性。如此,实现了密码的“只读”功能。

当然,因为还遗留了 laoqi.set_pwd() 方法,调用它还是能重置密码的。

代码语言:javascript
复制
>>> laoqi.set_pwd('456')
you have set your password.
>>> laoqi.password
'456'

但是,这样实现重置,有点“太丑”了,还是用 laoqi.password = '456' 的方式重置更优雅——注释(3)的执行结果已经说明,不能用赋值语句重置。还有,明码保存是不是太不安全?重置密码之后,最好是能加密保存。

于是有了第二个需求:能够用赋值语句(类似注释(3)那样)重置密码(从用户角度将,重置密码是非常必要的),并且密码要加密保存——否则不称之为“密”码。

根据这些需要,再次修改 mypassword.py 文件中的代码。

代码语言:javascript
复制
#coding:utf-8
'''
filename: mypassword.py
'''
class User:
    def __init__(self):
        self.__password = 'default'
    
    @property
    def password(self):
        return self.__password

    @password.setter                 # (5)
    def password(self, value):       # (6)
        value = int(value) + 728     # 最低级的加密方法
        self.__password = str(value)
        print("you have set your password.")

注意观察修改内容。注释(5)增加了一个装饰器(注释写法),它的作用就是让注释(6)所定义的方法变成以属性赋值的形式。在注释(6)的方法里面,用了一种最拙劣的加密方法。

重载模块 mypassword 后(参阅8.5.2节),在交互模式中继续操作:

代码语言:javascript
复制
>>> from mypassword import *
>>> laoqi = User()
>>> laoqi.password
'default'
>>> laoqi.password = '456'     # (7)
you have set your password.
>>> laoqi.password             # (8)
'1184'

注释(7)实现了用赋值语句重置密码的需求,并且从注释(8)的返回值可知,注释(7)所提交的密码已经被“加密”。

由上述内容,已经初步理解了 @property 装饰器的一个作用:将方法转换为属性访问。就凭这个功能,它就能让程序“优雅”很多。比如:

代码语言:javascript
复制
#coding:utf-8
'''
filename: computearea.py
'''
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

if __name__ == "__main__":
    rect = Rectangle(7, 8)     # (9)
    rect_area = rect.area()    # (10)
    print(f"width = {rect.width}, height = {rect.height}")
    print(f"call rect.area() method to rectangle area = {rect_area}")

在此程序中,注释(9)创建了一个矩形实例,注释(10)得到了此矩形的面积。虽然这种写法能够实现功能,但是在追求“优雅”的 Python 开发者心目中,注释(10)是“丑陋的”。实例的宽度和长度,分别用属性 rect.widthrect.height 得到,那么面积,也应该是实例的属性,不应该是方法。所以用 rect.area() 计算面积,本身就不很“OOP”。如果用 rect.area 这样的属性形式得到实例的面积,那才符合 OOP 思想,并体现着 Python 的优雅,更蕴含着开发者的智慧。

@property 对程序稍作修改:

代码语言:javascript
复制
#coding:utf-8
'''
filename: computearea.py
'''
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    @property                          # 增加装饰器
    def area(self):
        return self.width * self.height

if __name__ == "__main__":
    rect = Rectangle(7, 8)
    #rect_area = rect.area()          # 不再调用方法
    print(f"width = {rect.width}, height = {rect.height}")
    print(f"call rect.area attribute to rectangle area = {rect.area}")

执行结果:

代码语言:javascript
复制
% python computearea.py
width = 7, height = 8
call rect.area attribute to rectangle area = 56

装饰器 @property 是基于内置函数 property() ,这方面类似于8.4.2和8.4.3节使用过的类方法、静态方法装饰器。它不仅能能实现“属性”的读、写,还能实现删除功能。下面的示例中,读者进一步体会 @property 的作用。

代码语言:javascript
复制
#coding:utf-8
'''
temperature.py
'''
class Celsius:
    def __init__(self, temperature=0):
        self.__temperature = temperature

    @property
    def temperature(self):
        return self.__temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273.15:       # (11)
            raise ValueError("Temperature below -273 is not possible")
        self.__temperature = value

    @temperature.deleter
    def temperature(self):
        raise AttributeError("Can't delete attribute")

进入到交互模式(参阅8.5.2节),进行如下操作:

代码语言:javascript
复制
>>> from temperature import *
>>> person = Celsius()
>>> person.temperature
0
>>> person.temperature = 36

>>> person.temperature = -300      # (12)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/qiwsir/Documents/my_books/Python完全自学手册/codes/temperature.py", line 16, in temperature
    raise ValueError("Temperature below -273 is not possible")
ValueError: Temperature below -273 is not possible
  
>>> del person.temperature         # (13)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/qiwsir/Documents/my_books/Python完全自学手册/codes/temperature.py", line 21, in temperature
    raise AttributeError("Can't delete attribute")
AttributeError: Can't delete attribute

重点看注释(12)的操作结果,之所抛出异常,是因为在程序中注释(11)对值的大小进行了判断,如果条件满足,就执行 raise 语句(参阅第10章10.3节)。

再看注释(13)的执行结果,抛出了 AttributeError 异常,是因为用装饰器 @temperature.deleter 所装饰的方法中,执行了 raise 语句,即禁止删除 person.temperature ,如此对该对象给予“保护”。

自学建议 学到本章是对读者的最大考验,一般的学习者会止步于本书第7章,对第8章及以后的内容望而却步。为什么?因为从本章开始,不仅要综合运用已学过的知识,还对日常以“直觉感受”为主的思考问题方式提出了挑战。在8.3节的【自学建议】中已经提到了“抽象能力”之于编写类的重要性,并且建议读者要“多练习”。 在这里进一步建议,要对原有的练习作品,用后续所学去优化。如果读者现在“回头看”从第1章以来做过的各种练习,或许对某些问题又有了新的思考,甚至于认为书中的代码也不怎样——这说明已经有了较高的欣赏和评价能力。除了批判之外,更要自己动手,先把我写的示例代码进行优化——别忘了告诉我,让我和其他读者都能进步。 还有,要“胆子大一些”,敢于自己设计(或者是“想象”)一些项目,并动手尝试“做一做”,哪怕最后不成功,自己也有了经验。 ”

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老齐教室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 8.7.2 property 装饰器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档