使用自定义__getattribute__时的IPython REPL错误

问题描述 投票:0回答:1

我有假设如果构件是不是方法(因此一个属性)修改返回值的自定义__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>
python ipython getattribute
1个回答
1
投票

在IPython中,标准输出显示对象的一个​​相当印刷__repr__表示。而在Python,标准输出prints对象的__repr__表示,总之print(repr(obj))

蟒蛇:

正如您将在下面看到,在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__显示之前。标准输出对象的漂亮印花发生在位于../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)方法。你会发现这是在回溯异常错误的最顶层的栈和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的返回跟踪异常的第二堆栈:

    ~/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)的工作是让一个MRO(方法解析顺序)obj_class。在Python 3,所有的类都是新的风格,有一个__mro__属性。然而,旧的样式类定义已被保留向后兼容,没有这个属性。你的类与旧风格的语法定义。你可以阅读更多有关的NewClass V / S OldClass here。在定义_get_mro(obj_class),你的代码属于旧风格的语法和错误出try块。这是最新的和回溯异常最底部的栈:

  ~/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_string”如你所提到的所有属性都test_attr属性str。该getattr_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)try块中,我们得到一个AttributeError因为我们知道,str对象没有一个'__name__'属性试图执行一个字符串对象:

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__'

我们如何解决这个问题:

在IPython中,可以添加我们自己的漂亮的印刷规则,我们班的对象。由docs for module lib.pretty启发,我已经修改了代码和限定了在_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不是一个迭代。

在一般情况下,推荐一个__repr__功能添加到您的类,因为它清楚地显示在您的类对象的代表性。你可以阅读更多关于它here

结论:IPython的标准输出实现,而不是Python解释器,利用内置的__repr__功能为标准输出自己的漂亮的印刷repr()。为了改变上IPython的标准输出的行为,可以添加一个_repr_pretty_()函数到其类根据需要来显示输出。

© www.soinside.com 2019 - 2024. All rights reserved.