基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Python中的反射(Introspection/Reflection)

知识点图片

在 Python 中,反射(Reflection)内省(Introspection) 通常指程序在运行时能够检查、访问和修改自身状态或行为的能力。

由于 Python 是一门高度动态的语言,一切皆对象(类、函数、模块都是对象),因此 Python 对反射的支持非常强大且自然。

以下是 Python 中反射机制的核心概念、常用函数及实际应用场景。


1. 核心概念:四大内置函数

这是 Python 反射机制中最常用、最基础的四个函数,允许你通过字符串来操作对象的属性和方法。

假设我们有一个简单的类:

python
class Person:
    def __init__(self, name):
        self.name = name

    def say_hello(self):
        print(f"Hello, I am {self.name}")

p = Person("Alice")

(1) hasattr(object, name)

检查对象是否包含某个属性或方法。

  • 参数:对象,属性名(字符串)。
  • 返回:True / False。
python
print(hasattr(p, "name"))       # True
print(hasattr(p, "say_hello"))  # True
print(hasattr(p, "age"))        # False

(2) getattr(object, name[, default])

获取对象的属性或方法引用。

  • 参数:对象,属性名(字符串),默认值(可选)。
  • 用途:动态调用方法或获取值。
python
# 获取属性
n = getattr(p, "name")
print(n)  # Alice

# 获取方法并调用
func = getattr(p, "say_hello")
func()    # Hello, I am Alice

# 获取不存在的属性(设置默认值防止报错)
age = getattr(p, "age", 18)
print(age) # 18

(3) setattr(object, name, value)

设置对象的属性值。如果属性不存在,则创建它。

  • 参数:对象,属性名(字符串),值。
python
setattr(p, "age", 30)
print(p.age)  # 30

# 甚至可以动态添加方法(虽然不常用)
def say_bye(self):
    print("Bye!")

# 将函数绑定到对象上(注意:这样绑定不会自动传入 self,通常用于类级别或特殊处理)
setattr(p, "say_bye", say_bye)
# p.say_bye(p) # 需要手动传参,除非绑定到类上

(4) delattr(object, name)

删除对象的属性。

  • 参数:对象,属性名(字符串)。
python
delattr(p, "age")
# print(p.age) # 报错:AttributeError

2. 深入内省:了解对象内部

除了操作属性,Python 还提供了工具来查看对象的元数据(类型、结构、文档等)。

(1) dir(object)

列出对象所有的属性和方法名(包括内置的魔术方法)。

python
print(dir(p))
# 输出: ['__class__', '__delattr__', ..., 'name', 'say_hello']

(2) type()isinstance()

检查对象类型。

  • type(obj): 返回对象的类。
  • isinstance(obj, Class): 检查对象是否是该类(或其子类)的实例(推荐使用)。
python
print(type(p) == Person)    # True
print(isinstance(p, Person)) # True

(3) __dict__ 属性

大多数对象使用 __dict__ 字典来存储其可写的属性。

python
print(p.__dict__)
# 输出: {'name': 'Alice'}

3. 高级反射:inspect 模块

当内置函数不够用时(例如需要获取函数的参数签名、源代码、或者调用堆栈),Python 提供了标准库 inspect

python
import inspect

def add(a: int, b: int = 10) -> int:
    """这是一个加法函数"""
    return a + b

# 1. 检查是否是函数
print(inspect.isfunction(add))  # True

# 2. 获取函数签名(参数和类型注解)
sig = inspect.signature(add)
print(sig)  # (a: int, b: int = 10) -> int

# 3. 获取参数详情
for name, param in sig.parameters.items():
    print(f"参数: {name}, 默认值: {param.default}")

# 4. 获取源代码
print(inspect.getsource(add))

# 5. 获取文档字符串
print(inspect.getdoc(add))

4. 动态导入模块

反射不仅限于类内部,还可以用于动态加载模块。这在插件系统或根据配置加载不同驱动时非常有用。

使用 importlib 模块:

python
import importlib

# 假设字符串是 "json"
module_name = "json"

# 动态导入模块
my_module = importlib.import_module(module_name)

# 动态调用模块里的方法
data = {"key": "value"}
json_str = my_module.dumps(data)
print(json_str)  # {"key": "value"}

5. 实际应用场景

反射在 Python 开发中无处不在,尤其是框架开发:

  1. Web 框架 (如 Django/Flask) 的路由系统
    • URL 字符串 /users/create 映射到视图函数 create_user。框架会根据字符串名称找到对应的函数并执行。
  2. ORM (如 SQLAlchemy/Django ORM)
    • 将数据库的列名映射到 Python 对象的属性。
    • user.save() 时,ORM 会遍历 user 对象的属性,生成 SQL 语句。
  3. 配置文件解析
    • 读取文本配置文件,根据键值对动态设置程序的全局变量。
  4. 插件架构
    • 扫描 plugins 文件夹,动态 import 里面的文件,并查找特定的类或入口函数执行。

6. 简单的实战示例:命令分发器

假设你正在写一个命令行工具,用户输入字符串命令,你执行对应的方法。

python
class CommandHandler:
    def cmd_start(self):
        print("系统启动中...")

    def cmd_stop(self):
        print("系统停止中...")

    def cmd_help(self):
        print("可用命令: start, stop, help")

    def run(self, command):
        # 拼接方法名
        method_name = f"cmd_{command}"
        
        # 利用反射查找方法
        if hasattr(self, method_name):
            method = getattr(self, method_name)
            method()
        else:
            print(f"未知命令: {command}")

handler = CommandHandler()
user_input = "start"  # 模拟用户输入
handler.run(user_input) # 输出: 系统启动中...

7. 注意事项

虽然反射很强大,但也要谨慎使用:

  1. 可读性差getattr(obj, "func")()obj.func() 难读,IDE 也无法进行代码跳转和自动补全。
  2. 性能开销:反射操作比直接调用稍微慢一点(通常可以忽略不计,除非在极高频循环中)。
  3. 安全性:如果允许用户输入直接传递给 getattrimport_module,可能会导致恶意代码执行。永远不要对用户输入使用 eval()exec(),尽量限制 getattr 的查找范围。

总结

  • Introspection (内省): type(), dir(), id(), inspect 模块 —— 对象是什么。
  • Reflection (反射): getattr(), setattr(), hasattr() —— 态地操作对象。

Python 的反射机制是其灵活性("Magic")的主要来源之一。

00:00
00:00