简短的回答是,没有办法在 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)
. (虽然实际上该实现只是dir
CPython 2.x 中的一个包装器,但它可以做更多事情——事实上在 CPython 3.2+ 中也可以。)
它们都可以处理各种__dict__
无法处理的事情,并且可能会跳过那些__dict__
你不想看到的事情。但它们本质上仍然是不完整的。
无论您使用什么,获取值和键都很容易。如果您使用__dict__
or getmembers
,那很简单;__dict__
通常,它是 a或dict
与 a 足够接近的东西以dict
供您使用,并getmembers
显式返回键值对。如果你正在使用dir
,你可以dict
很容易地得到:
{key: getattr(spam, key) for key in dir(spam)}
最后一件事:“对象”是一个有点模棱两可的术语。它可以表示“派生自的类的object
任何实例”、“类的任何实例”、“新式类的任何实例”或“任何类型的任何值”(模块、类、函数等) .)。你几乎可以在任何东西上使用dir
和getmembers
;文档中描述了这意味着什么的确切细节。
最后一件事:您可能会注意到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 %magic 函数,或者只用它制作一个 %macro ,或者把它作为一个常规函数并显式调用它。
在评论中,您询问是否可以将其打包成一个__repr__
函数,而不是尝试创建一个 %magic 函数或其他任何东西。
如果您已经让所有类都继承自一个根类,那么这是一个好主意。您可以编写一个__repr__
适用于所有类的单曲(或者,如果它适用于其中 99% 的类,则可以覆盖__repr__
其他 1% 的类),然后每次在解释器中评估任何对象或打印他们出去,你会得到你想要的。
但是,需要记住一些事项:
Python 有__str__
(如果你print
是一个对象,你会得到什么)和__repr__
(如果你只是在交互式提示下评估一个对象,你会得到什么)是有原因的。通常,前者是一种很好的人类可读表示,而后者是可以的eval
(或可输入到交互式提示中),或者是简洁的尖括号形式,足以让您区分类型和对象的身份。
这只是一个惯例而不是规则,所以你可以随意打破它。然而,如果你要打破它,你可能仍然想使用str
/repr
区别——例如,makerepr
给你一个完整的所有内部信息的转储,而str
只显示有用的公共值。
更严重的是,您必须考虑repr
值是如何组成的。例如,如果您print
或repr
a list
,您将有效地得到'[' + ', '.join(map(repr, item))) + ']'
. 使用 multi-line 看起来会很奇怪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
看看它们做了什么)。