是否有可能像Matlab一样在IPython中显示对象实例变量呢?

use*_*844 5 python matlab ipython

我正试图从Matlab转向Python.虽然神奇?在IPython中很好,Matlab的一个非常好的功能是你可以在命令行上看到(通过省略;)相关对象的实例变量(在Matlab中称为属性).这可能在python中(我想通过IPython)?

理想情况下这样的类:

class MyClass(object):
    _x = 5

    @property
    def x(self):
        return self._x + 100

    @x.setter
    def x(self, value):
        self._x = value + 1

    def myFunction(self, y):
        return self.x ** 2 + y
Run Code Online (Sandbox Code Playgroud)

会显示如下:

mc = Myclass()
mc
<package.MyClass> <superclass1> <superclass2>

Attributes:
_x: 5
 x: 105

Method Attributes:
myFunction(self, y)
Run Code Online (Sandbox Code Playgroud)

这是否可以通过覆盖类的打印方法(如果这样的东西退出)?或者通过ipython中的魔术方法?

aba*_*ert 7

简短的回答是,无法在Python中获取对象的所有属性的列表,因为属性可以动态生成.举一个极端的例子,考虑这个类:

>>> class Spam(object):
...     def __getattr__(self, attr):
...         if attr.startswith('x'):
...             return attr[1:]
>>> spam = Spam()
>>> spam.xeggs
'eggs'
Run Code Online (Sandbox Code Playgroud)

即使解释器可能有人找出所有属性的列表,该列表也是无限的.

对于简单的课程,spam.__dict__通常是足够好的.它不处理动态属性,__slots__基于属性,类属性,C扩展类,从上面的大多数继承的属性,以及各种其他东西.但它至少是某种东西 - 有时,它是你想要的东西.对于第一个近似值,它正是您明确指定__init__或稍后指定的内容,而不是其他内容.

为了尽可能地针对人类可读性的"一切",请使用dir(spam).

为了尽可能地针对程序化使用的"一切",请使用inspect.getmembers(spam).(虽然实际上实现只是dirCPython 2.x中的一个包装,它可以做得更多 - 事实上在CPython 3.2+中也是如此.)

这些都会处理各种__dict__不可能的事情,并且可能会跳过__dict__您不想看到的内容.但它们本质上仍然是不完整的.

无论您使用什么,获取值和键都很容易.如果你正在使用__dict__getmembers,它是微不足道的; 的__dict__是,通常情况下,无论是dict,还是一些行为足够接近dict你的目的,并getmembers明确地返回键-值对.如果您正在使用dir,您可以dict轻松获得:

{key: getattr(spam, key) for key in dir(spam)}
Run Code Online (Sandbox Code Playgroud)

最后一件事:"对象"是一个含糊不清的术语.它可以表示"派生自类的object任何实例","类的任何实例","任何新样式类的实例",或"任何类型的任何值"(模块,类,函数等) ).您可以使用dirgetmembers对任何事情; 文档中描述了这意味着什么的具体细节.

一个甚至是最后一件事:你可能会注意到它会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('_')]
Run Code Online (Sandbox Code Playgroud)

你可以把它变成一个自定义的IPython%魔术函数,或者只是从中创建一个%宏,或者只是把它作为常规函数并明确地调用它.


在评论中,您询问是否可以将其打包到__repr__函数中,而不是尝试创建%magic函数或其他任何函数.

如果您已经从一个根类继承了所有类,那么这是一个好主意.您可以编写一个__repr__适用于所有类的单个(或者,如果它适用于99%的类,您可以__repr__在另一个1%中覆盖它),然后每次在解释器中评估任何对象或打印时他们出去了,你会得到你想要的.

但是,要记住以下几点:

Python有两个__str__(如果你print是一个对象你得到的)和__repr__(如果你只是在交互式提示符下评估一个对象,你会得到什么).通常,前者是一个很好的人类可读的表示形式,而后者是eval可用的(或可打字到交互式提示),或简洁的角括号形式,使你足以区分类型和对象的身份.

这只是一个惯例而不是规则,所以你可以随意打破它.不过,如果你要打破它,你可能还是要利用的str/ repr区别,例如,使repr给你所有的内部的一个完整的转储,而str只显示了有用的公共价值.

更严重的是,您必须考虑如何repr组成值.例如,如果你print还是repr一个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)
Run Code Online (Sandbox Code Playgroud)

右对齐属性名称是这里最难的部分.(我可能错了,因为这是未经测试的代码......)其他一切都很简单,或者很有趣(使用不同的过滤器getmembers来看看他们做了什么).