Python 属性装饰器 @property 基础用法

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

前言:我被一个"假属性"坑了整整三天

说实话,刚开始学Python那会儿,我一度以为自己掌握了面向对象的精髓。不就是class嘛!不就是self嘛!直到重构一个电商后台系统,我差点被一个小小的属性访问问题整破防了……

事情是这样的。我写了个Product类,里面有个price字段。起初简单粗暴,直接self.price = price完事儿。结果产品经理突然说:“价格不能为负数啊!要是程序里把价格算成了-50块,数据库一写进去,财务那边直接炸裂!”

行吧,那我加个校验呗。把price改成私有属性_price,然后写两个方法:get_price()和set_price()。改完一运行,我蚌埠住了——调用方代码全崩了!几百个地方都在用product.price = 19.9这种写法,现在全得改成product.set_price(19.9)。这工作量……我当场就想提桶跑路。

就在这时候,一个后端大佬默默发给我一行代码:@property。我试了下,卧槽!代码不用改,功能还能加!这就是Python最骚的操作之一,今天必须给你们整明白!

一、@property 到底是啥?说白了就是个"整容医生"

要搞懂@property,咱得先聊聊别的语言是怎么做的。

Java和C++那帮老派程序员,特别喜欢getter/setter模式。啥意思呢?就是不管啥属性,先藏起来(private),然后通过两个方法去操作。取数据用get_xxx(),存数据用set_xxx()。美其名曰"封装",实际上……代码写得像八股文,又臭又长。

Python就不一样了。Python的设计哲学是"简单优于复杂",但也得考虑扩展性啊!于是@property应运而生。它本质上是个装饰器,能把一个方法"伪装"成属性。你访问的时候像变量,实际上背后跑的是方法。这就叫"表里不一",但深得我心!

举个例子你就懂了。就像你去住酒店,门卡刷一下,门开了。对你来说就是"插卡开门"这么个简单动作。但背后呢?酒店系统要验证房号、检查有效期、记录开门时间、甚至可能还要联动电梯权限。@property就是帮你把这些复杂逻辑藏到"插卡"这个动作背后,对外看起来还是那么简单。

二、传统写法的痛,懂的都懂

在@property出现之前,咱们怎么处理属性校验的?来看段"考古代码":

class Circle:
    def init(self, radius):
        self._radius = radius  # 下划线开头,假装私有
    def get_radius(self):
        return self._radius

    def set_radius(self, value):
        if value < 0:
            raise ValueError("半径不能为负数啊喂!")
        self._radius = value

用起来是这样的

c = Circle(5)
print(c.get_radius())  # 输出:5
c.set_radius(10)       # 正常修改
c.set_radius(-5)       # 报错!

发现问题没?丑!太丑了! 每次访问属性都得带括号,像便秘一样不畅快。更可怕的是,如果你一开始直接用了c.radius = 5这种简单写法,后来想加校验,就得把所有调用方代码都改一遍。这在大型项目里简直是噩梦,几百个文件等着你翻修……

我当时就卡在这种情况。代码已经写了3万多行,突然要改接口风格?项目经理的眼神能杀人。说实话,那段时间我晚上做梦都在改get_xxx()和set_xxx(),睡醒枕头都是湿的。

三、@property 登场:一行代码拯救强迫症

@property的写法,看完之后我直接献上膝盖:

class Circle:
    def init(self, radius):
        self._radius = radius
    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("半径不能为负数!想啥呢?")
        self._radius = value

用起来……丝滑!

c = Circle(5)
print(c.radius)    # 看起来是访问属性,实际是调getter
c.radius = 10      # 看起来是赋值,实际是调setter
c.radius = -5      # 照样报错,但写法完全没变!

看到没?调用方式完全没变! 对用户来说,c.radius还是c.radius,但背后已经加了层层校验。这就是@property的精髓——后期加逻辑,不改前期代码。这种"向后兼容"的能力,在2025年的敏捷开发里简直是刚需,需求天天变,代码得扛得住折腾啊!

这里有个细节得注意。@property下面那个方法名,就是对外暴露的属性名。比如我上面写的def radius(self),那外面就用.radius访问。然后setter的写法是@radius.setter,这个radius必须和前面那个方法名完全一致,不然Python会一脸懵逼。

四、实战案例:电商系统的价格保卫战

回到我开头说的那个坑。用@property重构后,代码长这样:

class Product:
    def init(self, name, price):
        self._name = name
        self._price = price
    @property
    def price(self):
        """获取价格时自动保留两位小数,强迫症福音"""
        return round(self._price, 2)

    @price.setter
    def price(self, value):
        """设置价格时多重校验,产品经理再也没法挑刺"""
        if not isinstance(value, (int, float)):
            raise TypeError("价格必须是数字!别给我传字符串")
        if value < 0:
            raise ValueError("价格不能为负数!咱不做赔本买卖")
        if value > 999999:
            raise ValueError("价格离谱了!这卖的是黄金吗?")
        self._price = float(value)

    @property
    def name(self):
        """商品名自动转大写,品牌部要求"""
        return self._name.upper()

    @name.setter
    def name(self, value):
        if not value or len(value.strip()) == 0:
            raise ValueError("商品名不能为空!")
        self._name = value.strip()

业务代码完全不用改!

phone = Product("iPhone 17", 8999)
print(phone.price)      # 输出:8999
phone.price = 7999      # 正常打折
phone.price = -100      # 直接报错,拦截脏数据
print(phone.name)       # 输出:IPHONE 17,自动大写

这段代码我现在看着都爽。三个校验规则(类型检查、负数检查、上限检查)全塞进setter里,业务方调用的时候完全无感知。而且getter里我还加了round处理,解决浮点数精度问题(0.1+0.2不等于0.3那种坑)。这种"润物细无声"的增强,才是真正的Pythonic!

五、只读属性:让某些数据"只准看不准摸"

有时候咱们希望某个属性只能读,不能改。比如用户的id或者创建时间,这些一旦生成就该锁死。咋搞?

很简单!只写getter,不写setter。Python会自动把这个属性变成只读:

class User:
    def init(self, user_id):
        self._user_id = user_id
        self._created_at = "2025-04-13 14:30:00"
    @property
    def user_id(self):
        return self._user_id

    @property
    def created_at(self):
        return self._created_at
u = User(9527)
print(u.user_id)        # 正常输出:9527
u.user_id = 8866        # 报错:AttributeError: can't set attribute!

想删除属性?还可以加deleter(虽然用得少,但得知道):

@user_id.deleter
def user_id(self):
    del self._user_id
    print("用户ID已删除!数据脱敏完成")

不过说实话,deleter我工作这么多年用过的次数……不超过5次。大部分时候咱们还是get/set用得多。

六、@property 的隐藏技能:计算属性

除了校验数据,@property还能做动态计算。比如你想实时获取年龄,但只存生日;或者想实时算 BMI,但只存身高体重。这种"派生数据"用@property绝配:

from datetime import datetime
class Person:
    def init(self, birth_year):
        self._birth_year = birth_year
    @property
    def age(self):
        """年龄实时计算,每年自动涨一岁,不用维护"""
        current_year = datetime.now().year
        return current_year - self._birth_year

    @property
    def birth_year(self):
        return self._birth_year
p = Person(1990)
print(f"今年{datetime.now().year}岁")  # 输出:今年35岁

注意!age没有setter,因为是计算出来的,改它没意义

这种写法比手动算年龄优雅100倍!而且每次访问.age都是实时结果,不存在"数据过期"的问题。2025年最新的Python 3.13里,@property的性能还做了进一步优化,计算属性的开销几乎可以忽略。

七、踩坑指南:这些坑我帮你踩过了

用了这么多年@property,我也总结了一些血泪教训:

  1. 千万别在getter里改数据!
    曾经有个同事在@property的getter里加了日志记录,还顺便改了计数器。结果一调试发现数据对不上,因为每次访问属性都会触发getter,副作用太多了。getter就该纯读取,别搞事情!

  2. setter里的类型检查要趁早
    Python是动态类型,传啥都有可能。建议用isinstance()做类型校验,不然传个None进去,后面报错信息会把你带偏到怀疑人生。

  3. 别滥用@property
    不是所有属性都需要包装。简单的、不需要校验的字段,直接self.xxx = xxx就行。过度封装反而让代码显得"装"。记住:需要加逻辑的时候再上@property,不要为了用而用。

  4. 继承时要小心
    如果在子类里重写父类的property,要注意setter和getter是绑定的。重写getter的时候,如果不小心,可能会把setter给覆盖没了。这种情况……建议用传统方法或者super()处理,别硬刚。

八、结语:@property 真香!

说实话,刚开始学@property的时候,我觉得这玩意儿有点"魔法",不够直观。但经历过那次3万行代码的重构惨案后,我彻底真香了。它完美解决了"既要封装安全,又要调用简洁"的矛盾,让Python的面向对象编程既有Java的严谨,又不失Python的优雅。

在2025年的今天,Python 3.12/3.13已经发布,@property的性能和稳定性都达到了新高度。不管你是写Web后端、数据分析,还是搞AI模型封装,掌握这个装饰器都能让你的代码质量上一个台阶。

最后灵魂拷问一句:你现在的项目里,有多少本应该用@property却没用的"裸奔"属性? 回去翻翻代码,说不定能发现一堆隐患!如果这篇帮你少踩了一个坑,记得点个赞,咱们评论区聊聊你遇到过最离谱的属性校验需求是啥?我上次遇到个要求价格必须是质数的……产品经理的脑洞,不服不行!

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值