简短的回答是,没有办法获得Python中所有对象的属性列表,因为这些属性可以动态生成。举一个极端的例子,考虑这个类:
>>> class Spam(object):
... def __getattr__(self, attr):
... if attr.startswith('x'):
... return attr[1:]
>>> spam = Spam()
>>> spam.xeggs
'eggs'
即使解释器可以有人找出所有属性的列表,该列表将是无限的。
对于简单的课程,spam.__dict__
往往足够好。它不处理动态属性,基于__slots__
的属性,类属性,C扩展类,从上面大多数继承的属性以及所有其他类型的东西。但至少是某种东西 - 有时候,这是你想要的东西。第一个近似值,这正是你在__init__
或更高版本中明确分配的内容,而没有其他内容。
为了达到旨在提高人们可读性的“一切”的最佳效果,请使用dir(spam)
。
如果您想尽一切努力来达到编程使用的“一切”目标,请使用inspect.getmembers(spam)
。 (虽然实际上实施只是一个在CPython的2.x的周围dir
包装,它可以做多,并在事实上确实在CPython的3.2+)
这些都将处理各种各样的事情,__dict__
不能,并且可能会跳过__dict__
中但您不想看到的内容。但它们本质上还不完整。
无论你使用什么,获取值和键都很容易。如果您使用__dict__
或getmembers
,这是微不足道的; __dict__
通常是dict
,或者为了您的目的而足够接近dict
,而getmembers
显式返回键值对。如果您使用dir
,你可以得到一个dict
很容易:
{key: getattr(spam, key) for key in dir(spam)}
最后一件事:“对象”是一个有点模糊的术语。它可以表示“从object
派生的类的任何实例”,“类的任何实例”,“新样式类的任何实例”或“任何类型的任何值”(模块,类,函数,等等。)。几乎任何东西都可以使用dir
和getmembers
;文档中描述了这些含义的具体细节。
一个偶数最后-ER的事情:您可能注意到,getmembers
回报之类的东西('__str__', <method-wrapper '__str__' of Spam object at 0x1066be790>
),这你可能不感兴趣,给出的结果也只是名称 - 值对,如果你只是想删除__dunder__
方法。 ,_private
变量等,这很容易。但是,通常情况下,你想过滤“种类的成员”。 getmembers
函数需要一个过滤器参数,但文档在解释如何使用它方面做得并不出色(并且,最重要的是,希望您了解描述符是如何工作的)。基本上,如果你想要一个过滤器,它通常是callable
,lambda x: not callable(x)
或lambda
由inspect.isfoo
函数的组合组成。
所以,这是很常见,你可能希望把它写了一个函数:
def get_public_variables(obj):
return [(name, value) for name, value
in inspect.getmembers(obj, lambda x: not callable(x))
if not name.startswith('_')]
你可以把它转换成一个自定义的IPython%的神奇功能,或者只是做一个%宏出来,或者把它作为一个常规函数并明确地调用它。
在一个评论,你问你是否可以只打包这件事成__repr__
功能,而不是试图创建%法术功能或什么的。
如果您已经拥有了从单个根类继承的所有类,这是一个好主意。你可以编写一个适用于你所有课程的单一__repr__
(或者,如果它适用于其中99%的课程,则可以在其他1%中覆盖该__repr__
),然后每次评估解释器中的任何对象时或打印出来,你会得到你想要的。
然而,几件事情要记住:
Python有两个__str__
(你会得到什么,如果你print
的对象)和__repr__
(你会得到什么,如果你只是评估在交互提示的对象)一个原因。通常,前者是一种很好的人类可读表示,而后者是eval
-able(或交互式提示中的可打字形式),或者简洁的角括号形式,可以让您足以区分类型和对象的身份。
这只是一个惯例,而不是一个规则,所以你可以随意打破它。但是,如果您的为要打破它,您可能仍然要使用str
/repr
区别 - 例如,使repr
为您提供了所有内部的完整转储,而str
仅显示了有用的公共值。
更严重的是,您必须考虑如何组成repr
值。例如,如果您使用print
或repr
a list
,那么实际上可以得到'[' + ', '.join(map(repr, item))) + ']'
。这在多行repr
中看起来很奇怪。如果你使用任何一种试图缩进嵌套集合的漂亮打印机,就像IPython内置的集合那样,情况会更糟糕。结果可能不会是无法读取的,它只会打败漂亮打印机提供的好处。
至于你想展示的具体东西:这很容易。类似这样的:
def __repr__(self):
lines = []
classes = inspect.getmro(type(self))
lines.append(' '.join(repr(cls) for cls in classes))
lines.append('')
lines.append('Attributes:')
attributes = inspect.getmembers(self, callable)
longest = max(len(name) for name, value in attributes)
fmt = '{:>%s}: {}' % (longest,)
for name, value in attributes:
if not name.startswith('__'):
lines.append(fmt.format(name, value))
lines.append('')
lines.append('Methods:')
methods = inspect.getmembers(self, negate(callable))
for name, value in methods:
if not name.startswith('__'):
lines.append(name)
return '\n'.join(lines)
右对齐属性名称是最难的部分。 (而且我可能弄错了,因为这是未经测试的代码...)其他一切都很简单,或者很有趣(使用不同的过滤器以getmembers
来查看它们的功能)。
尝试编写对象名称后面跟一个点,然后双击TAB,例如'MyObj中。'。这是你寻找的那种东西吗? –
Marius
2013-03-18 22:07:21
这似乎只是列出了实例变量和方法,它们很接近但并不完全。我希望看到实例变量的值以及... – user1507844 2013-03-18 22:34:17
您定义属性的方式很奇怪 - 它是一个由类属性('MyClass._x')支持的每实例属性...直到您首先设置它,然后创建一个影响类属性的实例属性('mc._x')。这种行为实际上可能很有用,但我怀疑这是类和实例成员之间混淆的迹象。 – abarnert 2013-03-20 19:28:26