关于python:可以像在Matlab中一样在IPython中显示对象实例变量吗?

Is it possible to display an objects instance variables in IPython like Matlab does?

我正试图从matlab转向python。而魔法呢?在ipython中,Matlab的一个非常好的特性是,您可以在命令行(通过省略;)上看到相关对象的实例变量(在Matlab中称为属性)。这在Python中是可能的吗(我猜是通过i python实现的)?

理想情况下是这样的一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

将显示如下内容:

1
2
3
4
5
6
7
8
9
10
mc = Myclass()
mc
<package.MyClass> <superclass1> <superclass2>

Attributes:
_x: 5
 x: 105

Method Attributes:
myFunction(self, y)

是否可以通过重写类的print方法(如果存在这样的东西)来实现?或是通过伊普敦的魔法方法?


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

1
2
3
4
5
6
7
>>> 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:好的。

1
{key: getattr(spam, key) for key in dir(spam)}

最后一件事:"对象"是一个含糊不清的术语。它可以是"派生自object的类的任何实例"、"类的任何实例"、"新样式类的任何实例"或"任何类型的任何值"(模块、类、函数等)。你可以在任何事情上使用dirgetmembers;这意味着什么的确切细节在文档中描述。好的。

最后一件事是:你可能会注意到getmembers会返回你可能不感兴趣的东西,比如('__str__', 。由于结果只是名称-值对,如果您只想删除__dunder__方法、_private变量等,这很容易。但通常情况下,您希望筛选"某种成员"。getmembers函数接受一个过滤参数,但文档并不能很好地解释如何使用它(除此之外,还希望您了解描述符是如何工作的)。基本上,如果您想要一个过滤器,它通常是由inspect.isfoo函数组合而成的callablelambda x: not callable(x)lambda。好的。

所以,这很常见,你可能想把它写成一个函数:好的。

1
2
3
4
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__函数,而不是试图创建一个%魔力函数或其他什么函数。好的。

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

但是,要记住的一些事情:好的。

由于某种原因,python同时拥有__str__(如果你是print)和__repr__(如果你只是在交互提示下评估一个对象,你会得到什么)。通常,前者是一种很好的人类可读的表示,而后者是一种既可以用eval表示(也可以用交互式提示进行打字),也可以是一种简洁的尖括号形式,使您能够很好地区分对象的类型和标识。好的。

这只是惯例而不是规则,所以你可以随意打破它。但是,如果你打算打破它,你可能仍然想利用strrepr的区别,例如,让repr给你一个完整的内部信息,而str只显示有用的公共价值。好的。

更严重的是,您必须考虑repr值是如何组成的。例如,如果您使用printrepra list,您实际上可以使用'[' + ', '.join(map(repr, item))) + ']'。使用多行repr会让这看起来很奇怪。如果你使用任何一种漂亮的打印机来缩进嵌套的集合,比如内置在IPython中的集合,情况会更糟。结果可能不会是不可读的,它只会破坏漂亮的打印机所提供的好处。好的。

至于你想展示的具体内容:这很容易。像这样:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 '
'
.join(lines)

正确地证明属性名是最困难的部分。(我可能搞错了,因为这是未经测试的代码…)其他的事情要么简单,要么有趣(对getmembers使用不同的过滤器来查看它们的功能)。好的。好啊。


通过实现_repr_pretty_,我能够用ipython(至少大致上)实现我想要的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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}{} {:<}
'

            line = f.format(str(name), ':', str(value))
            # p.text(str(name) + ': ' + str(value) + '
')
            p.text(line)

class MyClass(MySuperClass):
    _x = 5

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

给了我:

1
2
3
4
5
mc = MyClass()
mc
Out[15]:
          _x: 5
           x: 105

很明显,在空白等方面还需要进行一些微调,但这正是我试图完成的工作。


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

1
2
3
4
5
6
7
8
9
10
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)

输出:

1
2
age :  7
name :  Fido

但是,您可能会发现,对于可能具有大量实例变量和方法的实际对象,这并不适用。