属性装饰器 property
property 是 Python 中一种强大的机制,它把方法"伪装"成属性访问。通过 property,可以在读取、设置、删除属性时执行自定义逻辑,同时保持简洁的语法。
⚠️ property 只在新式类(继承 object)中工作。旧式类中 property 不会拦截属性访问。
基本用法
class Circle(object):
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 cannot be negative")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
c = Circle(5)
print c.radius # 5,像访问属性一样调用 getter
print c.area # 78.53975
c.radius = 10 # 像赋值一样调用 setter
print c.area # 314.159
c.radius = -1 # ValueError: Radius cannot be negative
@property 把 radius() 方法变成属性访问。@radius.setter 把 radius() 方法变成属性赋值。area 只有 getter,是只读属性。
等价的非装饰器写法
class Circle(object):
def __init__(self, radius):
self._radius = radius
def get_radius(self):
return self._radius
def set_radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
def get_area(self):
return 3.14159 * self._radius ** 2
radius = property(get_radius, set_radius)
area = property(get_area)
property(fget, fset, fdel, doc) 是一个内置函数,接受 getter、setter、deleter 和文档字符串。装饰器语法只是它的语法糖。
实际应用场景
1. 数据验证:
class Temperature(object):
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9 / 5.0 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5 / 9.0
t = Temperature(25)
print t.fahrenheit # 77.0
t.fahrenheit = 98.6
print t.celsius # 37.0
2. 惰性计算:
class DataSet(object):
def __init__(self, data):
self._data = data
self._stats = None
@property
def stats(self):
if self._stats is None:
self._stats = {
"mean": sum(self._data) / float(len(self._data)),
"max": max(self._data),
"min": min(self._data),
}
return self._stats
ds = DataSet([1, 2, 3, 4, 5])
print ds.stats # 第一次计算
print ds.stats # 直接返回缓存结果
3. 向后兼容:
class Person(object):
def __init__(self, first_name, last_name):
self._first_name = first_name
self._last_name = last_name
@property
def full_name(self):
return "%s %s" % (self._first_name, self._last_name)
@full_name.setter
def full_name(self, value):
parts = value.split()
self._first_name = parts[0]
self._last_name = parts[1] if len(parts) > 1 else ""
p = Person("Alice", "Smith")
print p.full_name # Alice Smith
p.full_name = "Bob Jones"
print p._first_name # Bob
删除属性
class ManagedAttribute(object):
def __init__(self):
self._value = None
@property
def value(self):
if self._value is None:
raise AttributeError("Value not set")
return self._value
@value.setter
def value(self, val):
self._value = val
@value.deleter
def value(self):
self._value = None
obj = ManagedAttribute()
obj.value = 42
print obj.value # 42
del obj.value # 调用 deleter
print obj.value # AttributeError: Value not set
与旧式类的对比
旧式类中 property 不工作:
class OldCircle: # 旧式类!
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return 3.14 * self._radius ** 2
c = OldCircle(5)
print c.area # <property object>,不是计算结果!
旧式类中 property 只是返回 property 对象本身,不会拦截属性访问。这是始终使用新式类的又一个理由。