本文旨在提供一种使用 python 反射机制获取类及其父类中所有声明或类型化的参数的方法。通过结合 inspect.get_annotations 和类的 __mro__ 属性,我们可以提取包括父类在内的所有类型注解,并将其组织成一个易于使用的字典。本文提供详细的代码示例和解释,帮助读者理解和应用这一技术。
使用 inspect.get_annotations 和 __mro__ 获取参数类型信息
Python 的 inspect 模块提供了强大的反射功能,其中 inspect.get_annotations 函数可以获取指定对象的类型注解。类的 __mro__ 属性则包含了类的继承关系,即方法解析顺序(Method Resolution Order)。结合这两个工具,我们可以遍历类的继承链,提取所有父类的类型注解。
以下是一个示例代码:
import inspect from typing import Any, Optional, Dict class Parent: parent_param: str parent_default: Optional[str] = None @classmethod def find_params_meta(cls) -> Dict[str, Any]: params_meta = {} for c in reversed(cls.__mro__): if hasattr(c, '__annotations__'): for k, v in inspect.get_annotations(c).items(): if k not in params_meta: # 避免子类覆盖父类同名参数的类型信息 params_meta[k] = {"types": [v]} if hasattr(cls, k) and getattr(cls, k) != inspect._empty: params_meta[k]["default"] = getattr(cls, k) return params_meta class Child(Parent): child_param: str _params_meta: Optional[Dict[str, Any]] = None def __init__(self, parent_param: str, child_param: str): self._params_meta = self.find_params_meta() self.child_param = child_param self.parent_param = parent_param def __getattr__(self, name): # 模拟未初始化的属性访问 raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") # 实例化对象并打印结果 child_instance = Child("parent param", "child param") print(child_instance._params_meta) # 断言 expected_result = { "parent_param": {"types": [str]}, "parent_default": {"types": [str, type(None)], "default": None}, "child_param": {"types": [str]}, } assert child_instance._params_meta == expected_result
代码解释:
- Parent 类和 Child 类: 定义了父类 Parent 和子类 Child,包含类型注解的类属性。
- find_params_meta 方法: 这是一个类方法,用于获取类及其父类的类型注解。
- cls.__mro__: __mro__ 属性返回一个包含类及其所有父类的元组,按照方法解析顺序排列。 使用 reversed 反转这个顺序,确保子类的类型信息优先。
- inspect.get_annotations(c): 获取每个类 c 的类型注解,返回一个字典。
- 类型信息合并: 遍历父类链时,将父类的类型信息合并到 params_meta 中。如果子类定义了与父类同名的参数,则子类的类型信息优先。
- 默认值获取: 检查类属性是否有默认值,如果有则添加到结果字典中。
- __getattr__ 方法: 为了模拟问题描述中“未实例化属性”的场景,重写了 __getattr__ 方法,当访问不存在的属性时抛出 AttributeError。
运行结果:
{'parent_param': {'types': [<class 'str'>]}, 'parent_default': {'types': [<class 'str'>, <class 'NoneType'>], 'default': None}, 'child_param': {'types': [<class 'str'>]}
注意事项
- 类型覆盖: 在继承关系中,如果子类重新定义了父类的属性,并且类型注解不同,则子类的类型注解会覆盖父类的类型注解。
- 性能: 使用反射可能会影响性能,特别是在频繁调用的场景中。 可以考虑缓存结果以提高性能。
- 兼容性: inspect.get_annotations 是 Python 3.9 引入的,如果需要兼容更早的版本,可以使用 typing.get_type_hints。
总结
通过结合 inspect.get_annotations 和 __mro__ 属性,我们可以方便地获取类及其父类的类型注解。这对于实现一些通用的工具函数,例如自动生成 API 文档、参数校验等非常有用。 在实际应用中,需要注意类型覆盖、性能和兼容性等问题。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END