简而言之,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) http://docs.python.org/2/library/functions.html#dir.
为了尽最大努力针对“一切”进行编程使用,请使用inspect.getmembers(spam) http://docs.python.org/2/library/inspect.html#inspect.getmembers。 (尽管实际上实现只是一个包装dir
在 CPython 2.x 中,它could做得更多——事实上在 CPython 3.2+ 中也是如此。)
这些都可以处理各种各样的事情__dict__
不能,并且可能会跳过__dict__
但你不想看到。但它们本质上仍然是不完整的。
无论您使用什么,获取值和键都很容易。如果您正在使用__dict__
or getmembers
,这是微不足道的;这__dict__
通常是dict
,或者行为足够接近的东西dict
为了您的目的,并且getmembers
显式返回键值对。如果您正在使用dir
,你可以得到一个dict
非常简单地:
{key: getattr(spam, key) for key in dir(spam)}
最后一件事:“对象”是一个有点含糊的术语。它可以意味着“派生自的类的任何实例object
”、“类的任何实例”、“新式类的任何实例”或“任何类型的任何值”(模块、类、函数等)。您可以使用dir
and getmembers
几乎任何事情;文档中描述了这意味着什么的确切细节。
最后一件事:你可能会注意到getmembers
返回类似的东西('__str__', <method-wrapper '__str__' of Spam object at 0x1066be790>
),您可能对此不感兴趣。由于结果只是名称-值对,如果您只想删除__dunder__
方法,_private
变量等等,这很简单。但通常,您想要过滤“成员类型”。这getmembers
函数接受一个过滤器参数,但文档并没有很好地解释如何使用它(最重要的是,期望您了解描述符的工作原理)。基本上,如果你想要一个过滤器,通常是callable
, lambda x: not callable(x)
, or a 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 函数,或者仅使用它创建一个 % 宏,或者将其保留为常规函数并显式调用它。
在评论中,您询问是否可以将其打包成一个__repr__
函数而不是尝试创建 %magic 函数或其他函数。
如果您已经让所有类都继承自单个根类,那么这是一个好主意。可以单独写一个__repr__
适用于您的所有课程(或者,如果它适用于 99% 的课程,您可以覆盖它__repr__
在另外 1% 中),然后每次你在解释器中评估任何对象或打印它们时,你都会得到你想要的。
但是,需要记住以下几点:
Python 两者都有__str__
(如果你得到什么print
一个物体)和__repr__
(如果你只是在交互式提示下评估一个对象,你会得到什么)是有原因的。通常,前者是一个很好的人类可读的表示,而后者是eval
-able(或可在交互式提示中输入),或者简洁的尖括号形式,足以让您区分对象的类型和身份。
这只是一个惯例而不是规则,所以你可以随意打破它。但是,如果您are要打破它,你可能仍然想利用str
/repr
区别——例如,使repr
为您提供所有内部结构的完整转储,同时str
仅显示有用的公共价值观。
更严重的是,你必须考虑如何repr
值是组成的。例如,如果您print
or 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
看看他们做了什么)。