检查 python 类属性
-
27-09-2019 - |
题
我需要一种方法来检查类,以便我可以安全地识别哪些属性是用户定义的类属性。问题是 dir()、inspect.getmembers() 等函数返回所有类属性,包括预定义的属性,例如: __class__
, __doc__
, __dict__
, __hash__
. 。这当然是可以理解的,有人可能会说我可以只列出一个要忽略的命名成员列表,但不幸的是,这些预定义的属性必然会随着Python版本的不同而改变,因此使我的项目在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',因为它是实例属性。所以我的问题是...谁能帮我完成上述虚构的功能 get_user_attributes(cls)
?
附:我花了一些时间尝试通过解析 AST 级别的类来解决问题,这非常容易。但我找不到一种方法将已解析的对象转换为 AST 节点树。我想一旦一个类被编译成字节码,所有 AST 信息都会被丢弃。
最好的问候雅各布
解决方案
下面是困难的方法。这是简单的方法。不知道为什么我没有早点想到。
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
然后返回 attrs.items()
或者 list(attrs.items())
在Python 3中。
如果您实际上不想遍历 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
其他提示
“特殊属性”两端的双下划线在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']
谢谢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 类。
/ 雅各布
如果使用新样式类,是否可以简单地减去父类的属性?
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 年,仍然没有简单的函数(或库)来处理如此常见的用法。
我要感谢 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
关键字让它看起来工作量比实际少。