是 Python 的一个内置装饰器,常用于定义一个类的方法,并将其伪装成“属性”。
- 保护类的封装特性
- 让开发者可以使用“对象.属性”的方式操作操作类属性
通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。
语法格式
@property
def 方法名(self)
代码块
将方法伪装成属性
class Circle:
def __init__(self, radius):
self._radius = radius # 下划线表示私有属性
@property
def radius(self):
# 将该方法伪装成属性
return self._radius
# 实例化对象
c = Circle(5)
print(c.radius) # 访问 radius 属性,输出: 5
解释:
radius
是一个方法,但由于添加了@property
装饰器,它被当作属性来访问。- 访问
c.radius
时,不需要调用c.radius()
,因此代码更简洁。
为属性定义设置器(@property.setter)
默认情况下,使用 @property
定义的属性是“只读”的。如果需要对属性进行赋值,可以使用 @property.setter
。
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
# 实例化对象
c = Circle(5)
print(c.radius) # 访问 radius 属性,输出: 5
c.radius = 10 # 设置 radius 属性
print(c.radius) # 输出: 10
c.radius = -1 # 尝试赋值负值,抛出 ValueError
解释:
@radius.setter
为属性提供了一个设置值的入口。- 在设置
radius
时,代码会检查新值是否满足条件(如是否大于 0),从而保护属性值。
为属性定义删除器(@property.deleter)
如果需要删除某个属性,可以使用 @property.deleter
。
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
@radius.deleter
def radius(self):
print("Deleting radius...")
self._radius = None
# 实例化对象
c = Circle(5)
print(c.radius) # 输出: 5
del c.radius # 删除 radius 属性
print(c.radius) # 输出: None
解释:
@radius.deleter
定义了删除属性时的行为。del c.radius
会触发@radius.deleter
中的代码。
完整示例
import math
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""获取半径"""
return self._radius
@radius.setter
def radius(self, value):
"""设置半径,必须是正数"""
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
@property
def area(self):
"""动态计算面积"""
return math.pi * self._radius ** 2
@property
def circumference(self):
"""动态计算周长"""
return 2 * math.pi * self._radius
# 创建 Circle 对象
c = Circle(5)
# 获取属性
print("Radius:", c.radius) # 半径: 5
print("Area:", c.area) # 面积: 78.53981633974483
print("Circumference:", c.circumference) # 周长: 31.41592653589793
# 设置属性
c.radius = 10
print("New Radius:", c.radius) # 新半径: 10
print("New Area:", c.area) # 新面积: 314.1592653589793
# 尝试设置不合法的值
# c.radius = -5 # 会抛出 ValueError
特点:
@property
实现了动态计算:
area
和circumference
是基于radius
动态计算的属性,没有直接存储,而是在每次访问时实时计算。@radius.setter
对radius
的赋值进行了验证,确保其始终为正值。- 使用这些装饰器,隐藏了底层实现,提供了良好的接口。
常见应用场景
-
动态计算属性值:
使用@property
定义某些不需要显式存储、但可以通过计算得到的值。例如:- 举例:
self.area
在每次访问时动态计算,而不是存储在内存中。 - 圆的面积、周长。
- 举例:
-
只读属性:
一些属性不允许直接修改,可以通过@property
实现只读行为。例如,用于模型的某些统计数据。 -
验证输入:
对属性赋值时,利用@property.setter
检查新值是否合法,防止数据异常。 -
延迟加载:
某些资源或对象需要延迟加载(即仅在需要时加载),可以通过@property
动态创建这些对象。例如:- 模型的动态初始化
- 数据库连接
不用@property的实现
import math
class Circle:
def __init__(self, radius):
self._radius = radius # 私有属性,外部不可直接访问
# Getter 方法
def get_radius(self):
"""获取半径"""
return self._radius
# Setter 方法
def set_radius(self, value):
"""设置半径,必须是正数"""
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
# 计算面积
def get_area(self):
"""动态计算面积"""
return math.pi * self._radius ** 2
# 计算周长
def get_circumference(self):
"""动态计算周长"""
return 2 * math.pi * self._radius
# 创建 Circle 对象
c = Circle(5)
# 获取半径
print("Radius:", c.get_radius()) # 半径: 5
# 计算面积
print("Area:", c.get_area()) # 面积: 78.53981633974483
# 计算周长
print("Circumference:", c.get_circumference()) # 周长: 31.41592653589793
# 设置新的半径
c.set_radius(10)
print("New Radius:", c.get_radius()) # 新半径: 10
print("New Area:", c.get_area()) # 新面积: 314.1592653589793
# 尝试设置不合法的值
# c.set_radius(-5) # 会抛出 ValueError: Radius must be positive
重要应用——延时加载
- 某些对象或资源(如文件、数据库连接、大型对象等)只有在第一次被访问时才会被加载和初始化。(文件的内容只有在真正需要访问时才被加载。)
- 这样可以避免程序启动时加载所有内容而浪费资源或时间。
应用场景——加载大型的数据集(如机器学习中的数据预处理)
class LargeDataset:
def __init__(self, data_source):
self.data_source = data_source
self._data = None # 大型数据尚未加载
@property
def data(self):
"""延迟加载数据"""
if self._data is None: # 如果数据未加载
print("Loading large dataset...") # 模拟数据加载
self._data = self._load_data(self.data_source)
return self._data
def _load_data(self, source):
"""模拟数据加载的内部方法"""
return [i for i in range(1000000)] # 生成一个大数据集
# 使用数据集加载器
dataset = LargeDataset("dummy_source")
# 数据尚未加载
print("Dataset is not loaded yet.")
# 第一次访问触发加载
print(len(dataset.data)) # 输出: 1000000
# 再次访问直接返回已加载的数据
print(len(dataset.data)) # 输出: 1000000
更多解释可以参考:Python @property装饰器详解 - 贾志文 - 博客园