在 python 中,property 是一个内置的装饰器,用于将类方法转换为类属性,实现对属性的高级控制(如类型检查、只读限制、计算属性等)。
以下是对 property 的详细解析与实战案例:
一、核心概念与基础语法
1.property的本质
- 属性封装:将方法伪装成属性,通过点号(
.)直接访问 - 访问控制:自定义属性的 getter、setter 和 deleter 方法
 - 计算属性:动态计算属性值,无需显式存储
 
2. 基础语法(两种方式)
# 方式1:使用 property() 函数
class person:
    def __init__(self, age):
        self._age = age  # 私有属性
        
    def get_age(self):
        return self._age
        
    def set_age(self, value):
        if value < 0:
            raise valueerror("年龄不能为负数")
        self._age = value
        
    age = property(get_age, set_age)  # 创建 property 对象
# 方式2:使用装饰器(推荐)
class person:
    def __init__(self, age):
        self._age = age
        
    @property  # 相当于 age = property(age)
    def age(self):
        return self._age
        
    @age.setter  # 相当于 age = age.setter(setter)
    def age(self, value):
        if value < 0:
            raise valueerror("年龄不能为负数")
        self._age = value
3. 使用示例
p = person(25) print(p.age) # 调用 getter 方法,输出: 25 p.age = 30 # 调用 setter 方法 p.age = -5 # 抛出 valueerror: 年龄不能为负数
二、进阶用法
1. 只读属性(仅实现 getter)
class circle:
    def __init__(self, radius):
        self.radius = radius
        
    @property
    def area(self):
        return 3.14 * self.radius ** 2
c = circle(5)
print(c.area)  # 输出: 78.5
c.area = 100   # 报错: attributeerror: can't set attribute
2. 计算属性(动态计算)
class rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        
    @property
    def area(self):
        return self.width * self.height
        
    @property
    def perimeter(self):
        return 2 * (self.width + self.height)
r = rectangle(3, 4)
print(r.area)     # 输出: 12
print(r.perimeter)  # 输出: 14
3. 删除属性(实现 deleter)
class person:
    def __init__(self, name):
        self._name = name
        
    @property
    def name(self):
        return self._name
        
    @name.deleter
    def name(self):
        print("删除名字...")
        del self._name
p = person("alice")
print(p.name)  # 输出: alice
del p.name     # 输出: 删除名字...
print(p.name)  # 报错: attributeerror
4. 类型检查与验证
class temperature:
    def __init__(self, celsius):
        self.celsius = celsius
        
    @property
    def celsius(self):
        return self._celsius
        
    @celsius.setter
    def celsius(self, value):
        if not isinstance(value, (int, float)):
            raise typeerror("温度必须是数字")
        self._celsius = value
        
    @property
    def fahrenheit(self):
        return self._celsius * 1.8 + 32
t = temperature(25)
print(t.fahrenheit)  # 输出: 77.0
t.celsius = "30"     # 报错: typeerror
三、实战案例
1. 数据库字段映射
class user:
    def __init__(self, db_row):
        self._db_row = db_row
        
    @property
    def id(self):
        return self._db_row["id"]
        
    @property
    def username(self):
        return self._db_row["username"]
        
    @property
    def email(self):
        return self._db_row["email"]
# 使用示例
db_data = {"id": 1, "username": "john", "email": "john@example.com"}
user = user(db_data)
print(user.username)  # 输出: john
2. 缓存计算结果
import math
class factorial:
    def __init__(self, number):
        self.number = number
        self._result = none
        
    @property
    def result(self):
        if self._result is none:
            print("计算阶乘...")
            self._result = math.factorial(self.number)
        return self._result
f = factorial(5)
print(f.result)  # 输出: 计算阶乘... 120
print(f.result)  # 直接返回缓存结果: 120
3. 权限控制
class account:
    def __init__(self, balance, owner):
        self._balance = balance
        self._owner = owner
        
    @property
    def balance(self):
        return self._balance
        
    @balance.setter
    def balance(self, value):
        if self._owner != "admin":
            raise permissionerror("只有管理员可以修改余额")
        self._balance = value
acc = account(1000, "user")
print(acc.balance)  # 输出: 1000
acc.balance = 2000  # 报错: permissionerror
四、深入理解与注意事项
1.property的底层实现
property 是一个描述符(descriptor)类,其简化实现如下:
class property:
    def __init__(self, fget=none, fset=none, fdel=none):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        
    def __get__(self, instance, owner):
        if instance is none:
            return self
        if self.fget is none:
            raise attributeerror("unreadable attribute")
        return self.fget(instance)
        
    def __set__(self, instance, value):
        if self.fset is none:
            raise attributeerror("can't set attribute")
        self.fset(instance, value)
        
    def __delete__(self, instance):
        if self.fdel is none:
            raise attributeerror("can't delete attribute")
        self.fdel(instance)
        
    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel)
        
    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel)
        
    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel)
2. 继承与property
- 子类可重写父类的 property:
class parent: @property def value(self): return 10 class child(parent): @property def value(self): return 20 
3. 性能考量
- 少量调用:
property的性能开销极小(约 100ns / 次) - 大量调用:若性能敏感(如循环百万次),建议直接访问属性
 
五、与其他技术的对比
| 技术 | 用途 | 实现方式 | 
|---|---|---|
| property | 属性封装与控制 | 装饰器或 property () 函数 | 
| getter/setter | 传统的访问控制方法 | 显式定义方法 | 
| getattr | 动态属性获取 | 魔法方法(所有属性都经过它) | 
| setattr | 动态属性设置 | 魔法方法(所有属性都经过它) | 
六、常见误区与最佳实践
1. 避免过度使用property
- 推荐场景:需要验证、计算或访问控制的属性
 - 不推荐场景:简单的数据封装(直接使用公有属性)
 
2. 私有属性命名约定
- 使用单下划线 
_attr表示受保护属性(非强制私有) - 使用双下划线 
__attr进行名称修饰(name mangling) 
3. 初始化时调用 setter
class person:
    def __init__(self, age):
        self.age = age  # 调用 setter 进行验证
        
    @property
    def age(self):
        return self._age
        
    @age.setter
    def age(self, value):
        if value < 0:
            raise valueerror("年龄不能为负数")
        self._age = value
七、总结
| 场景 | 推荐实现方式 | 
|---|---|
| 简单属性 | 直接使用公有属性 | 
| 需要类型检查 / 验证 | 使用 property 装饰器 | 
| 只读属性 | 只实现 getter 方法 | 
| 计算属性 | 使用 property 装饰器 | 
| 复杂的属性访问控制 | 自定义描述符类 | 
合理使用 property 可以提高代码的安全性、可维护性和可读性,尤其在需要控制属性访问逻辑的场景中表现出色。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
            
                                            
                                            
                                            
                                            
                                            
                                            
发表评论