我需要一种检查类的方法,这样我就可以安全地识别哪些属性是用户定义的类属性。问题是像dir(),inspect.getmembers()和friends这样的函数返回所有类属性,包括预定义的属性,如:__class__
,__doc__
,__dict__
,{{1 }}。这当然是可以理解的,有人可能会说我可以只列出一个要忽略的命名成员列表,但不幸的是这些预定义的属性必然会随着Python的不同版本而改变,因此使我的项目在python项目中发生了变化 - 我不喜欢那样。
示例:
__hash__
在上面的例子中,我想要一种安全的方法来只检索用户定义的类属性['a','b']而不是'c',因为它是一个实例属性。所以我的问题是......任何人都可以帮我解决上述虚构函数>>> 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']
吗?
P.S。我花了一些时间试图通过解析AST级别的类来解决问题,这很容易。但我找不到将已解析的对象转换为AST节点树的方法。我想一旦将一个类编译成字节码,就会丢弃所有的AST信息。
最好的问候雅各布
答案 0 :(得分:27)
以下是困难的方法。这是简单的方法。不知道为什么它不会早点发生在我身上。
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
答案 1 :(得分:6)
'特殊属性'两端的双下划线是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']
答案 2 :(得分:3)
谢谢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
答案 3 :(得分:2)
如果你使用新的样式类,你能简单地减去父类的属性吗?
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__
,但对象本身不存在。你可能会遇到一些特殊情况 - 我怀疑它们经常发生变化。
答案 4 :(得分:1)
很抱歉使线程坏死。令我惊讶的是,截至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
关键字。
答案 5 :(得分:0)
这对我有用,可以在 __
中包含用户定义的 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__