请解释 Python 描述符的 get、set、delete 协议,并说明如何用描述符实现 Agent 配置的类型校验和环境变量注入?
“描述符协议,其实就是 Python 给类属性安的 ‘访问拦截器’。
你只要在一个类里定义了 get / set / delete,这个类的实例再被挂到别的类上,就成了描述符,能完全接管那个属性的读写删。”
🔧 协议怎么工作¶
| 方法 | 触发时机 | 典型用途 |
|---|---|---|
| get(self, instance, owner) | 读取属性 | 延迟计算、缓存、代理 |
| set(self, instance, value) | 设置属性 | 类型校验、约束、日志 |
| delete(self, instance) | 删除属性 | 清理、审计 |
只要对象实现了这三个中的任意一个,它就是一个描述符。
优先级比实例字典高,所以能做到“看起来是普通属性,背后全是逻辑”。
🧪 应用1:类型校验¶
我写过最爽的一个描述符,就是用来给 Agent 的配置做运行时类型守护:
class Typed:
"""给 Agent 配置字段加类型锁"""
def __init__(self, expected_type):
self.expected_type = expected_type
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None: return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError(
f"💥 {self.name} 应为 {self.expected_type.__name__},"
f"实际收到 {type(value).__name__}"
)
instance.__dict__[self.name] = value
这样定义配置类:
class AgentConfig:
temperature = Typed(float)
max_tokens = Typed(int)
config = AgentConfig()
config.temperature = 0.7 # ✅
config.temperature = "hot" # ❌ TypeError
所有校验逻辑完全集中在描述符内部,配置类自己不需要写任何 setter。
🌍 应用2:环境变量注入¶
同一个描述符机制,还能让 Agent 配置自动感知环境变量——这在 12-factor 应用里特别实用:
import os
class EnvVar:
"""自动从环境变量取值,支持默认值"""
def __init__(self, env_key, default=None, cast=str):
self.env_key = env_key
self.default = default
self.cast = cast
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None: return self
# 优先从环境变量读取,找不到就用默认值
raw = os.environ.get(self.env_key)
if raw is not None:
return self.cast(raw)
return self.default
def __set__(self, instance, value):
# 允许手动覆盖
instance.__dict__[self.name] = value
Agent 配置就可以写成:
class AgentConfig:
api_key = EnvVar("OPENAI_API_KEY")
model = EnvVar("LLM_MODEL", default="gpt-4o")
timeout = EnvVar("LLM_TIMEOUT", default=30, cast=int)
效果:
-
本地开发:设好环境变量,
AgentConfig().api_key自动注入。 -
测试环境:直接
config.api_key = "test-key"手动覆盖,完全不碰环境变量。
📦 两者结合,一个完整例子¶
把类型校验和环境注入叠加到一起,就是一个生产可用的 Agent 配置描述符(伪代码):
class SafeEnvVar:
def __get__(self, instance, owner):
val = os.environ.get(self.key, self.default)
return self._cast_and_validate(val) # 读时做校验
def __set__(self, instance, value):
self._validate(value) # 写时也做校验
instance.__dict__[self.name] = value
这样无论配置是从环境来,还是手动设置,类型永远安全。