有没有可能像Matlab那样在IPython中显示objects实例变量?

2024-10-01 11:35:31 发布

您现在位置:Python中文网/ 问答频道 /正文

我正试图从Matlab转向Python。而魔法呢?在ipythonisnice中,Matlab的一个非常好的特性是,您可以在命令行上(通过省略;)看到相关对象的实例变量(在Matlab中称为properties)。在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

会显示如下内容:

^{pr2}$

是否可以通过重写类的print方法(如果有这样的东西存在的话)?或者通过ipython的魔法方法?在


Tags: 对象实例方法命令行selfreturnvaluedef
3条回答

简而言之,在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__或更高版本中显式分配的内容,而没有其他内容。在

为了达到“一切”的最佳效果,可以使用^{}。在

为了尽最大努力将“一切”用于编程,请使用^{}。(尽管实际上该实现只是cpython2.x中dir的包装器,但它在cpython3.2+中可以做得更多,事实上也可以做得更多。)

它们都将处理__dict__不能处理的大量内容,并且可能会跳过__dict__中您不想看到的内容。但它们本质上还是不完整的。在

无论使用什么方法,获取值和键都很容易。如果您使用的是__dict__getmembers,那么这很简单;__dict__通常是dict,或者是与dict非常接近的东西,getmembers显式返回键值对。如果您正在使用dir,您可以很容易地得到一个dict

^{pr2}$

最后一件事:“object”是一个有点模棱两可的术语。它可以表示“从object派生的类的任何实例”、“类的任何实例”、“新样式类的任何实例”或“任何类型的任何值”(模块、类、函数等)。您可以在任何事情上使用dirgetmembers;文档中详细描述了这一点。在

还有最后一件事:您可能会注意到getmembers返回了('__str__', <method-wrapper '__str__' of Spam object at 0x1066be790>)之类的内容,而您可能对此并不感兴趣。由于结果只是名称-值对,如果您只想删除__dunder__方法、_private变量等,这很简单。但通常,你需要过滤“成员种类”。getmembers函数接受一个过滤器参数,但是文档没有很好地解释如何使用它(而且,除此之外,希望您理解描述符的工作原理)。基本上,如果你想要一个过滤器,它通常是callablelambda x: not callable(x),或者由inspect.isfoo函数组合而成的lambda。在

所以,这是很常见的,你可以把它写成一个函数:

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__函数,而不是尝试创建一个%magic函数之类的。在

如果您已经从一个根类继承了所有类,这是一个好主意。您可以编写一个对所有类都有效的__repr__(或者,如果它适用于99%的类,那么您可以重写其他1%中的__repr__),然后每次在解释器中计算任何对象或将其打印出来时,都会得到您想要的结果。在

不过,有几点要记住:

Python既有__str__(如果你print一个对象,你会得到什么)和__repr__(如果你只是在交互提示下计算一个对象,你会得到什么)。通常,前者是一个很好的人类可读的表示,而后一个要么是eval-可(或可在交互提示中键入),要么是简洁的尖括号形式,它只给你足够的区分对象的类型和标识。在

那只是个惯例而不是一条规则,所以你可以随意打破它。但是,如果您要打破它,您可能仍然需要使用str/repr的区别,例如,使repr提供所有内部构件的完整转储,而str只显示有用的公共值。在

更严重的是,您必须考虑repr值是如何组成的。例如,如果您printrepralist,那么您实际上得到'[' + ', '.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看看它们做了什么)。在

通过实现_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

给我的答案是:

^{pr2}$

很明显,在空白等方面还有一些微调要做,但这正是我试图完成的

您可以使用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)

输出:

^{pr2}$

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

相关问题 更多 >