• Python 面向对象 之 @property


    Python 面向对象 之 Property

    初识 @property

    Property 是 Python 类的一个内置的 装饰器. @property 的作用是 将一个方法, 变为属性来调用.

    装饰器: 在不改变原函数的基础上, 动态给函数增加功能

    之前已经讨论过很多装饰器的细节了, 本质就是利用Pyhton变量的引用(指针)特性 和 变量命名空间来实现的.

    def out(func):
        
        def inner(*args, **kwargs):
            
            print(f"...{args[0]} is checking...")
            return func(*args, **kwargs)
        
        return inner
    
    
    @out
    def check_2019_nCov(name):
        msg = f"okay, {name} is very healthy."
        return msg
    
    ret = check_2019_nCov("youge")
    print(ret)
    
    # output
    ...youge is checking...
    okay, youge is very health
    

    @property 这个装饰器, 我一般会用在代码优化上多一点, 将一个方法, 封装为属性, 在阅读体验会好一些. 具体咋实现的, 也没搞清楚, 暂时能应用就好了.

    具体写法即在需要的方法上边用 @property 装饰, 参数只有self, 调用的时候不加括号.

    @property 将方法装饰为属性

    # 访问类的私有属性
    class Foo:
        def __init__(self, score):
            self.__score = score
    
        @property  # 将方法装饰为属性
        def show_score(self):
            return self.__score
    
    
    if __name__ == '__main__':
        s = Foo(66)
        # 在写法上更加优雅, 不用多写 "方法()" 没用的括号
        print(s.show_score)
    
    # output:
    66
    

    即在类自己的成员之间玩, (不需要传外部参数的情景下用, 表示 类的成员属性.), 初觉得比较 Pythonic 吧.

    插一嘴, 上例中的 @property 所装饰的方法, 的功能是给 外部一个查看内部属性的接口. __ score 表示私有变量, 不能被直接访问, 期望上, 但 外部仍然是可以访问内部变量的, 感觉这算是 Pyhton不够严谨的地方吧, 没有绝对的安全哦.

    s = Foo(66)
    
    # 外部直接访问私有变量(方法) 是不可以的
    
    print(s.__score)
    
    # output
    AttributeError: 'Foo' object has no attribute '__score'
    

    but, 通过 实例对象.类名_ 私有属性 这样的方式是可以的 (为啥呢, 可以直接研究下Python类自身的构造原理)

    s = Foo(66)
    
    # 没有绝对的安全哦, 只要互相尊重
    print(s._Foo__score)
    
    # output
    66
    

    @property 相关属性

    通过上面的写法, 感觉无非就是, 把方法变成了属性而已, 似乎.除了写法上比较简洁优雅一点, 具体的使用场景是什么呢?

    绑定属性-校验-手动实现

    class Foo:
        def __init__(self, score):
            self.score = score
         
    # 绑定score属性
    s = Foo(666)
    
    

    更改 score 属性时, 我通常这样写.

    s.score = 666
    

    这样的无比粗暴 (直接将属性给暴露出去) 方式来 绑定属性 虽然写起来简单, 但是没有检验参数 导致可以随意被改.

    这样粗暴的方法显然是不太合理的. 应该对 score进行一个校验, 值, 类型等都可以. 即我们在设置 score 属性的时候, 应该要实现两个 (自定义) 方法, get_score 用来查看值, 而 set_score 用来设置值.

    class Foo:
        def get_score(self):
            """获取属性值"""
            print(self.score)
    
        def set_score(self, value):
            """设置属性值"""
            if not isinstance(value, int):
                raise ValueError("score 必须为整数哦")
            if value > 100 or value <= 0:
                raise ValueError("score 必须在 0-100哦")
    
            self.score = value
    
    
    if __name__ == '__main__':
        f = Foo()
        # 校验通过是可以的
        f.set_score(66)
        f.get_score()
        
        # output
        66
    
    
    # 随便设置 score 属性就不可以了. 
    if __name__ == '__main__':
        f = Foo()
        f.set_score('youge')
        f.get_score()
        
        # output
        	raise ValueError("score 必须为整数哦")
    	ValueError: score 必须为整数哦
    
    # 继续 debug
    if __name__ == '__main__':
        f = Foo()
        f.set_score('youge')
        f.get_score()
        
        # output
            raise ValueError("score 必须在 0-100哦")
    ValueError: score 必须在 0-100哦
    

    case1 绑定属性-校验-@property 实现

    • 默认是 只读
    • setter
    • deleter
    class Foo:
    
        @property  # read only
        def score(self):
            """获取属性值"""
            return self.score
    
        @score.setter
        def score(self, value):
            """设置属性值"""
            if not isinstance(value, int):
                raise ValueError("score 必须为整数哦")
            if value > 100 or value <= 0:
                raise ValueError("score 必须在 0-100哦")
    
            # 这里一定不能 self.socre = value 哦, 不然就递归了.
            self._score = value
            print(f"set score = {value} successfully.")
    
    if __name__ == '__main__':
        f = Foo()
        f.score = 66
        
        # print(f.score)
        # output:
        set score = 66 successfully
        
    
    if __name__ == '__main__':
        f = Foo()
        f.score = 'youge'
        
        # output
            raise ValueError("score 必须为整数哦")
    	ValueError: score 必须为整数哦
    
    

    case 2 - 封装类属性

    再来一小栗子吧. 嗯, 封装一个Book的类, 主要用来描述书籍的名称, 价格和评论. 然后呢, 要求能用 property 装饰器实现对 Book 的实例属性 set 和 get 方法进行定义. 即让实例对象可以用属性的方式来操作方法/属性.

    class Book:
        def __init__(self, name, price, comment=None):
            self.__name = name
            self.__price = price
            self.__comment = comment
    
        # 传统方法对price属性进行 设置和访问
        def get_price(self):
            return self.__price
    
        def set_price(self, value):
            self.__price = value
    
        # price = property(get_price, set_price)
    
        # @property 装饰器对 comment 属性进行 设置和访问
        @property
        def my_comment(self):
            # 默认是 getter, 需要return 或 yeild
            return self.__comment
    
        @my_comment.setter
        def my_comment(self, value):
            # 检验
            if not isinstance(value, float):
                raise ValueError("must be a decimal number")
    
            if value < 0 or value > 1000:
                raise ValueError("0 ~ 1000")
    
            self.__comment = value
    
        @my_comment.deleter
        def my_comment(self):
            print("delete this atrr...")
    
    

    小结

    • @property 装饰器的将方法变成属性来使用
    • @property 默认是只读, 配合 setter, deleter ... 等一起使用
    • 常用场景是对类实例属性赋值是, 进行一个 值的校验功能
  • 相关阅读:
    解决Python错误-----SSL: CERTIFICATE_VERIFY_FAILED
    SpringBoot性能优化
    解决WARN:No URLs will be polled as dynamic configuration sources.
    浅析如何解决终端输入长命令不换行覆盖(Docker容器内输入长命令折行覆盖)问题:如何设置docker容器tty终端窗口大小-Linux stty命令设置串口终端行列数
    shell中的传递参数$0 / $n、shell运算符(算术/关系/布尔/字符串/文件测试)、echo 命令输出字符串、printf 命令输出格式化的字符串、test 命令检查某条件是否成立
    【转】Grafna学习随记
    【转】使用InfluxDB的连续查询解决聚合性能问题
    【转】TDengine踩坑随记(最后一次更新:2021-4-7 20:30)
    【转】tdengine的更新功能,呼声最高的数据更新功能来了,用户需要什么,我们就开源什么
    【转】Go mod常用与高级操作
  • 原文地址:https://www.cnblogs.com/chenjieyouge/p/12244456.html
Copyright © 2020-2023  润新知