property 是 Python 的一个内置函数,它允许你将类的方法“伪装”成类的属性,这意味着你可以像访问普通属性一样访问一个方法,但在后台,实际上是调用了这个方法,这让你能够在不改变外部调用方式的情况下,为属性的获取、设置和删除添加额外的逻辑,例如数据验证、计算或触发事件。

为什么需要 property?
想象一个简单的场景:你有一个 Student 类,每个学生都有一个年龄,你希望确保年龄不能是负数。
没有 property 的实现方式:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# 创建一个学生
s1 = Student("Alice", 20)
print(s1.age) # 输出: 20
# 问题:可以直接修改 age 为负数,这不符合业务逻辑
s1.age = -5
print(s1.age) # 输出: -5,这显然是错误的!
为了解决这个问题,你可能会想:写一个 set_age 方法。
class Student:
def __init__(self, name, age):
self.name = name
self._age = age # 使用下划线表示这是一个“受保护的”内部变量
def set_age(self, new_age):
if new_age < 0:
print("年龄不能为负数!")
return
self._age = new_age
def get_age(self):
return self._age
s1 = Student("Alice", 20)
s1.set_age(25)
print(s1.get_age()) # 输出: 25
s1.set_age(-5) # 输出: 年龄不能为负数!
print(s1.get_age()) # 输出: 25
这种方式虽然能解决问题,但调用方式变得不直观:s1.set_age(25) 而不是 s1.age = 25,如果代码量很大,到处都是 get 和 set 方法,会显得很冗余。

这时,property 就派上用场了。
property 的基本用法
property 通常通过装饰器(decorator)语法来实现,这样更简洁、更 Pythonic。
使用装饰器语法(推荐)
这是最常见和最清晰的方式。
class Student:
def __init__(self, name, age):
self.name = name
# 注意:这里我们直接调用 setter 方法来设置初始值
self.age = age
@property
def age(self):
"""这是一个 getter 方法,通过 @property 装饰器变成 'age' 属性"""
print("正在获取年龄...")
return self._age # 返回内部存储的值
@age.setter
def age(self, value):
"""这是一个 setter 方法,通过 @age.setter 装饰器与 'age' 属性关联"""
print(f"正在设置年龄为 {value}...")
if not isinstance(value, int):
raise TypeError("年龄必须是整数")
if value < 0:
raise ValueError("年龄不能为负数")
self._age = value
@age.deleter
def age(self):
"""这是一个 deleter 方法,通过 @age.deleter 装饰器与 'age' 属性关联"""
print("正在删除年龄...")
del self._age
# --- 使用示例 ---
s1 = Student("Alice", 20)
# 1. 获取值 (调用 getter)
print(s1.age)
# 输出:
# 正在获取年龄...
# 20
# 2. 设置值 (调用 setter)
s1.age = 25
# 输出:
# 正在设置年龄为 25...
print(s1.age)
# 输出:
# 正在获取年龄...
# 25
# 3. 验证逻辑
try:
s1.age = -5
except ValueError as e:
print(e)
# 输出:
# 正在设置年龄为 -5...
# 年龄不能为负数
# 4. 删除属性 (调用 deleter)
del s1.age
# 输出:
# 正在删除年龄...
# 再次访问会报错,因为 _age 已经被删除
try:
print(s1.age)
except AttributeError as e:
print(e)
# 输出:
# 正在获取年龄...
# 'Student' object has no attribute '_age'
代码解析:

@property: 将age方法转换成一个名为age的只读属性,当你访问s1.age时,这个方法会被调用。@age.setter: 将age方法的 setter 版本与age属性关联,当你给s1.age赋值时(如s1.age = 25),这个方法会被调用。@age.deleter: 将age方法的 deleter 版本与age属性关联,当你使用del s1.age时,这个方法会被调用。_age: 这是一个命名约定,表示这是一个“内部”或“受保护”的变量,用于实际存储数据,外部代码应该通过property来访问它,而不是直接访问_age。
使用 property() 函数(非装饰器语法)
你也可以不使用装饰器,而是直接在类中调用 property() 函数,这种方式在旧代码或某些特定场景下可能会见到。
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def get_temperature(self):
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("温度不能低于绝对零度")
self._temperature = value
# 使用 property() 函数创建属性
temperature = property(get_temperature, set_temperature)
c = Celsius(10)
print(c.temperature) # 调用 get_temperature
c.temperature = 37 # 调用 set_temperature
print(c.temperature)
try:
c.temperature = -300
except ValueError as e:
print(e)
property(fget, fset, fdel, doc) 的参数分别是:
fget: 获取属性的函数 (getter)。fset: 设置属性的函数 (setter)。fdel: 删除属性的函数 (deleter)。doc: 属性的文档字符串。
装饰器语法的优势在于代码组织更清晰,getter, setter, deleter 都围绕同一个属性名,可读性更强。
property 的典型应用场景
-
数据验证 这是最常见的用途,确保赋给属性的值是合法的,如上面的年龄例子,或者确保邮箱格式正确、密码长度足够等。
-
计算属性(Computed Attributes) 当一个属性的值需要根据其他属性动态计算时,使用
property可以让访问方式像普通属性一样。class Circle: def __init__(self, radius): self.radius = radius @property def diameter(self): """计算直径,这是一个只读属性""" print("计算直径...") return self.radius * 2 @property def area(self): """计算面积,这也是一个只读属性""" print("计算面积...") return 3.14159 * self.radius ** 2 c = Circle(5) print(f"半径: {c.radius}") print(f"直径: {c.diameter}") # 访问时自动计算 print(f"面积: {c.area}") # 访问时自动计算 # 输出: # 半径: 5 # 计算直径... # 直径: 10 # 计算面积... # 面积: 78.53975注意:
diameter和area是只读的,因为我们没有为它们定义setter。 -
延迟计算 对于计算成本很高的属性,可以在第一次访问时计算并缓存结果,后续访问直接返回缓存值。
-
维护类的内部一致性 当一个类的多个属性需要保持同步时,
property非常有用。class BankAccount: def __init__(self, owner, balance): self.owner = owner self._balance = balance self._transactions = [] @property def balance(self): return self._balance @balance.setter def balance(self, value): if value < 0: raise ValueError("余额不能为负") self._transactions.append(f"余额更新: {self._balance} -> {value}") self._balance = value account = BankAccount("Alice", 1000) account.balance = 1500 # 触发 setter,同时记录交易 account.balance = 1200 print(account._transactions) # 输出: ['余额更新: 1000 -> 1500', '余额更新: 1500 -> 1200']
| 特性 | 描述 |
|---|---|
| 核心作用 | 将方法封装成属性,实现“受控”的访问。 |
| 主要优势 | 保持接口简洁(像访问普通属性一样),同时可以添加复杂的内部逻辑(验证、计算等)。 |
| 三种方法 | getter (@property), setter (@<attr>.setter), deleter (@<attr>.deleter)。 |
| 命名约定 | 通常使用一个下划线前缀(如 _value)作为内部存储变量,以区分通过 property 公开的属性。 |
| 最佳实践 | 优先使用装饰器语法,因为它更清晰、更符合 Python 的风格。 |
property 是 Python 面向对象编程中一个非常重要且强大的工具,它完美地体现了 Python "简洁而强大" 的哲学。
