检查Python类属性

40 投票
6 回答
44961 浏览
提问于 2025-04-16 07:25

我需要一种方法来检查一个类,以便安全地识别哪些属性是用户自定义的类属性。问题是像 dir()inspect.getmembers() 这些函数会返回所有类属性,包括一些预定义的属性,比如 __class____doc____dict____hash__。这当然是可以理解的,有人可能会说我可以列出要忽略的属性,但不幸的是,这些预定义属性在不同版本的 Python 中可能会变化,这样就会让我的项目受到影响,而我并不喜欢这样。

举个例子:

>>> class A:
...   a=10
...   b=20
...   def __init__(self):
...     self.c=30
>>> dir(A)
['__doc__', '__init__', '__module__', 'a', 'b']
>>> get_user_attributes(A)
['a','b']

在上面的例子中,我想安全地获取用户自定义的类属性 ['a','b'],而不是 'c',因为 'c' 是一个实例属性。所以我的问题是……有没有人能帮我实现这个虚构的函数 get_user_attributes(cls)

我花了一些时间尝试通过解析类的 AST(抽象语法树)层面来解决这个问题,这样会很简单。但我找不到将已经解析的对象转换为 AST 节点树的方法。我想一旦类被编译成字节码,所有的 AST 信息就会被丢弃。

6 个回答

4

谢谢你,aaronasterling,你给了我需要的表达式 :-)

我最终的类属性检查函数看起来是这样的:

def get_user_attributes(cls,exclude_methods=True):
  base_attrs = dir(type('dummy', (object,), {}))
  this_cls_attrs = dir(cls)
  res = []
  for attr in this_cls_attrs:
    if base_attrs.count(attr) or (callable(getattr(cls,attr)) and exclude_methods):
      continue
    res += [attr]
  return res

这个函数可以选择只返回类的属性变量(exclude_methods=True),或者也可以获取方法。我的初步测试显示,这个函数支持旧式和新式的Python类。

/ Jakob

8

在Python中,前后都有双下划线的“特殊属性”早在2.0版本之前就已经存在了。可以说,在不久的将来,他们不太可能会改变这个规则。

class Foo(object):
  a = 1
  b = 2

def get_attrs(klass):
  return [k for k in klass.__dict__.keys()
            if not k.startswith('__')
            and not k.endswith('__')]

print get_attrs(Foo)

['a', 'b']

38

下面是比较复杂的方法。接下来是简单的方法。我也不知道为什么我没早点想到。

import inspect

def get_user_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return [item
            for item in inspect.getmembers(cls)
            if item[0] not in boring]

这是一个开始

def get_user_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    attrs = {}
    bases = reversed(inspect.getmro(cls))   
    for base in bases:
        if hasattr(base, '__dict__'):
            attrs.update(base.__dict__)
        elif hasattr(base, '__slots__'):
            if hasattr(base, base.__slots__[0]): 
                # We're dealing with a non-string sequence or one char string
                for item in base.__slots__:
                    attrs[item] = getattr(base, item)
            else: 
                # We're dealing with a single identifier as a string
                attrs[base.__slots__] = getattr(base, base.__slots__)
    for key in boring:
        del attrs['key']  # we can be sure it will be present so no need to guard this
    return attrs

这个方法应该相当可靠。基本上,它的工作原理是获取一个默认的 object 子类上要忽略的属性。然后,它获取传入类的 MRO(方法解析顺序),并反向遍历这个顺序,这样子类的键就可以覆盖父类的键。最后,它返回一个键值对的字典。如果你想要像 inspect.getmembers 那样的键值元组列表,只需在 Python 3 中返回 attrs.items()list(attrs.items()) 就可以了。

如果你其实不想遍历 MRO,只想获取直接在子类上定义的属性,那就简单多了:

def get_user_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    if hasattr(cls, '__dict__'):
        attrs = cls.__dict__.copy()
    elif hasattr(cls, '__slots__'):
        if hasattr(base, base.__slots__[0]): 
            # We're dealing with a non-string sequence or one char string
            for item in base.__slots__:
                attrs[item] = getattr(base, item)
            else: 
                # We're dealing with a single identifier as a string
                attrs[base.__slots__] = getattr(base, base.__slots__)
    for key in boring:
        del attrs['key']  # we can be sure it will be present so no need to guard this
    return attrs

撰写回答