property 是 Python 的一个内置函数,它允许你以一种“面向对象”的方式,为类的属性添加 getter、setter 和 deleter 方法,这使得你可以控制对属性的访问,同时保持代码的简洁性和易用性,让外部调用者感觉就像在直接操作一个普通属性一样。

为什么需要 property?
想象一个场景:你有一个 Student 类,其中有一个 score 属性,你希望确保分数的值总是在 0 到 100 之间,不能为负数,也不能超过 100。
不使用 property 的方式(不够优雅):
class Student:
def __init__(self, name, score):
self.name = name
self._score = score # 使用下划线表示“内部使用”的变量
def set_score(self, value):
if not 0 <= value <= 100:
raise ValueError("Score must be between 0 and 100")
self._score = value
def get_score(self):
return self._score
# 使用方式
s = Student("Alice", 95)
# 想要修改分数,必须调用方法,而不是直接赋值
s.set_score(98)
print(s.get_score()) # 输出: 98
# 很容易忘记使用方法,直接赋值就破坏了规则
s._score = 101 # 这样做是允许的,但违背了设计初衷
print(s.get_score()) # 输出: 101,这显然是错误的
这种方式虽然可行,但不够“Pythonic”,使用者需要记住 get_score 和 set_score 这两个方法,而不是像使用普通属性一样直观。
使用 property 函数(优雅的方式)
property 函数可以让你将上述的 get_score 和 set_score 方法“伪装”成一个属性,这样,外部代码就可以像 s.score = 98 这样直接赋值,Python 会自动调用你定义的 setter 方法。

property 函数的基本用法如下:
property(fget=None, fset=None, fdel=None, doc=None)
fget: 获取属性值的函数 (getter)。fset: 设置属性值的函数 (setter)。fdel: 删除属性值的函数 (deleter)。doc: 属性的文档字符串。
示例:用 property 重构 Student 类
class Student:
def __init__(self, name, score):
self.name = name
self.score = score # 注意:这里会调用 setter 方法!
# 1. 定义 getter 方法
def get_score(self):
print("Getting score...")
return self._score
# 2. 定义 setter 方法
def set_score(self, value):
print("Setting score...")
if not isinstance(value, int):
raise TypeError("Score must be an integer.")
if not 0 <= value <= 100:
raise ValueError("Score must be between 0 and 100")
self._score = value
# 3. 使用 property 函数将它们绑定到 'score' 属性
score = property(get_score, set_score)
# --- 使用 ---
s = Student("Bob", 88)
# 获取值 (调用 get_score)
print(s.score)
# 输出:
# Getting score...
# 88
# 设置值 (调用 set_score)
s.score = 95
# 输出:
# Setting score...
# 尝试设置非法值
try:
s.score = 101
except ValueError as e:
print(e)
# 输出:
# Setting score...
# Score must be between 0 and 100
在这个例子中,s.score = 95 看起来是直接赋值,但实际上它调用了 set_score 方法。print(s.score) 看起来是直接访问,但实际上它调用了 get_score 方法,这完美地实现了“控制访问”和“语法简洁”的统一。
更现代的 @property 装饰器语法
虽然 property() 函数很强大,但它的语法(先定义方法,再用 property 绑定)有些冗长,从 Python 2.6 开始,引入了更简洁的 @property 装饰器语法,这是目前最推荐、最常用的方式。
装饰器语法将 getter、setter 和 deleter 方法更紧密地联系在一起。

示例:使用 @property 装饰器
class Student:
def __init__(self, name, score):
self.name = name
self.score = score # 同样,这里会调用 setter
# --- Getter ---
@property
def score(self):
"""This is the 'score' property."""
print("Getting score (using decorator)...")
return self._score
# --- Setter ---
# 注意:装饰器名称必须和 getter 一样!这里是 @score.setter
@score.setter
def score(self, value):
print("Setting score (using decorator)...")
if not isinstance(value, int):
raise TypeError("Score must be an integer.")
if not 0 <= value <= 100:
raise ValueError("Score must be between 0 and 100")
self._score = value
# --- Deleter (可选) ---
# 当执行 `del s.score` 时会调用此方法
@score.deleter
def score(self):
print("Deleting score...")
del self._score
# 或者 self._score = None
# --- 使用 ---
s = Student("Charlie", 76)
# 获取
print(s.score)
# 输出:
# Getting score (using decorator)...
# 76
# 设置
s.score = 82
# 输出:
# Setting score (using decorator)...
# 删除
del s.score
# 输出:
# Deleting score...
# 再次尝试获取会报错,因为 _score 已被删除
try:
print(s.score)
except AttributeError as e:
print(e)
# 输出:
# Getting score (using decorator)...
# 'Student' object has no attribute '_score'
@property 语法解析:
@property: 将下面的方法标记为属性的 "getter",这个方法名(这里是score)本身会成为属性的名称。@<property_name>.setter: 将下面的方法标记为属性的 "setter"。<property_name>必须和 getter 的方法名完全一致。@<property_name>.deleter: 将下面的方法标记为属性的 "deleter"。
这种语法更清晰,将所有与 score 属性相关的方法都放在了一起,可读性更高。
property 的典型应用场景
-
数据验证: 如上面的分数例子,确保赋给属性的值是有效的。
-
计算属性 (Read-only 属性): 创建一个没有 setter 的属性,它的值是通过其他属性计算得出的,它就像一个只读属性。
import math class Circle: def __init__(self, radius): self.radius = radius # 这会调用 radius 的 setter @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError("Radius cannot be negative") self._radius = value @property def area(self): """这是一个只读的计算属性""" return math.pi * self._radius ** 2 c = Circle(5) print(c.area) # 输出: 78.53981633974483 # c.area = 100 # 这会报错,因为没有 @area.setter -
属性修改时的“副作用”: 当一个属性被修改时,可能需要触发一些其他的操作,比如更新数据库、发送通知、重新计算相关数据等。
class LightBulb: def __init__(self): self._is_on = False @property def is_on(self): return self._is_on @is_on.setter def is_on(self, value): if self._is_on != value: if value: print("Light is ON!") else: print("Light is OFF.") self._is_on = value bulb = LightBulb() bulb.is_on = True # 输出: Light is ON! bulb.is_on = False # 输出: Light is OFF. bulb.is_on = False # 什么也不输出,因为状态没有改变
| 特性 | property() 函数 |
@property 装饰器 |
|---|---|---|
| 语法 | attr = property(fget, fset, fdel) |
@property @attr.setter @attr.deleter |
| 可读性 | 一般,getter/setter 方法分散 | 优秀,相关方法聚合在一起 |
| 推荐度 | 可用,但非首选 | 强烈推荐,现代 Python 的标准做法 |
| 核心思想 | 将方法转换为属性访问接口,实现对对象内部状态的受控访问,同时保持外部接口的简洁性。 |
property 的核心思想是 “封装”,它让你可以隐藏对象内部的实现细节,只提供一个干净、可控、易于理解的公共接口,这是面向对象编程中非常重要的一个概念。
