朴素:直接暴露字段
class Player:
def __init__(self, name):
self.name = name
但有时候你想:
- 设值时校验(hp 不能小于 0)
- 取值时计算(full_name 由 first + last 组合)
- 后期改实现而不破坏调用方
@property:把方法包装成属性
class Player:
def __init__(self, name, hp):
self.name = name
self._hp = hp # 约定:_xxx 表示"内部"
@property
def hp(self): # 当成属性读
return self._hp
@hp.setter
def hp(self, value): # 当成属性写
if value < 0:
value = 0
print("⚠ HP 已纠正为 0")
self._hp = value
p = Player("WadeLy", 100)
print(p.hp) # 100 像访问字段一样
p.hp = -50 # ⚠ HP 已纠正为 0
print(p.hp) # 0
调用方完全不知道底层是方法——这就是封装。
只读属性:只 @property,不 setter
class Circle:
def __init__(self, r):
self._r = r
@property
def area(self):
import math
return math.pi * self._r ** 2
c = Circle(5)
print(c.area) # 78.5...
c.area = 100 # AttributeError: can't set
删除:deleter
@hp.deleter
def hp(self):
del self._hp
del p.hp # 真删字段
property 的本质:描述符
@property 其实是描述符协议的应用。看 property 的简化实现:
class Property:
def __init__(self, fget=None, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, instance, owner):
return self.fget(instance)
def __set__(self, instance, value):
self.fset(instance, value)
def setter(self, fset):
return Property(self.fget, fset)
描述符就是定义了 __get__ / __set__ / __delete__ 之一的对象——属于类,但代理对实例属性的访问。
自己写描述符:通用的非负整数字段
class NonNegative:
def __set_name__(self, owner, name):
self.name = name # Python 3.6+
def __get__(self, instance, owner):
return instance.__dict__.get(self.name, 0)
def __set__(self, instance, value):
if value < 0:
raise ValueError(f"{self.name} 不能为负")
instance.__dict__[self.name] = value
class Player:
hp = NonNegative()
mp = NonNegative()
gold = NonNegative()
p = Player()
p.hp = 100 # OK
p.gold = -10 # ValueError: gold 不能为负
一行 hp = NonNegative() 等价于一组完整的 property——描述符让你"批量"复用 getter/setter 逻辑。
实战意义
ORM(Django / SQLAlchemy)的字段、dataclass 的 default 处理、Pydantic 的字段都基于描述符。读源码时遇到 __get__ / __set__ 不要慌——它就是个"代理类属性访问的小工具"。
一句话总结
- 暴露字段就够 → 直接
self.x - 加点逻辑 →
@property - 多个字段共享逻辑 → 写描述符
下一篇讲 元类——"类的类"。