【问题标题】:Is it possible to display an objects instance variables in IPython like Matlab does?是否可以像 Matlab 那样在 IPython 中显示对象实例变量?
【发布时间】:2013-03-18 22:05:34
【问题描述】:

我正在尝试从 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)

是否可以通过覆盖类的 print 方法(如果存在这种情况)来实现?还是通过 ipython 中的魔法方法?

【问题讨论】:

  • 尝试在对象名称后面加上一个点,然后双击 TAB,例如myobj.&lt;TAB&gt;&lt;TAB&gt;。这就是你要找的东西吗?
  • 这似乎只是列出了实例变量和方法,这很接近但并不完全。我也想看看实例变量的值...
  • 您定义属性的方式很奇怪——它是由类属性 (MyClass._x) 支持的每个实例属性……直到您第一次设置它,此时您创建了一个实例属性(mc._x) 隐藏类属性。这种行为实际上可能很有用,但我怀疑这是类成员和实例成员之间混淆的迹象。

标签: python matlab ipython


【解决方案1】:

简短的回答是,没有办法在 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__', &lt;method-wrapper '__str__' of Spam object at 0x1066be790&gt;) 之类的内容,您可能对此并不感兴趣。由于结果只是名称-值对,如果您只想删除__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 %magic 函数,或者只用它制作一个 %macro,或者把它作为一个常规函数并显式调用它。


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

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

不过,有几点需要牢记:

Python 同时拥有__str__(如果您使用print 一个对象会得到什么)和__repr__(如果您只是在交互式提示下评估一个对象会得到什么)是有原因的。通常,前者是一种很好的人类可读表示,而后者是 eval-able(或 typable-into-the-interactive-prompt)或简洁的尖括号形式,足以让您区分对象的类型和身份。

这只是一个约定而不是规则,因此您可以随意打破它。但是,如果您打算破坏它,您可能仍想利用 str/repr 的区别——例如,让 repr 为您提供所有内部结构的完整转储,而str 仅显示有用的公共值。

更严重的是,您必须考虑repr 值是如何组成的。例如,如果您将printreprlist 联系起来,那么您实际上会得到'[' + ', '.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 玩不同的过滤器,看看它们做了什么)。

【讨论】:

  • inspect.getmembers(spam) 似乎非常接近我正在寻找的内容(除了列表中的前 18 个条目)。是否可以覆盖该类的打印方法,以便我得到(新编辑的)原始问题中描述的行为?
  • ...而不是我的意思是 ipython 魔术函数。那么有没有办法让它成为默认行为
  • 我可以写一个超类来覆盖__repr__ 来完成我所描述的事情,然后确保我的所有子类都继承自这个超类吗?
  • 是的,如果你想为你的所有类创建一个超类,这是一个很好的解决方案。您可能需要考虑同时覆盖__repr____str__,这样您就可以在IPython 提示符处评估obj 时提供例如详细的内部信息,但在print 时提供更简洁和可读的信息,但除此之外,当然。
【解决方案2】:

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

显然,在空格等方面需要进行一些微调。但这大致是我想要完成的目标

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 它似乎也不包括在class Dog: 行和def __init__ 行之间声明的实例变量...
    • 当然这不适用于不使用__dict__的对象(例如,因为它们有__slots__,或者它们是用C实现的,或者它们有一个基类执行上述任一操作),或具有__dict__ 但也具有自定义__getattr__ 和朋友的对象,或者......
    • @user1507844:那些不是实例变量,它们是变量。 (但你是对的,它不会显示它们。)
    猜你喜欢
    • 1970-01-01
    • 2022-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多