文章内容
2025/9/22 23:50:26,作 者: 黄兵
在 Python 中理解 @property / @<prop>.setter(详解 + 示例)
1) 基本概念(一句话版)
@property 把一个方法变成像属性一样访问的“访问器”。配合 @<prop>.setter 可以实现“读写受控的属性”。
它是 Python 的 描述符(descriptor) 的高阶用法。
2) 最简单的例子
class User:
def __init__(self, first, last):
self.first = first
self.last = last
@property
def full_name(self):
# 只读属性,像访问属性,但其实执行方法
return f"{self.first} {self.last}"
@full_name.setter
def full_name(self, value):
# 写入时会走这里:把传入字符串分解为 first/last
first, last = value.split(" ", 1)
self.first = first
self.last = last
u = User("John", "Doe")
print(u.full_name) # -> "John Doe" (调用 getter)
u.full_name = "Jane Smith" # -> 调用 setter
@property(getter)让 obj.full_name 调用函数结果而不是直接读字段。
@full_name.setter 定义写入逻辑:obj.full_name = "..." 会调用 setter。
3) property 的实现原理(重要细节)
-
property是描述符(descriptor)。它在类上是一个对象:property(fget,fset,fdel,doc)。 -
数据描述符(data descriptor):如果
property有fset(setter)或fdel,它就是数据描述符,优先于实例字典(__dict__)。也就是说,读取/写入都会走描述符的方法。 -
非数据描述符(non-data descriptor):如果只有
fget(只有 getter,没有 setter),它就是非数据描述符,实例字典中的同名键会覆盖它(赋值给实例会创建实例属性,遮住 property)。
这点解释了为什么要提供 setter 来阻止外部直接覆盖属性。
4) 常见用法与模式
a) 只读属性
只定义 getter,不定义 setter — 外部无法用 obj.prop = ... 改它(但注意:如果没有 setter,赋值会在实例字典创建一个同名普通属性,遮住 property)。
class C:
@property
def now(self):
return datetime.utcnow()
b) 有验证的写入
在 setter 中做校验或转换:
class Person:
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("age must be >= 0")
self._age = int(value)c) 延迟计算 & 缓存
使用 functools.cached_property(Python 3.8+)或内部缓存 _cached_value。
d) 可删除属性
用 @prop.deleter 定义 del obj.prop 的行为。
5) 在 ORM / SQLAlchemy 中用 @property:要注意的几个坑
-
不要把同名的
db.Column和@property放在同一个名字上-
如果你在类上同时声明了
secret_key = db.Column(...)和@property def secret_key(...),会造成冲突(SQLAlchemy 会把 Column 映射为类属性或 descriptor,与 property 冲突)。 -
解决办法:数据库字段使用不同的名字(常见:
secret_key_encrypted、_secret_key_encrypted),对外暴露secret_key属性。
-
-
@property只在 Python 层生效,不会被 SQL 查询引擎理解-
user.secret_key(property)是 Python 层的“计算属性”,不能用于.filter(User.secret_key == 'x')。如果你需要在查询中过滤某种“属性”,考虑用 SQLAlchemy 的hybrid_property。
-
-
迁移 / Alembic
-
property不会生成数据库列,迁移脚本只根据db.Column生成。确保数据库列定义完备用于迁移。
-
评论列表