38

我需要一种检查类的方法,以便我可以安全地识别哪些属性是用户定义的类属性。问题是 dir()、inspect.getmembers() 和朋友之类的函数会返回所有类属性,包括预定义的属性,例如:__class__、、、、。这当然是可以理解的,有人可能会争辩说我可以只列出一个要忽略的命名成员,但不幸的是,这些预定义的属性必然会随着 Python 的不同版本而改变,因此我的项目很容易在 python 项目中发生变化- 我不喜欢那样。__doc____dict____hash__

例子:

>>> 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',因为它是一个实例属性。所以我的问题是......任何人都可以帮助我完成上述虚构功能get_user_attributes(cls)吗?

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

4

6 回答 6

36

下面是困难的方法。这是简单的方法。不知道为什么我没有早点想到。

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 并以相反的顺序遍历它,以便子类键可以覆盖超类键。它返回一个键值对字典。如果你想要一个键值元组的列表,比如 ininspect.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
于 2010-11-22T00:13:05.600 回答
8

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

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']

于 2010-11-22T00:09:29.323 回答
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 类。

/雅各布

于 2010-11-22T07:46:02.897 回答
3

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

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__在从对象继承时出现,但在对象本身中不存在。您可以对这些进行特殊处理-我怀疑它们会经常更改。

于 2010-11-22T00:15:05.653 回答
2

对不起,因为死灵撞到了线程。令我惊讶的是,截至 2019 年仍然没有简单的函数(或库)来处理这种常见用法。

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

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关键字使它看起来比实际工作少。

于 2019-04-28T06:40:15.330 回答
1

这对我有用,可以包含用户定义的属性,__这些属性可能在cls.__dict__

import inspect

class A:
    __a = True
    
    def __init__(self, _a, b, c):
        self._a = _a
        self.b = b
        self.c = c 

    def test(self):
        return False

cls = A(1, 2, 3)

members = inspect.getmembers(cls, predicate=lambda x: not inspect.ismethod(x))
attrs = set(dict(members).keys()).intersection(set(cls.__dict__.keys()))
__attrs = {m[0] for m in members if m[0].startswith(f'_{cls.__class__.__name__}')}
attrs.update(__attrs)

这将正确产生:{'_A__a', '_a', 'b', 'c'}

cls.__class__.__name__如果您愿意,您可以更新以清理

于 2021-01-23T22:54:00.217 回答