@property装饰器是一个强大而灵活的工具,它允许我们将方法调用伪装成属性访问,从而实现更自然、更安全的对象操作。除了基本的getter/setter功能,@property还有许多高级用法可以显著提升代码质量。本文将深入探讨10种高级用法,涵盖从基础封装到元编程的各个层面。场景:当某个属性的值需要基于其他属性计算得出,且计算成本较高时,可以使用@property结合缓存机制。
代码示例:
fromfunctoolsimportlru_cache
classCircle:
def__init__(self, radius):
self._radius = radius
self._diameter = None # 缓存直径
@property
defradius(self):
returnself._radius
@radius.setter
defradius(self, value):
self._radius = value
self._diameter = None # 清除缓存
@property
@lru_cache(maxsize=1)
defarea(self):
"""计算并缓存圆的面积"""
print("计算面积中...")
return3.14159*self.radius**2
@property
defdiameter(self):
"""惰性计算直径"""
ifself._diameterisNone:
print("计算直径中...")
self._diameter = 2*self.radius
returnself._diameter
# 使用示例
circle = Circle(5)
print(circle.area) # 第一次计算
print(circle.area) # 从缓存读取
circle.radius = 10
print(circle.area) # 重新计算
print(circle.diameter) # 惰性计算场景:当多个属性之间存在依赖关系,一个属性变化需要自动更新其他相关属性时。
代码示例:
classRectangle:
def__init__(self, width, height):
self._width = width
self._height = height
self._area = width*height
self._perimeter = 2* (width+height)
@property
defwidth(self):
returnself._width
@width.setter
defwidth(self, value):
self._width = value
self._update_derived_properties()
@property
defheight(self):
returnself._height
@height.setter
defheight(self, value):
self._height = value
self._update_derived_properties()
def_update_derived_properties(self):
"""更新所有衍生属性"""
self._area = self._width*self._height
self._perimeter = 2* (self._width+self._height)
@property
defarea(self):
returnself._area
@property
defperimeter(self):
returnself._perimeter
# 使用示例
rect = Rectangle(4, 5)
print(f"面积: {rect.area}, 周长: {rect.perimeter}")
rect.width = 6
print(f"更新后面积: {rect.area}, 周长: {rect.perimeter}")场景:需要定义类级别的常量或只读属性,防止意外修改。
代码示例:
classConfiguration:
def__init__(self):
self._api_key = "sk-1234567890abcdef"
self._max_retries = 3
@property
defapi_key(self):
"""只读API密钥"""
return"***"+self._api_key[-4:] # 部分隐藏
@property
defmax_retries(self):
"""只读最大重试次数"""
returnself._max_retries
@property
defapi_endpoint(self):
"""计算得出的只读属性"""
returnf"https://api.example.com/v1?key={self._api_key}"
@property
defVERSION(self):
"""类常量"""
return"1.0.0"
# 使用示例
config = Configuration()
print(f"API端点: {config.api_endpoint}")
print(f"版本: {config.VERSION}")
# config.api_key = "new_key" # 会报错,因为没有setter场景:需要在属性访问时自动进行类型转换或格式化输出。
代码示例:
classProduct:
def__init__(self, name, price_cents):
self.name = name
self._price_cents = price_cents
@property
defprice(self):
"""将分转换为元并格式化"""
returnf"¥{self._price_cents / 100:.2f}"
@price.setter
defprice(self, value):
"""接受字符串或数字,统一转换为分"""
ifisinstance(value, str):
# 移除货币符号和逗号
value = value.replace('¥', '').replace(',', '').strip()
self._price_cents = int(float(value) *100)
elifisinstance(value, (int, float)):
self._price_cents = int(value*100)
else:
raiseTypeError("价格必须是数字或字符串")
@property
defprice_details(self):
"""返回详细的价格信息"""
yuan = self._price_cents//100
cents = self._price_cents%100
return {
'total_cents': self._price_cents,
'yuan': yuan,
'cents': cents,
'formatted': self.price
}
# 使用示例
product = Product("笔记本电脑", 599900)
print(f"价格: {product.price}")
product.price = "7999.99"
print(f"新价格: {product.price}")
print(f"价格详情: {product.price_details}")场景:需要记录属性的访问和修改历史,用于调试或审计。
代码示例:
importtime
fromfunctoolsimportwraps
deflog_access(func):
"""装饰器:记录属性访问"""
@wraps(func)
defwrapper(self):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] 访问属性: {func.__name__}")
returnfunc(self)
returnwrapper
deflog_modification(func):
"""装饰器:记录属性修改"""
@wraps(func)
defwrapper(self, value):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] 修改属性: {func.__name__} = {value}")
returnfunc(self, value)
returnwrapper
classBankAccount:
def__init__(self, owner, initial_balance=0):
self.owner = owner
self._balance = initial_balance
self._transaction_log = []
@property
@log_access
defbalance(self):
returnself._balance
@balance.setter
@log_modification
defbalance(self, value):
old_balance = self._balance
self._balance = value
self._transaction_log.append({
'timestamp': time.time(),
'old': old_balance,
'new': value,
'type': 'BALANCE_UPDATE'
})
@property
deftransaction_history(self):
"""只读交易历史"""
returnself._transaction_log.copy()
# 使用示例
account = BankAccount("张三", 1000)
print(f"余额: {account.balance}")
account.balance = 1500
account.balance = 1200
print(f"交易记录: {account.transaction_history}")场景:需要确保属性值符合特定的业务规则或约束条件。
代码示例:
classUser:
def__init__(self, username, email, age):
self._username = None
self._email = None
self._age = None
self.username = username # 使用setter进行初始化验证
self.email = email
self.age = age
@property
defusername(self):
returnself._username
@username.setter
defusername(self, value):
ifnotvalue:
raiseValueError("用户名不能为空")
iflen(value) <3:
raiseValueError("用户名至少3个字符")
iflen(value) >20:
raiseValueError("用户名最多20个字符")
ifnotvalue.isalnum():
raiseValueError("用户名只能包含字母和数字")
self._username = value
@property
defemail(self):
returnself._email
@email.setter
defemail(self, value):
ifnotvalue:
raiseValueError("邮箱不能为空")
if'@'notinvalue:
raiseValueError("邮箱格式不正确")
self._email = value
@property
defage(self):
returnself._age
@age.setter
defage(self, value):
ifnotisinstance(value, int):
raiseTypeError("年龄必须是整数")
ifvalue<0:
raiseValueError("年龄不能为负数")
ifvalue>150:
raiseValueError("年龄不能超过150")
self._age = value
@property
defis_adult(self):
"""计算属性:是否成年"""
returnself._age>= 18
@property
defage_group(self):
"""计算属性:年龄分组"""
ifself._age<13:
return"儿童"
elifself._age<20:
return"青少年"
elifself._age<65:
return"成人"
else:
return"长者"
# 使用示例
try:
user = User("john_doe", "john@example.com", 25)
print(f"用户名: {user.username}")
print(f"是否成年: {user.is_adult}")
print(f"年龄分组: {user.age_group}")
user.age = 17 # 修改年龄
print(f"修改后是否成年: {user.is_adult}")
exceptValueErrorase:
print(f"错误: {e}")场景:需要为属性提供多个名称,或重构时保持向后兼容性。
代码示例:
classEmployee:
def__init__(self, first_name, last_name, salary):
self._first_name = first_name
self._last_name = last_name
self._salary = salary
# 主要属性
@property
deffirst_name(self):
returnself._first_name
@first_name.setter
deffirst_name(self, value):
self._first_name = value
@property
deflast_name(self):
returnself._last_name
@last_name.setter
deflast_name(self, value):
self._last_name = value
# 别名属性(保持向后兼容)
@property
defgiven_name(self):
"""first_name的别名"""
returnself.first_name
@given_name.setter
defgiven_name(self, value):
self.first_name = value
@property
deffamily_name(self):
"""last_name的别名"""
returnself.last_name
@family_name.setter
deffamily_name(self, value):
self.last_name = value
# 计算属性
@property
deffull_name(self):
returnf"{self.first_name} {self.last_name}"
@full_name.setter
deffull_name(self, value):
"""通过全名设置姓和名"""
if' 'invalue:
first, last = value.split(' ', 1)
self.first_name = first
self.last_name = last
else:
self.first_name = value
self.last_name = ""
@property
defannual_salary(self):
"""月薪的别名"""
returnself._salary*12
@annual_salary.setter
defannual_salary(self, value):
"""通过年薪设置月薪"""
self._salary = value/12
@property
defmonthly_salary(self):
returnself._salary
@monthly_salary.setter
defmonthly_salary(self, value):
self._salary = value
# 使用示例
emp = Employee("张", "三", 8000)
print(f"全名: {emp.full_name}")
print(f"月薪: {emp.monthly_salary}")
print(f"年薪: {emp.annual_salary}")
# 使用别名
emp.given_name = "李"
emp.family_name = "四"
print(f"新全名: {emp.full_name}")
# 通过全名设置
emp.full_name = "王 五"
print(f"姓: {emp.last_name}, 名: {emp.first_name}")场景:属性对应昂贵的资源(如数据库连接、文件内容),需要延迟加载和正确管理。
代码示例:
importsqlite3
importjson
frompathlibimportPath
classDataManager:
def__init__(self, db_path, config_path):
self.db_path = db_path
self.config_path = config_path
self._db_connection = None
self._config_data = None
self._cache = {}
@property
defdb_connection(self):
"""延迟加载数据库连接"""
ifself._db_connectionisNone:
print("建立数据库连接...")
self._db_connection = sqlite3.connect(self.db_path)
# 设置连接属性
self._db_connection.row_factory = sqlite3.Row
returnself._db_connection
@property
defconfig(self):
"""延迟加载配置文件"""
ifself._config_dataisNone:
print("加载配置文件...")
withopen(self.config_path, 'r', encoding='utf-8') asf:
self._config_data = json.load(f)
returnself._config_data
@property
defusers(self):
"""缓存用户数据"""
if'users'notinself._cache:
print("查询用户数据...")
cursor = self.db_connection.cursor()
cursor.execute("SELECT * FROM users")
self._cache['users'] = cursor.fetchall()
returnself._cache['users']
defclear_cache(self, key=None):
"""清除缓存"""
ifkey:
self._cache.pop(key, None)
else:
self._cache.clear()
print("缓存已清除")
defclose(self):
"""清理资源"""
ifself._db_connection:
self._db_connection.close()
self._db_connection = None
self._config_data = None
self._cache.clear()
print("资源已清理")
# 使用示例
manager = DataManager("example.db", "config.json")
print(f"配置项: {manager.config.get('app_name')}")
print(f"用户数量: {len(manager.users)}")
print(f"再次访问用户: {len(manager.users)}") # 从缓存读取
manager.clear_cache('users')
manager.close()场景:需要根据用户角色或权限控制属性的访问和修改。
代码示例:
classSecureDocument:
def__init__(self, content, owner, security_level=0):
self._content = content
self._owner = owner
self._security_level = security_level
self._access_log = []
def_check_permission(self, user, required_level):
"""检查用户权限"""
ifuser.role == 'admin':
returnTrue
ifuser.role == 'owner'anduser.username == self._owner:
returnTrue
returnuser.clearance_level>= required_level
def_log_access(self, user, action):
"""记录访问日志"""
self._access_log.append({
'user': user.username,
'action': action,
'timestamp': time.time()
})
@property
defcontent(self):
"""受保护的content属性"""
# 这里需要传入user对象,实际应用中可能从上下文获取
raiseAttributeError("请使用get_content(user)方法")
defget_content(self, user):
"""安全的内容获取方法"""
ifnotself._check_permission(user, self._security_level):
raisePermissionError(f"用户 {user.username} 没有权限访问此文档")
self._log_access(user, 'READ')
returnself._content
@property
defmetadata(self):
"""公开的元数据"""
return {
'owner': self._owner,
'security_level': self._security_level,
'access_count': len(self._access_log)
}
@property
defsummary(self):
"""公开的摘要(低安全级别)"""
iflen(self._content) >100:
returnself._content[:100] +"..."
returnself._content
defset_content(self, user, new_content):
"""安全的内容设置方法"""
ifuser.username!= self._owneranduser.role!= 'admin':
raisePermissionError("只有所有者或管理员可以修改内容")
self._log_access(user, 'WRITE')
self._content = new_content
classUser:
def__init__(self, username, role='user', clearance_level=0):
self.username = username
self.role = role
self.clearance_level = clearance_level
# 使用示例
doc = SecureDocument("这是一份机密文档内容...", "admin", security_level=2)
user1 = User("alice", role="user", clearance_level=1)
user2 = User("admin", role="admin", clearance_level=3)
print(f"文档元数据: {doc.metadata}")
print(f"文档摘要: {doc.summary}")
try:
content = doc.get_content(user1)
print(f"用户 {user1.username} 获取的内容: {content}")
exceptPermissionErrorase:
print(f"权限错误: {e}")
try:
content = doc.get_content(user2)
print(f"用户 {user2.username} 获取的内容: {content}")
exceptPermissionErrorase:
print(f"权限错误: {e}")场景:需要动态创建具有特定行为的property装饰器,或批量处理类属性。
代码示例:
defvalidated_property(validator_func, error_message=None):
"""创建带有验证的property装饰器工厂"""
defdecorator(func):
@property
defwrapper(self):
returnfunc(self)
@wrapper.setter
defwrapper(self, value):
ifnotvalidator_func(value):
iferror_message:
raiseValueError(error_message)
else:
raiseValueError(f"值 {value} 未通过验证")
# 调用原始的setter或直接设置属性
ifhasattr(func, '__set__'):
func.__set__(self, value)
else:
# 如果没有setter,直接设置私有属性
private_name = '_'+func.__name__
setattr(self, private_name, value)
returnwrapper
returndecorator
defrange_validator(min_val, max_val):
"""范围验证器"""
defvalidator(value):
returnmin_val<= value<= max_val
returnvalidator
defregex_validator(pattern):
"""正则表达式验证器"""
importre
defvalidator(value):
returnbool(re.match(pattern, value))
returnvalidator
classProduct:
def__init__(self, name, price, quantity):
self.name = name
self._price = price
self._quantity = quantity
# 使用装饰器工厂创建验证属性
@validated_property(
validator_func=range_validator(0, 10000),
error_message="价格必须在0-10000之间"
)
defprice(self):
returnself._price
@price.setter
defprice(self, value):
self._price = value
@validated_property(
validator_func=lambdax: isinstance(x, int) andx>= 0,
error_message="数量必须是非负整数"
)
defquantity(self):
returnself._quantity
@quantity.setter
defquantity(self, value):
self._quantity = value
@property
deftotal_value(self):
returnself.price*self.quantity
# 动态为类添加property
defadd_timestamp_property(cls):
"""为类添加时间戳属性"""
@property
deftimestamp(self):
importtime
returntime.time()
cls.timestamp = timestamp
returncls
@add_timestamp_property
classDynamicClass:
def__init__(self, data):
self.data = data
# 使用示例
product = Product("手机", 2999, 10)
print(f"产品总价值: {product.total_value}")
try:
product.price = 15000 # 会触发验证错误
exceptValueErrorase:
print(f"验证错误: {e}")
product.quantity = 20
print(f"新数量: {product.quantity}, 新总价值: {product.total_value}")
dynamic_obj = DynamicClass("测试数据")
print(f"动态添加的时间戳: {dynamic_obj.timestamp}")通过这10种高级用法,我们可以看到@property装饰器在Python中的强大灵活性。从基本的属性封装到复杂的元编程应用,@property都能帮助我们编写更安全、更优雅、更高效的代码。
建议:
@property,简单的公共属性直接暴露即可掌握这些高级用法,你将能更好地利用Python的面向对象特性,构建更健壮、更易维护的应用程序。
“无他,惟手熟尔”!有需要的用起来!
如果你觉得这篇文章有用,欢迎点赞、转发、收藏、留言、推荐❤!
本文分享自 Nicholas与Pypi 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!