Python面向对象:@property装饰器属性访问控制

Python面向对象:@property装饰器属性访问控制

在这里插入图片描述

一、开篇:把方法伪装成属性

在Java中,你会写obj.getName()obj.setName("张三")——一堆getter和setter方法。Python的做法更优雅:用@property装饰器,让你像访问属性一样调用方法——可以无缝添加验证、计算、惰性加载等逻辑。

⌨️ 先看对比:

# ❌ Java风格——啰嗦
class PersonJavaStyle:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        if not value:
            raise ValueError("名字不能为空")
        self._name = value

# 使用:person.get_name() / person.set_name("李四")

# ✅ Python风格——优雅
class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        """获取名字——像属性一样访问"""
        return self._name

    @name.setter
    def name(self, value):
        """设置名字——带验证"""
        if not value:
            raise ValueError("名字不能为空")
        self._name = value

# 使用:person.name / person.name = "李四"
p = Person("张三")
print(p.name)      # 张三 —— 像属性一样读
p.name = "李四"    # 像属性一样写 —— 但实际调用了setter
print(p.name)      # 李四

二、@property的三种用法

2.1 只读属性

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        """半径——只读"""
        return self._radius

    @property
    def area(self):
        """面积——只读计算属性"""
        import math
        return math.pi * self._radius ** 2

    @property
    def circumference(self):
        """周长——只读计算属性"""
        import math
        return 2 * math.pi * self._radius

circle = Circle(5)
print(circle.radius)          # 5
print(f"面积: {circle.area:.2f}")   # 78.54
print(f"周长: {circle.circumference:.2f}")  # 31.42

# circle.radius = 10   # AttributeError —— 没有setter!
# circle.area = 100    # AttributeError —— 没有setter!

2.2 可读写属性(getter + setter)

class Product:
    def __init__(self, name, price):
        self.name = name
        self._price = price

    @property
    def price(self):
        """价格——getter"""
        return self._price

    @price.setter
    def price(self, value):
        """价格——setter(带验证)"""
        if value < 0:
            raise ValueError("价格不能为负数")
        self._price = value

    @property
    def discount_price(self):
        """折后价——只读(基于_price计算)"""
        return round(self._price * 0.9, 2)

product = Product("笔记本电脑", 5999)
print(product.price)          # 5999 —— getter
product.price = 4999          # setter —— 看起来像直接赋值
print(product.price)          # 4999
print(product.discount_price) # 4499.1 —— 自动基于最新价格计算

2.3 deleter——删除属性

class User:
    def __init__(self, username, password):
        self.username = username
        self._password = password

    @property
    def password(self):
        """密码——不可读(安全考虑)"""
        raise AttributeError("密码不可直接读取")

    @password.setter
    def password(self, value):
        """设置密码——带强度检查"""
        if len(value) < 8:
            raise ValueError("密码至少8位")
        self._password = value

    @password.deleter
    def password(self):
        """删除密码——清除敏感数据"""
        print("密码已被清除")
        self._password = None

user = User("zhangsan", "old_password")
# print(user.password)  # AttributeError: 密码不可直接读取

user.password = "new_secure_password"  # setter
del user.password                       # deleter —— 密码已被清除

三、@property的实际应用

3.1 数据验证

class Student:
    def __init__(self, name, age, score):
        self.name = name
        self.age = age      # 会调用setter
        self.score = score  # 会调用setter

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise TypeError("年龄必须是整数")
        if not 0 <= value <= 150:
            raise ValueError("年龄必须在0-150之间")
        self._age = value

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not 0 <= value <= 100:
            raise ValueError("分数必须在0-100之间")
        self._score = value

    @property
    def grade(self):
        """等级——基于score动态计算"""
        if self._score >= 90:
            return "A"
        elif self._score >= 80:
            return "B"
        elif self._score >= 70:
            return "C"
        elif self._score >= 60:
            return "D"
        else:
            return "F"

s = Student("张三", 20, 92)
print(f"{s.name}: {s.score}分, 等级{s.grade}")  # 张三: 92分, 等级A

3.2 惰性加载

class DataAnalyzer:
    """数据分析器——惰性计算统计信息"""

    def __init__(self, data):
        self.data = data
        self._statistics = None     # 缓存
        self._sorted_data = None    # 缓存

    @property
    def statistics(self):
        """统计信息——只在第一次访问时计算"""
        if self._statistics is None:
            print("正在计算统计数据...")
            self._statistics = {
                "count": len(self.data),
                "sum": sum(self.data),
                "avg": sum(self.data) / len(self.data),
                "min": min(self.data),
                "max": max(self.data),
            }
        else:
            print("从缓存获取统计数据")
        return self._statistics

    @property
    def sorted_data(self):
        """排序数据——惰性排序"""
        if self._sorted_data is None:
            print("正在排序...")
            self._sorted_data = sorted(self.data)
        else:
            print("从缓存获取排序数据")
        return self._sorted_data

    def add_data(self, value):
        """添加新数据——清除缓存"""
        self.data.append(value)
        self._statistics = None     # 缓存失效
        self._sorted_data = None    # 缓存失效

analyzer = DataAnalyzer([5, 2, 8, 1, 9, 3])
print(analyzer.statistics)    # 计算
print(analyzer.statistics)    # 缓存命中
print(analyzer.sorted_data)   # 计算(没有缓存)
analyzer.add_data(10)
print(analyzer.statistics)    # 重新计算(缓存已清除)

3.3 向后兼容的重构

# 场景:最初用简单属性,后来需要加验证/计算逻辑
# @property让你在保持API不变的情况下升级

# 版本1:简单属性
# class Product:
#     def __init__(self, price):
#         self.price = price  # 直接属性

# 版本2:需要价格验证——保持API不变!
class Product:
    def __init__(self, price):
        self.price = price  # 仍然像直接赋值一样使用!

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("价格不能为负")
        self._price = value

# 新旧代码都不需要改变!
p = Product(100)
print(p.price)     # 100 —— 和以前一样
p.price = 200      # 和以前一样 —— 但有了验证!
# p.price = -50    # ValueError —— 新的保护

四、@property的底层原理

# @property本质上是描述符(Descriptor)的语法糖

# 下面的代码:
class MyClass:
    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value

# 等价于:
class MyClassManual:
    def get_value(self):
        return self._value

    def set_value(self, new_value):
        self._value = new_value

    value = property(get_value, set_value)  # property()构造

# property()的完整签名:
# property(fget=None, fset=None, fdel=None, doc=None)
# fget: getter函数
# fset: setter函数
# fdel: deleter函数
# doc: 文档字符串

五、最佳实践和注意事项

# ✅ 使用@property的场景:
# 1. 需要数据验证
# 2. 计算属性(基于其他属性动态计算)
# 3. 惰性加载
# 4. 向后兼容的重构
# 5. 只读属性

# ⚠️ 不要使用@property的场景:
# 1. 有副作用的操作(读属性不应该修改状态)
#    @property
#    def latest_data(self):
#        self._fetch_from_api()  # ❌ 副作用!读属性不应该触发网络请求
#        return self._data

# 2. 耗时操作(读属性应该很快)
#    @property
#    def complex_report(self):
#        time.sleep(5)  # ❌ 太慢了!用方法代替
#        return "report"

# 3. 需要传参数时(用方法代替)
#    @property
#    def data_by_date(self):  # ❌ 不能传参数!
#        pass
#    def get_data(self, date):  # ✅ 用方法
#        pass

# 💡 简单判断:如果访问感觉像"读一个属性",用@property
# 如果感觉像"执行一个动作",用方法

六、总结

@property是Python最优雅的特性之一——它让方法看起来像属性,让你在保持简洁API的同时,拥有方法的所有能力(验证、计算、缓存)。

💡 核心要点:

装饰器作用调用方式
@propertygetterobj.attr
@attr.settersetterobj.attr = value
@attr.deleterdeleterdel obj.attr

黄金法则:对外像属性一样简单,对内像方法一样灵活。@property是Python封装的最佳拍档。

评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值