Python @property 装饰器详解

@property 是 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 isinstance(value, str):
            raise TypeError("姓名必须是字符串")
        self._name = value
    
    @name.deleter
    def name(self):
        """删除姓名"""
        del self._name

# 使用示例
p = Person("张三")
print(p.name)      # 张三 - 像属性一样访问
p.name = "李四"     # 像属性一样赋值
print(p.name)      # 李四
del p.name         # 像属性一样删除

三种装饰器

1. @property - getter

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        """获取半径"""
        return self._radius
    
    @property
    def area(self):
        """计算面积 - 只读属性"""
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.radius)  # 5
print(c.area)    # 78.53975
# c.area = 100   # 错误!AttributeError: can't set attribute

2. @方法名.setter - setter

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """只读属性"""
        return self._celsius * 9/5 + 32

t = Temperature(25)
t.celsius = 30    # 使用setter
print(t.celsius)   # 30
# t.celsius = -300 # ValueError!

3. @方法名.deleter - deleter

class User:
    def __init__(self, username):
        self._username = username
        self._password = "secret"
    
    @property
    def password(self):
        return self._password
    
    @password.deleter
    def password(self):
        """删除密码"""
        print("正在删除密码...")
        del self._password

user = User("alice")
print(user.password)  # secret
del user.password     # 正在删除密码...
# print(user.password) # AttributeError

实际应用场景

1. 数据验证

class Student:
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score
    
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        if not value or not value.strip():
            raise ValueError("姓名不能为空")
        self._name = value.strip()
    
    @property
    def age(self):
        return self._age
    
    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise TypeError("年龄必须是整数")
        if value < 0 or 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):
        """只读属性 - 根据分数计算等级"""
        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("张三", 18, 85)
print(f"{s.name}, {s.age}岁, 成绩{s.score}, 等级{s.grade}")

2. 延迟计算/缓存

class DataProcessor:
    def __init__(self, data):
        self.data = data
        self._processed = None
    
    @property
    def processed_data(self):
        """延迟计算,结果会被缓存"""
        if self._processed is None:
            print("正在处理数据...")
            self._processed = [x * 2 for x in self.data]
        return self._processed

dp = DataProcessor([1, 2, 3, 4, 5])
print(dp.processed_data)  # 第一次:正在处理数据... [2,4,6,8,10]
print(dp.processed_data)  # 第二次:直接返回 [2,4,6,8,10](不打印)

3. 属性依赖更新

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
        self._area = None
    
    @property
    def width(self):
        return self._width
    
    @width.setter
    def width(self, value):
        self._width = value
        self._area = None  # 清除缓存
    
    @property
    def height(self):
        return self._height
    
    @height.setter
    def height(self, value):
        self._height = value
        self._area = None
    
    @property
    def area(self):
        if self._area is None:
            self._area = self._width * self._height
        return self._area

r = Rectangle(5, 3)
print(r.area)  # 15(计算)
r.width = 10
print(r.area)  # 30(重新计算)

使用 @property 的优点

1. 向后兼容

# 最初版本 - 直接使用属性
class PersonV1:
    def __init__(self, name):
        self.name = name

# 后续版本 - 需要添加验证,但不影响现有代码
class PersonV2:
    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("Name cannot be empty")
        self._name = value
# 现有代码 p.name = "张三" 仍然可以正常工作

2. 封装逻辑

class BankAccount:
    def __init__(self, balance=0):
        self._balance = balance
        self._transactions = []
    
    @property
    def balance(self):
        """只读属性"""
        return self._balance
    
    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("存款金额必须为正数")
        self._balance += amount
        self._transactions.append(f"存款: +{amount}")
    
    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("取款金额必须为正数")
        if amount > self._balance:
            raise ValueError("余额不足")
        self._balance -= amount
        self._transactions.append(f"取款: -{amount}")
    
    @property
    def transaction_history(self):
        """只读属性"""
        return self._transactions.copy()

acc = BankAccount(1000)
acc.deposit(500)
acc.withdraw(200)
print(f"余额: {acc.balance}")  # 1300
print(f"交易记录: {acc.transaction_history}")
# acc.balance = 9999  # 错误!无法直接修改

注意事项

  1. 命名约定:通常使用下划线前缀(_)表示私有属性

  2. 性能考虑@property 方法会有轻微的性能开销

  3. 不要滥用:简单的属性访问不需要使用 @property

  4. 继承行为@property 会被子类继承

class Parent:
    @property
    def value(self):
        return 10

class Child(Parent):
    @property
    def value(self):
        return 20  # 可以覆盖

c = Child()
print(c.value)  # 20

总结

@property 是 Python 中实现封装和数据验证的优雅方式,它让你能够:

  • 在访问属性时添加逻辑(验证、计算等)

  • 创建只读属性

  • 实现延迟计算

  • 保持向后兼容性

  • 提供清晰的 API 设计

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐