使用自定义__getattribute__时出现IPython REPL错误

时间:2019-02-02 15:40:19

标签: python ipython getattribute

我有一个自定义__getattribute__,如果该成员不是方法(因此是属性),则应该修改返回的值。假设所有的属性(self.a,self.b等)是str

class A:
    def __init__(self):
        self.a = 1

    def __getattribute__(self, k):
        attr = object.__getattribute__(self, k)
        if type(attr) != types.MethodType:
            return '{}!'.format(attr)
        return attr

我得到的类的实例的表示时得到IPython的错误A,但我不明白为什么。
例如:

In [26]: a = A()
In [27]: a
Out[27]: ---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/IPython/core/formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)
    703             printer.flush()
    704             return stream.getvalue()

~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in pretty(self, obj)
    380             #   1) a registered printer
    381             #   2) a _repr_pretty_ method
--> 382             for cls in _get_mro(obj_class):
    383                 if cls in self.type_pprinters:
    384                     # printer registered in self.type_pprinters

~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in _get_mro(obj_class)
    318         # Old-style class. Mix in object to make a fake new-style class.
    319         try:
--> 320             obj_class = type(obj_class.__name__, (obj_class, object), {})
    321         except TypeError:
    322             # Old-style extension type that does not descend from object.

AttributeError: 'str' object has no attribute '__name__'

但是print(a)工作正常

In [33]: print(a)
<__main__.A object at 0x10c566390>

注:在普通的Python REPL这似乎是正常工作

>>> a = A()
>>> a
<__main__.A object at 0x1032b9320>

1 个答案:

答案 0 :(得分:1)

在IPython中,标准输出显示对象的漂亮印刷的__repr__表示形式。在Python中,标准输出print是对象的__repr__表示形式,简称print(repr(obj))

Python

您将在下面注意到,Python中的标准输出与在print()上调用repr(a)函数相同。 repr(a)a的对象表示形式,并在调用时调用__repr__

>>> a = A()
>>> a
<__main__.A object at 0x000000D886391438>
>>> repr(a)
'<__main__.A object at 0x000000D886391438>'
>>> print(repr(a))
<__main__.A object at 0x000000D886391438>

IPython

另一方面,IPython具有自己的显示标准输出的实现,并在显示之前漂亮地打印了对象的__repr__。用于stdout的对象的漂亮打印发生在 ../ IPython / lib / pretty.py

pretty()类中的RepresentationPrinter函数中
def pretty(self, obj):
        """Pretty print the given object."""
        obj_id = id(obj)
        cycle = obj_id in self.stack
        self.stack.append(obj_id)
        self.begin_group()
        try:
            obj_class = _safe_getattr(obj, '__class__', None) or type(obj)
        #<---code--->

但是,在调用pretty()之前,IPython会在 ../ IPython / core / formatters.py 中调用__call__(self,obj)方法。您会注意到这是Traceback Exception错误中的最高堆栈,并且上面的pretty()函数在第702行被调用:

AttributeError                            Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/IPython/core/formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)

pretty()行上方的_safe_getattr(obj, '__class__', None) or type(obj)函数中,很有趣。该函数的定义说这是getarr()的安全实现,这意味着如果在获取该对象的属性时引发了异常,它将返回None

def _safe_getattr(obj, attr, default=None):
"""Safe version of getattr.

Same as getattr, but will return ``default`` on any Exception,
rather than raising.
"""
    try:
        return getattr(obj, attr, default)
    except Exception:
        return default

pretty()函数中,_safe_getattr(obj, '__class__', None) or type(obj)的值存储在obj_class中。稍后,在同一函数中,此变量将传递到_get_mro()。这在第382行的Traceback Exception的第二个堆栈中显示:

    ~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in pretty(self, obj)
    380             #   1) a registered printer
    381             #   2) a _repr_pretty_ method
--> 382             for cls in _get_mro(obj_class):
    383                 if cls in self.type_pprinters:
    384                     # printer registered in self.type_pprinters

_get_mro(obj_class)的工作是为obj_class获取MRO(方法解析顺序)。在Python 3中,所有类都是新样式,并具有__mro__属性。但是,保留了旧样式类定义是为了向后兼容,并且没有此属性。您的类是使用旧式语法定义的。您可以阅读有关NewClass v / s OldClass here的更多信息。在_get_mro(obj_class)的定义中,您的代码进入了旧式语法的try块中,并且出错了。这是Traceback异常中最新的和最底层的堆栈:

  ~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in _get_mro(obj_class)
    318         # Old-style class. Mix in object to make a fake new-style class.
    319         try:
--> 320             obj_class = type(obj_class.__name__, (obj_class, object), {})
    321         except TypeError:

那是怎么回事

让我们利用我们所学的所有知识,了解幕后真正发生的事情。我在下面修改了您的代码,以利用IPython模块中的上述功能。您应该在IPython控制台/ Jupyter笔记本上尝试一下:

    In [1]: from IPython.lib.pretty import _safe_getattr
       ...: from IPython.lib.pretty import pretty
       ...: from IPython.lib.pretty import _get_mro
       ...:
       ...: class A:
       ...:     def __init__(self):
       ...:         self.a = 1
       ...:
       ...:     def __getattribute__(self, k):
       ...:         attr = object.__getattribute__(self, k)
       ...:         if type(attr) != types.MethodType:
       ...:             return '{}!'.format(attr)
       ...:         return attr
       ...:
       ...: a = A()
       ...: a.test_attr = 'test_string'
    In [2]: getattr_res = _safe_getattr(a, 'test_attr') or type(a)
    In [6]: getattr_res
    Out[6]: 'test_string!'
    In [10]: getattr_res == getattr(a, 'test_attr')
    Out[10]: True

我已经定义了一个属性test_attr,它存储了一个字符串'test_string',正如您提到的,所有属性都是strgetattr_res变量存储用于调用_safe_getattr(a, 'test_attr')的值,该值与调用getattr(a, 'test_attr')并在代码中基本上调用__getattribute__的值相同。

In [13]: a.__getattribute__('test_attr')
Out[13]: 'test_string!'

您将看到getattr_res的类型为字符串,并且字符串对象没有__mro__属性。我们应该有一个类对象来获取MRO:

In [14]: type(getattr_res)
Out[14]: str
In [15]: _get_mro(getattr_res)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-d0ae02b5a6ac> in <module>()
----> 1 _get_mro(getattr_res)

C:\ProgramData\Anaconda3\lib\site-packages\IPython\lib\pretty.py in _get_mro(obj_class)
    316         # Old-style class. Mix in object to make a fake new-style class.
    317         try:
--> 318             obj_class = type(obj_class.__name__, (obj_class, object), {})
    319         except TypeError:
    320             # Old-style extension type that does not descend from object.

AttributeError: 'str' object has no attribute '__name__'

这个异常看起来很熟悉,不是吗?调用IPython的_safe_getattr(obj, '__class__', None)函数会在您的代码中调用__getattribute__,该返回的字符串对象不具有__mro__属性,即使_get_mro(obj_class)尝试在{{1 }}块,我们得到一个try,因为我们知道AttributeError对象没有str属性:

'__name__'

我们如何解决此问题

在IPython中,可以为类中的对象添加我们自己的漂亮打印规则。受docs for module lib.pretty的启发,我修改了代码并定义了In [16]: getattr_res.__name__ --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-16-0d8ba2c5af23> in <module>() ----> 1 getattr_res.__name__ AttributeError: 'str' object has no attribute '__name__' 函数,该函数在_repr_pretty_(self, p, cycle)中显式调用(在类型检查之后),以所需的格式显示对象。如果该属性是字符串,则只需再次返回该字符串:

__getattribute__

请注意,In [20]: class A: ...: def __init__(self): ...: self.a = 1 ...: ...: def __getattribute__(self, k): ...: attr = object.__getattribute__(self, k) ...: if type(attr) != types.MethodType: ...: return self._repr_pretty_(attr, cycle=False) ...: return attr ...: ...: def _repr_pretty_(self, p, cycle): ...: if cycle: ...: p.text('MyList(...)') ...: else: ...: if isinstance(p,str): ...: return p ...: return p.text(repr(self) + '!') In [21]: a = A() In [22]: a Out[22]: <__main__.A object at 0x0000005E6C6C00B8>! In [24]: a.test = 'test_string' In [25]: a.test Out[25]: 'test_string' 不可迭代,因此在cycle=False中调用_repr_pretty_()__getattribute__(self, k)

通常,建议向类中添加一个attr函数,因为它可以清楚地显示类中对象的表示形式。您可以详细了解here

结论:与使用内置__repr__函数用于stdout的Python解释器相反,IPython标准输出实现了自己漂亮的打印__repr__。为了更改IPython上stdout的行为,可以向其类添加repr()函数以根据需要显示输出。