属性(Property)是Python中一种特殊的属性访问机制,它允许你将方法"伪装"成属性,从而实现对数据的优雅访问和控制。
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
"""Getter方法 - 访问属性时调用"""
return self._name
@name.setter
def name(self, value):
"""Setter方法 - 设置属性时调用"""
if not value:
raise ValueError("姓名不能为空")
self._name = value
@name.deleter
def name(self):
"""Deleter方法 - 删除属性时调用"""
print("删除姓名")
del self._name
# 使用示例
p = Person("张三")
print(p.name) # 像访问属性一样调用getter
p.name = "李四" # 像设置属性一样调用setter
del p.name # 像删除属性一样调用deleter
class Person1:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
if not value:
raise ValueError("姓名不能为空")
self._name = value
# 缺点:调用方式不直观 p.get_name(), p.set_name("xxx")
class Person2:
def __init__(self, name):
self._name = name
def get_name(self):
return f"姓名: {self._name}"
def set_name(self, value):
if not value:
raise ValueError("姓名不能为空")
self._name = value
def del_name(self):
print(f"删除 {self._name}")
del self._name
# 使用property函数
name = property(get_name, set_name, del_name, "姓名属性文档")
class Person3:
def __init__(self, name):
self._name = name
@property
def name(self):
"""姓名属性"""
return self._name
@name.setter
def name(self, value):
if not value:
raise ValueError("姓名不能为空")
self._name = value
@name.deleter
def name(self):
print(f"删除 {self._name}")
del self._name
class ValidatedAttribute:
"""自定义描述符类"""
def __init__(self, validator=None):
self.validator = validator
self.data_name = None
def __set_name__(self, owner, name):
"""设置属性名称"""
self.data_name = f"_{name}"
def __get__(self, obj, objtype=None):
"""获取属性"""
if obj is None:
return self
return getattr(obj, self.data_name, None)
def __set__(self, obj, value):
"""设置属性"""
if self.validator:
value = self.validator(value)
setattr(obj, self.data_name, value)
def __delete__(self, obj):
"""删除属性"""
delattr(obj, self.data_name)
def validate_age(age):
"""年龄验证器"""
if not isinstance(age, int):
raise TypeError("年龄必须是整数")
if age < 0 or age > 150:
raise ValueError("年龄必须在0-150之间")
return age
class Person4:
name = ValidatedAttribute() # 使用自定义描述符
age = ValidatedAttribute(validate_age)
def __init__(self, name, age):
self.name = name
self.age = age
class Circle:
def __init__(self, radius):
self._radius = radius
self._area = None
@property
def radius(self):
return self._radius
@property
def area(self):
"""计算面积 - 只读属性"""
if self._area is None:
self._area = 3.14159 * self._radius ** 2
return self._area
@property
def diameter(self):
"""计算直径 - 只读属性"""
return 2 * self._radius
# 使用
c = Circle(5)
print(f"半径: {c.radius}")
print(f"面积: {c.area}")
print(f"直径: {c.diameter}")
# c.area = 100 # 错误:只读属性不能设置
import time
class ExpensiveComputation:
def __init__(self):
self._result = None
@property
def result(self):
"""延迟计算 - 第一次访问时才计算"""
if self._result is None:
print("正在进行复杂计算...")
time.sleep(2) # 模拟耗时操作
self._result = 42 # 计算结果
return self._result
# 使用
calc = ExpensiveComputation()
print("创建对象...")
print(f"结果: {calc.result}") # 第一次访问时计算
print(f"结果: {calc.result}") # 直接返回缓存结果
class Temperature:
def __init__(self, celsius):
self.celsius = celsius # 使用setter
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if not isinstance(value, (int, float)):
raise TypeError("温度必须是数字")
if value < -273.15:
raise ValueError("温度不能低于绝对零度(-273.15°C)")
self._celsius = value
self._fahrenheit = None # 清除缓存
@property
def fahrenheit(self):
"""华氏温度 - 自动转换"""
if self._fahrenheit is None:
self._fahrenheit = self._celsius * 9/5 + 32
return self._fahrenheit
@fahrenheit.setter
def fahrenheit(self, value):
"""通过华氏温度设置摄氏温度"""
self.celsius = (value - 32) * 5/9
# 使用
t = Temperature(25)
print(f"摄氏: {t.celsius}°C")
print(f"华氏: {t.fahrenheit}°F")
t.fahrenheit = 77 # 设置华氏温度
print(f"设置77°F后,摄氏: {t.celsius}°C")
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
self._area = None
self._perimeter = None
@property
def width(self):
return self._width
@width.setter
def width(self, value):
self._width = value
self._invalidate_cache()
@property
def height(self):
return self._height
@height.setter
def height(self, value):
self._height = value
self._invalidate_cache()
def _invalidate_cache(self):
"""清空缓存的计算结果"""
self._area = None
self._perimeter = None
@property
def area(self):
"""面积 - 依赖于width和height"""
if self._area is None:
self._area = self._width * self._height
return self._area
@property
def perimeter(self):
"""周长 - 依赖于width和height"""
if self._perimeter is None:
self._perimeter = 2 * (self._width + self._height)
return self._perimeter
# 使用
r = Rectangle(5, 10)
print(f"面积: {r.area}, 周长: {r.perimeter}")
r.width = 8 # 修改宽度会自动清除缓存
print(f"新面积: {r.area}, 新周长: {r.perimeter}")
class User:
def __init__(self, username, email, age):
self.username = username
self.email = email
self.age = age
@property
def username(self):
return self._username
@username.setter
def username(self, value):
if not value:
raise ValueError("用户名不能为空")
if len(value) < 3:
raise ValueError("用户名至少3个字符")
if len(value) > 20:
raise ValueError("用户名最多20个字符")
self._username = value
@property
def email(self):
return self._email
@email.setter
def email(self, value):
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, value):
raise ValueError("邮箱格式不正确")
self._email = value
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError("年龄必须是整数")
if value < 0:
raise ValueError("年龄不能为负数")
if value > 150:
raise ValueError("年龄不能超过150")
self._age = value
@property
def is_adult(self):
"""计算属性 - 是否成年"""
return self._age >= 18
# 使用
try:
user = User("john_doe", "john@example.com", 25)
print(f"用户: {user.username}, 成年: {user.is_adult}")
user.email = "invalid-email" # 会抛出异常
except ValueError as e:
print(f"错误: {e}")
class APIResponse:
"""封装API响应数据"""
def __init__(self, data, status_code=200):
self._data = data
self._status_code = status_code
@property
def data(self):
"""原始数据"""
return self._data
@property
def status_code(self):
"""状态码"""
return self._status_code
@property
def is_success(self):
"""是否成功"""
return 200 <= self._status_code < 300
@property
def is_error(self):
"""是否错误"""
return self._status_code >= 400
@property
def formatted_data(self):
"""格式化后的数据"""
if isinstance(self._data, dict):
return {k: v for k, v in self._data.items() if v is not None}
return self._data
@property
def message(self):
"""响应消息"""
if self.is_success:
return "请求成功"
elif self._status_code == 404:
return "资源未找到"
elif self._status_code == 500:
return "服务器内部错误"
else:
return "请求失败"
# 使用
response = APIResponse({"name": "Alice", "age": 30, "email": None}, 200)
print(f"成功: {response.is_success}")
print(f"消息: {response.message}")
print(f"格式化数据: {response.formatted_data}")
class AppConfig:
"""应用配置管理"""
def __init__(self):
self._config = {}
self._cache = {}
def set(self, key, value):
"""设置配置项"""
self._config[key] = value
# 清除相关缓存
if key in self._cache:
del self._cache[key]
def get(self, key, default=None):
"""获取配置项"""
return self._config.get(key, default)
@property
def debug(self):
"""是否调试模式"""
return self.get('debug', False)
@debug.setter
def debug(self, value):
self.set('debug', bool(value))
@property
def database_url(self):
"""数据库URL"""
if 'database_url' not in self._cache:
host = self.get('db_host', 'localhost')
port = self.get('db_port', 5432)
name = self.get('db_name', 'app_db')
self._cache['database_url'] = f"postgresql://{host}:{port}/{name}"
return self._cache['database_url']
@property
def log_level(self):
"""日志级别"""
return self.get('log_level', 'INFO').upper()
@property
def settings_summary(self):
"""配置摘要"""
return {
'debug': self.debug,
'database': self.database_url,
'log_level': self.log_level
}
# 使用
config = AppConfig()
config.debug = True
config.set('db_host', '192.168.1.100')
print(f"数据库URL: {config.database_url}")
print(f"配置摘要: {config.settings_summary}")
class BankAccount:
def __init__(self, owner, balance=0):
self._owner = owner
self._balance = balance
self._transaction_count = 0
@property
def owner(self):
"""只读属性 - 账户所有者"""
return self._owner
@property
def balance(self):
"""余额 - 受保护的读取"""
return self._balance
@property
def transaction_count(self):
"""交易次数 - 只读统计"""
return self._transaction_count
def deposit(self, amount):
"""存款 - 通过方法控制"""
if amount <= 0:
raise ValueError("存款金额必须为正数")
self._balance += amount
self._transaction_count += 1
return self._balance
def withdraw(self, amount):
"""取款 - 通过方法控制"""
if amount <= 0:
raise ValueError("取款金额必须为正数")
if amount > self._balance:
raise ValueError("余额不足")
self._balance -= amount
self._transaction_count += 1
return self._balance
# 使用
account = BankAccount("张三", 1000)
print(f"余额: {account.balance}")
account.deposit(500)
print(f"存款后余额: {account.balance}")
print(f"交易次数: {account.transaction_count}")
class ObservableProperty:
"""可观察属性"""
def __init__(self, initial_value=None):
self._value = initial_value
self._observers = []
def __get__(self, obj, objtype=None):
if obj is None:
return self
return self._value
def __set__(self, obj, value):
old_value = self._value
self._value = value
self._notify_observers(obj, old_value, value)
def add_observer(self, observer):
"""添加观察者"""
self._observers.append(observer)
def _notify_observers(self, obj, old_value, new_value):
"""通知所有观察者"""
for observer in self._observers:
observer(obj, self, old_value, new_value)
class TemperatureMonitor:
temperature = ObservableProperty(20.0)
def __init__(self, name):
self.name = name
# 添加观察者
TemperatureMonitor.temperature.add_observer(self.on_temperature_change)
def on_temperature_change(self, obj, prop, old_value, new_value):
"""温度变化回调"""
print(f"[{self.name}] 温度从 {old_value}°C 变为 {new_value}°C")
if new_value > 30:
print(f"[{self.name}] 警告: 温度过高!")
elif new_value < 10:
print(f"[{self.name}] 警告: 温度过低!")
# 使用
monitor1 = TemperatureMonitor("监控器1")
monitor2 = TemperatureMonitor("监控器2")
print("设置温度到25°C:")
TemperatureMonitor.temperature = 25
print("\n设置温度到35°C:")
TemperatureMonitor.temperature = 35
优先使用属性而非公开字段
# 不好
class Person:
def __init__(self, age):
self.age = age # 直接公开,无法控制
# 好
class Person:
def __init__(self, age):
self._age = age # 保护字段
@property
def age(self):
return self._age
保持属性简单
# 避免在getter中做复杂操作
@property
def data(self):
# 可以有一些简单计算或缓存
if self._cached_data is None:
self._cached_data = self._load_data()
return self._cached_data
合理使用缓存
class Product:
def __init__(self, price, tax_rate):
self._price = price
self._tax_rate = tax_rate
self._price_with_tax = None
@property
def price(self):
return self._price
@price.setter
def price(self, value):
self._price = value
self._price_with_tax = None # 清除缓存
@property
def price_with_tax(self):
if self._price_with_tax is None:
self._price_with_tax = self._price * (1 + self._tax_rate)
return self._price_with_tax
不要滥用属性
# 避免 - 属性应该像属性,不应该有副作用
@property
def save_to_database(self):
# 这会让人困惑,应该用方法
self._db.save(self)
# 应该使用方法
def save_to_database(self):
self._db.save(self)
性能考虑
# 避免每次访问都进行复杂计算
@property
def expensive_calculation(self):
# 如果没有缓存,每次访问都会计算
return self._do_expensive_calculation()
# 使用缓存
@property
def expensive_calculation(self):
if self._cached_result is None:
self._cached_result = self._do_expensive_calculation()
return self._cached_result
继承中的属性
class Base:
def __init__(self):
self._value = 0
@property
def value(self):
return self._value
@value.setter
def value(self, val):
self._value = val
class Derived(Base):
@Base.value.setter # 正确引用父类的property
def value(self, val):
if val < 0:
raise ValueError("值不能为负")
super(Derived, Derived).value.__set__(self, val)
class DebuggablePerson:
def __init__(self, name):
self._name = name
print(f"初始化: name={name}")
@property
def name(self):
print(f"获取name: {self._name}")
return self._name
@name.setter
def name(self, value):
print(f"设置name: {self._name} -> {value}")
self._name = value
@name.deleter
def name(self):
print(f"删除name: {self._name}")
del self._name
# 使用
p = DebuggablePerson("张三")
print(p.name) # 会打印获取日志
p.name = "李四" # 会打印设置日志
import unittest
class TestPersonProperties(unittest.TestCase):
def test_name_property(self):
"""测试姓名属性"""
p = Person("张三")
# 测试getter
self.assertEqual(p.name, "张三")
# 测试setter
p.name = "李四"
self.assertEqual(p.name, "李四")
# 测试验证
with self.assertRaises(ValueError):
p.name = "" # 空姓名应该抛出异常
# 测试deleter
with self.assertRaises(AttributeError):
del p.name
_ = p.name # 应该抛出AttributeError
if __name__ == "__main__":
unittest.main()
Python属性是一个强大的工具,可以让你:
封装数据:保护对象内部状态 添加验证:确保数据一致性 延迟计算:优化性能 创建计算属性:基于其他属性动态计算 实现观察者模式:属性变化时通知其他对象 保持向后兼容:将字段转换为属性而不改变API记住关键原则:属性应该让对象看起来有简单的属性访问语法,同时在后台执行必要的逻辑。合理使用属性可以让你的代码更加Pythonic、安全和易维护。