检查python类属性

Inspect python class attributes

我需要一种检查类的方法,以便安全地识别哪些属性是用户定义的类属性。问题是dir()、inspect.getmembers()和friends等函数返回所有类属性,包括预定义的属性,如:__class____doc____dict____hash__。当然,这是可以理解的,有人可能会说,我可以列出一个要忽略的命名成员列表,但不幸的是,这些预定义的属性在不同版本的python中必然会发生变化,因此,我的项目在python项目中是可以更改的——我不喜欢这样。

例子:

1
2
3
4
5
6
7
8
9
>>> 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',因为它是一个实例属性。所以我的问题是…有人能帮我完成上述虚构的功能吗?

另外,我花了一些时间试图通过解析AST级别的类来解决这个问题,这非常容易。但我找不到将已经解析的对象转换为AST节点树的方法。我猜一旦一个类被编译成字节码,所有的AST信息都会被丢弃。

祝雅各布好运


下面是一条艰难的路。这是简单的方法。不知道为什么我没早点想到。

1
2
3
4
5
6
7
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]

这是个开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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,只想直接在子类上定义属性,那么更容易:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


"特殊属性"两端的双下划线是2.0之前Python的一部分。他们不太可能在不久的将来随时改变这一点。

1
2
3
4
5
6
7
8
9
10
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']


谢谢你,你给了我一个我需要的表情。我的最后一个类属性检查器函数如下所示:

1
2
3
4
5
6
7
8
9
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

或者只返回类属性variabels(exclude_methods=true),或者也检索这些方法。我最初测试的是上面的函数,它既支持旧的也支持新的python类。

/雅各布


如果使用新的样式类,可以简单地减去父类的属性吗?

1
2
3
4
5
6
7
class A(object):
    a = 10
    b = 20
    #...

def get_attrs(Foo):
    return [k for k in dir(Foo) if k not in dir(super(Foo))]

编辑:不完全是。从客体继承时,出现了__dict____module____weakref__,但客体本身并不存在。你可以对这些进行特殊处理——我怀疑它们会经常改变。


抱歉,内格罗撞到了线。我很惊讶,到2019年还没有简单的函数(或库)来处理这种常见的用法。

我要感谢阿隆斯特林的想法。实际上,set容器提供了一种更直接的表达方式:

1
2
3
4
5
6
7
class dummy:    pass

def abridged_set_of_user_attributes(obj):
    return set(dir(obj))-set(dir(dummy))

def abridged_list_of_user_attributes(obj):
    return list(abridged_set_of_user_attributes(obj))

使用列表理解的原始解决方案实际上是两个级别的循环,因为有两个in关键字组合在一起,尽管只有一个for关键字使它看起来不像是工作。