← 返回题目列表

Python 反射机制怎么理解?getattr、setattr、hasattr 有什么用?

高频 中等 第 2 / 27 题 更新于 2026/07/27
反射getattrsetattrhasattr动态调用

简化版

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)

这比先 hasattrgetattr 更简洁。

但注意,如果 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 判存在,适合框架、插件和配置驱动;但动态能力越强,越要靠白名单和清晰边界控制风险。