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

时间:2013-03-18 22:05:34

标签: 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

会显示如下内容:

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

Attributes:
_x: 5
 x: 105

Method Attributes:
myFunction(self, y)

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

3 个答案:

答案 0 :(得分:6)

简短的回答是,无法在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派生的类的任何实例”,“类的任何实例”,“任何新类型的实例”或“任何类型的任何值”(模块,课程,职能等)。你几乎可以使用dirgetmembers;文档中描述了具体含义的详细信息。

一个甚至是最后一件事:您可能会注意到getmembers返回了您可能不感兴趣的内容,例如('__str__', <method-wrapper '__str__' of Spam object at 0x1066be790>)。因为结果只是名称 - 值对,如果您只想删除__dunder__方法,_private变量等,这很容易。但通常,您希望过滤“类型成员”。 getmembers函数采用过滤器参数,但是文档并没有很好地解释如何使用它(并且,最重要的是,期望您了解描述符的工作原理)。基本上,如果您想要一个过滤器,通常callablelambda x: not callable(x)lambdainspect.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 - 能够(或可以打字到交互式提示),或简洁的角括号形式,只给你一个足以区分对象的类型和身份。

这只是一个惯例而不是规则,所以你可以随意打破它。但是,如果 要破解它,您可能仍然希望使用str / repr区别 - 例如,make repr会给你一个完全转储所有内部,而str只显示有用的公共值。

更严重的是,您必须考虑如何组成repr值。例如,如果您printrepr 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看看他们做了什么)。

答案 1 :(得分:1)

通过实施_repr_pretty_,我能够通过IPython(至少粗略地)实现我想要的目标:

def get_public_variables(obj):
    from inspect import getmembers
    return [(name, value) for name, value in
            getmembers(obj, lambda x: not callable(x)) if
            not name.startswith('__')]


class MySuperClass(object):
    def _repr_pretty_(self, p, cycle):
        for (name, value) in get_public_variables(self):
            f = '{:>12}{} {:<} \n'
            line = f.format(str(name), ':', str(value))
            # p.text(str(name) + ': ' + str(value) + '\n')
            p.text(line)

class MyClass(MySuperClass):
    _x = 5

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

让我离开:

mc = MyClass()
mc
Out[15]: 
          _x: 5 
           x: 105 
显然,在空白等方面有一些微调。但这大致是我想要完成的事情

答案 2 :(得分:0)

您可以使用obj.__dict__访问对象的实例变量,例如:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age


d1 = Dog("Fido", 7)

for key, val in d1.__dict__.items():
    print(key, ": ", val)

输出:

age :  7
name :  Fido

您可能会发现这对于可能包含大量实例变量和方法的真实对象不起作用。