<p>简而言之,在Python中无法获得对象所有属性的列表,因为这些属性可以动态生成。对于一个极端的例子,考虑这个类:</p>
<pre><code>>>> class Spam(object):
... def __getattr__(self, attr):
... if attr.startswith('x'):
... return attr[1:]
>>> spam = Spam()
>>> spam.xeggs
'eggs'
</code></pre>
<p>即使解释器能找出一个包含所有属性的列表,这个列表也是无限的。在</p>
<p>对于简单的类,<code>spam.__dict__</code>通常就足够了。它不处理动态属性、基于<code>__slots__</code>的属性、类属性、C扩展类、继承自上面大部分内容的属性以及其他各种东西。但这至少是某些东西,有时,它是你想要的东西。对于第一个近似值,它正是您在<code>__init__</code>或更高版本中显式分配的内容,而没有其他内容。在</p>
<p>为了达到“一切”的最佳效果,可以使用<a href="http://docs.python.org/2/library/functions.html#dir" rel="noreferrer">^{<cd4>}</a>。在</p>
<p>为了尽最大努力将“一切”用于编程,请使用<a href="http://docs.python.org/2/library/inspect.html#inspect.getmembers" rel="noreferrer">^{<cd5>}</a>。(尽管实际上该实现只是cpython2.x中<code>dir</code>的包装器,但它在cpython3.2+中可以做得更多,事实上也可以做得更多。)</p>
<p>它们都将处理<code>__dict__</code>不能处理的大量内容,并且可能会跳过<code>__dict__</code>中您不想看到的内容。但它们本质上还是不完整的。在</p>
<p>无论使用什么方法,获取值和键都很容易。如果您使用的是<code>__dict__</code>或<code>getmembers</code>,那么这很简单;<code>__dict__</code>通常是<code>dict</code>,或者是与<code>dict</code>非常接近的东西,<code>getmembers</code>显式返回键值对。如果您正在使用<code>dir</code>,您可以很容易地得到一个<code>dict</code>:</p>
^{pr2}$
<p>最后一件事:“object”是一个有点模棱两可的术语。它可以表示“从<code>object</code>派生的类的任何实例”、“类的任何实例”、“新样式类的任何实例”或“任何类型的任何值”(模块、类、函数等)。您可以在任何事情上使用<code>dir</code>和<code>getmembers</code>;文档中详细描述了这一点。在</p>
<p>还有最后一件事:您可能会注意到<code>getmembers</code>返回了<code>('__str__', <method-wrapper '__str__' of Spam object at 0x1066be790></code>)之类的内容,而您可能对此并不感兴趣。由于结果只是名称-值对,如果您只想删除<code>__dunder__</code>方法、<code>_private</code>变量等,这很简单。但通常,你需要过滤“成员种类”。<code>getmembers</code>函数接受一个过滤器参数,但是文档没有很好地解释如何使用它(而且,除此之外,希望您理解描述符的工作原理)。基本上,如果你想要一个过滤器,它通常是<code>callable</code>,<code>lambda x: not callable(x)</code>,或者由<code>inspect.isfoo</code>函数组合而成的<code>lambda</code>。在</p>
<p>所以,这是很常见的,你可以把它写成一个函数:</p>
<pre><code>def get_public_variables(obj):
return [(name, value) for name, value
in inspect.getmembers(obj, lambda x: not callable(x))
if not name.startswith('_')]
</code></pre>
<p>你可以把它变成一个自定义的IPython%魔术函数,或者只是用它做一个%宏,或者把它作为一个常规函数显式调用。在</p>
<hr/>
<p>在一篇评论中,您问您是否可以将它打包成一个<code>__repr__</code>函数,而不是尝试创建一个%magic函数之类的。在</p>
<p>如果您已经从一个根类继承了所有类,这是一个好主意。您可以编写一个对所有类都有效的<code>__repr__</code>(或者,如果它适用于99%的类,那么您可以重写其他1%中的<code>__repr__</code>),然后每次在解释器中计算任何对象或将其打印出来时,都会得到您想要的结果。在</p>
<p>不过,有几点要记住:</p>
<p>Python既有<code>__str__</code>(如果你<code>print</code>一个对象,你会得到什么)和<code>__repr__</code>(如果你只是在交互提示下计算一个对象,你会得到什么)。通常,前者是一个很好的人类可读的表示,而后一个要么是<code>eval</code>-可(或可在交互提示中键入),要么是简洁的尖括号形式,它只给你足够的区分对象的类型和标识。在</p>
<p>那只是个惯例而不是一条规则,所以你可以随意打破它。但是,如果您要打破它,您可能仍然需要使用<code>str</code>/<code>repr</code>的区别,例如,使<code>repr</code>提供所有内部构件的完整转储,而<code>str</code>只显示有用的公共值。在</p>
<p>更严重的是,您必须考虑<code>repr</code>值是如何组成的。例如,如果您<code>print</code>或<code>repr</code>a<code>list</code>,那么您实际上得到<code>'[' + ', '.join(map(repr, item))) + ']'</code>。如果使用多行<code>repr</code>,这看起来会很奇怪。如果你使用任何一种漂亮的打印机来缩进嵌套的集合,就像IPython内置的那样,情况会更糟。结果可能不会是不可读的,它只会打败漂亮的打印机提供的好处。在</p>
<p>至于你想展示的具体内容:这很简单。像这样:</p>
<pre><code>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)
</code></pre>
<p>正确地证明属性名是这里最难的部分。(我可能搞错了,因为这是未经测试的代码……)其他的事情要么简单,要么有趣(用不同的过滤器来<code>getmembers</code>看看它们做了什么)。在</p>