朴素:直接暴露字段

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
  • 多个字段共享逻辑 → 写描述符

下一篇讲 元类——"类的类"。