Python 反射机制怎么理解?getattr、setattr、hasattr 有什么用?
简化版
Python 反射是指程序在运行时根据字符串动态访问对象属性、方法或模块信息。getattr 读取属性,setattr 设置属性,hasattr 判断属性是否存在,常用于插件、路由分发、配置驱动和框架代码。
详细版
示例:
class Service:
def hello(self):
return "hello"
service = Service()
method_name = "hello"
method = getattr(service, method_name)
print(method()) # hello
常用函数:
getattr(obj, "name") # 读取属性
getattr(obj, "name", default) # 不存在时返回默认值
setattr(obj, "name", value) # 设置属性
hasattr(obj, "name") # 判断属性是否存在
delattr(obj, "name") # 删除属性
使用场景:
- 根据配置选择处理器;
- Web 框架根据路由调用方法;
- 插件系统动态加载能力;
- 序列化和对象映射;
- 测试或调试工具。
注意:反射很灵活,但过度使用会降低可读性,也可能带来安全风险。不要把用户输入的字符串不加限制地用于动态调用敏感方法。
完整版教学
一、反射为什么存在
普通调用是静态写死的:
service.hello()
反射允许你在运行时决定调用哪个属性或方法:
name = "hello"
getattr(service, name)()
这让代码可以由配置、路由、命令名、插件名驱动。
例如命令分发:
class CommandHandler:
def start(self):
return "start"
def stop(self):
return "stop"
def dispatch(handler, command):
method = getattr(handler, command)
return method()
二、getattr 的默认值
如果属性不存在:
getattr(obj, "missing")
会抛出 AttributeError。
可以提供默认值:
value = getattr(obj, "missing", None)
这比先 hasattr 再 getattr 更简洁。
但注意,如果 None 本身也是合法值,就要使用专门的哨兵对象:
MISSING = object()
value = getattr(obj, "name", MISSING)
if value is MISSING:
...
三、setattr 和动态属性
setattr 可以根据字符串设置属性:
setattr(user, "name", "Tom")
等价于:
user.name = "Tom"
它常用于把字典数据映射到对象:
for key, value in data.items():
setattr(user, key, value)
但这种写法要小心。外部数据可能覆盖对象内部属性或方法。
更安全的方式是白名单:
allowed = {"name", "age"}
for key, value in data.items():
if key in allowed:
setattr(user, key, value)
四、hasattr 的细节
hasattr(obj, "name") 本质上会尝试获取属性,如果抛出 AttributeError 就返回 False。
这意味着如果属性访问逻辑本身有副作用或复杂异常,hasattr 可能触发代码执行。
例如 property:
class User:
@property
def name(self):
print("loading name")
return "Tom"
hasattr(User(), "name")
会执行 property 的读取逻辑。
五、反射和 getattr
类可以定义 __getattr__ 来处理不存在的属性:
class Dynamic:
def __getattr__(self, name):
return f"missing: {name}"
调用:
obj.anything
如果普通查找找不到,就会进入 __getattr__。
这常用于代理对象、懒加载、动态配置等场景。
六、反射的风险
反射风险主要有两个。
第一,可读性下降。读代码时无法直接搜索到调用关系。
第二,安全风险。如果用户输入直接控制方法名:
method = getattr(service, user_input)
method()
用户可能调用到不该暴露的方法。应该做白名单映射:
commands = {
"start": service.start,
"stop": service.stop,
}
commands[user_input]()
白名单通常比完全动态反射更安全、更可维护。
七、常见误区与追问
记忆钩子:反射让“名字字符串”变成“对象属性操作”。它适合框架和动态分发,但字符串一旦来自外部输入,就要做白名单。
- 误区:
getattr(obj, name)只是读obj.__dict__。 属性访问会触发描述符、property、__getattribute__、__getattr__等机制,不只是查实例字典。 - 追问:
getattr第三个参数有什么用? 它是默认值,属性不存在时返回默认值而不是抛AttributeError;适合可选能力探测。 - 误区:
hasattr完全没有副作用。hasattr内部会尝试getattr并捕获AttributeError,因此可能触发 property 或__getattr__里的逻辑。 - 追问:
__getattr__和__getattribute__有什么区别?__getattribute__会拦截几乎所有属性访问;__getattr__只在正常查找失败后调用,通常更安全易用。 - 误区:
setattr可以随便设置任何字段。 普通对象可以动态加属性,但有__slots__、只读 property、描述符校验或自定义__setattr__时,设置可能失败或触发额外逻辑。 - 追问:反射做命令分发有什么风险? 如果直接把用户输入拼成方法名调用,可能暴露内部方法;应使用显式白名单映射,例如
{"start": start, "stop": stop}。
八、加强记忆
反射就是运行时通过字符串操作对象能力。getattr 读,setattr 写,hasattr 判存在,适合框架、插件和配置驱动;但动态能力越强,越要靠白名单和清晰边界控制风险。