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

2024-09-29 19:36:39 发布

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

我有一个自定义的__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

在获取类A实例的表示时,我在IPython中遇到了一个错误,但我不明白为什么。
例如:

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>

Tags: inselfobjreturnobjectlibtypeipython
1条回答
网友
1楼 · 发布于 2024-09-29 19:36:39

在IPython中,标准输出显示对象的一个漂亮的__repr__表示。在Python的输出中。你知道吗

Python

正如您将在下面注意到的,Python中的标准输出与在repr(a)上调用print()函数相同。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有自己的实现来显示标准输出,并在显示之前漂亮地打印对象的__repr__。stdout对象的漂亮打印发生在pretty()函数中,该函数位于./IPython/lib中的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中的__call__(self,obj)方法/格式化程序.py。您将注意到这是回溯异常错误中最顶层的堆栈,并且在第702行调用了上面的pretty()函数:

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)行是有趣的。如果这个函数的定义是安全的,那么它将返回一个异常:

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)的任务是获取obj_class的MRO(方法解析顺序)。在python3中,所有类都是新样式,并具有__mro__属性。但是,为了向后兼容,保留了旧样式类定义,并且没有此属性。您的类是用旧式语法定义的。您可以阅读有关NewClass v/s OldClasshere的更多信息。在_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:

发生了什么事

让我们利用所学的一切,了解幕后真正发生的事情。我修改了上面的ipythi模块中的函数来使用下面的代码。您应该在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\u 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是string类型,而string对象没有__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'

请注意,调用__getattribute__(self, k)中的_repr_pretty_()cycle=False,因为attr不是iterable。你知道吗

一般来说,建议在类中添加__repr__函数,因为它清楚地显示了对象i的表示形式在你们班上。您可以阅读更多关于它的信息here。你知道吗

结论:IPython标准输出实现了自己漂亮的__repr__,而Python解释器使用stdout的内置repr()函数。为了改变IPython上stdout的行为,可以向它们的类中添加_repr_pretty_()函数来显示所需的输出。你知道吗

相关问题 更多 >

    热门问题