如何通过类方法静态获取类的所有实例属性(支持继承场景)
如何通过类方法静态获取类的所有实例属性(支持继承场景)
首先聊聊你遇到的问题:你想用类方法拿到类的实例属性(就是__init__里给self绑定的那些属性),还得支持继承。你之前试的__dir__()是实例方法,直接用类调用返回的是类本身的属性列表,不是实例的;而MyClass.__dict__是类的命名空间,存的是类方法、类属性这些,自然拿不到实例的属性。
下面给你两种实用的解决方案,你可以根据自己的场景选择:
方法一:创建临时实例(简单直接,适合无副作用的__init__)
如果你的类的__init__方法只是单纯初始化属性,没有修改外部状态、发起请求这类副作用,那直接创建一个临时实例,然后取它的__dict__键列表就可以了,还能自动处理继承的属性:
class MyClass(): def __init__(self, x = None, y = None): self.a = x self.b = y @classmethod def get_all_attr(cls): # 用默认参数创建临时实例 temp_instance = cls() # 返回实例的属性列表,顺序和初始化顺序一致 return list(temp_instance.__dict__.keys()) class MyChildClass(MyClass): # 注意这里要继承MyClass,你原来的代码漏了 def __init__(self, z = None, **kwargs): self.c = z super().__init__(**kwargs) >>> MyClass.get_all_attr() ['a', 'b'] >>> MyChildClass.get_all_attr() ['c', 'a', 'b']
优点:代码简单,不需要额外依赖,自动处理继承关系;
缺点:如果__init__需要必填参数、或者有副作用,这种方法就不适用了。
方法二:解析__init__的源代码(无实例创建,适合有副作用的场景)
如果你的__init__有副作用,或者不想创建实例,那可以通过解析__init__方法的源代码,找出所有给self赋值的属性,还能遍历父类的__init__来收集继承的属性:
import inspect import ast class MyClass(): def __init__(self, x = None, y = None): self.a = x self.b = y @classmethod def get_all_attr(cls): attrs = set() # 遍历当前类及其所有父类(按照方法解析顺序MRO) for base_cls in cls.__mro__: # 跳过没有自定义__init__的父类(比如object) if '__init__' not in base_cls.__dict__: continue try: # 获取__init__方法的源代码 source = inspect.getsource(base_cls.__init__) # 解析成抽象语法树(AST) tree = ast.parse(source) # 遍历所有赋值语句 for node in ast.walk(tree): if isinstance(node, ast.Assign): # 检查赋值目标是不是self.xxx的形式 for target in node.targets: if (isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and target.value.id == 'self'): attrs.add(target.attr) except TypeError: # 如果是内置类或者无法获取源代码的类,跳过 continue # 转换成列表返回(顺序不固定,需要的话可以调整排序) return list(attrs) class MyChildClass(MyClass): def __init__(self, z = None, **kwargs): self.c = z super().__init__(**kwargs) >>> MyClass.get_all_attr() ['a', 'b'] >>> MyChildClass.get_all_attr() ['c', 'a', 'b']
优点:不需要创建实例,避免__init__的副作用;
缺点:依赖inspect和ast模块,而且如果__init__的代码是动态生成的、或者是C扩展类,可能无法获取源代码,导致收集不全。
备注:内容来源于stack exchange,提问作者Emke




