检查Python类属性
我需要一种方法来检查一个类,以便安全地识别哪些属性是用户自定义的类属性。问题是像 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 个回答
谢谢你,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
在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']
下面是比较复杂的方法。接下来是简单的方法。我也不知道为什么我没早点想到。
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