简体   繁体   English

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

[英]IPython REPL error when using a custom __getattribute__

I have a custom __getattribute__ that is supposed to modify the returned value if the member is not a method (thus an attribute). 我有一个自定义__getattribute__ ,如果成员不是方法(因此是属性),则应该修改返回的值。 Assume all the attributes (self.a, self.b, etc) are str . 假设所有属性(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

I get an error in IPython when getting the representation of instances of class A but I don't understand why. 在获取类A的实例的表示时,我在IPython中遇到错误,但我不明白为什么。
For example: 例如:

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

But print(a) works fine 但是print(a)可以正常工作

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

Note: In the plain Python REPL it seems to be working properly. 注意:在普通的Python REPL中,它似乎工作正常。

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

In IPython, the standard output displays a pretty printed __repr__ representation of the object. 在IPython中,标准输出显示该对象的漂亮的__repr__表示形式。 While in Python, the standard output print s the __repr__ representation of the object, in short, print(repr(obj)) . 在Python中,标准输出print是对象的__repr__表示形式,简称print(repr(obj))

Python : Python

As you will notice below, the standard output in Python is the same as calling the print() function on the repr(a) . 您将在下面注意到,Python中的标准输出与在repr(a)上调用print()函数相同。 repr(a) is the object representation of a and invokes __repr__ when called. 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的

IPython, on the other hand, has its own implementation of displaying the standard output and pretty prints the __repr__ for the object before displaying. 另一方面,IPython具有自己的显示标准输出的实现,并在显示之前漂亮地打印了对象的__repr__ The pretty printing of the object for stdout happens in the pretty() function located in the RepresentationPrinter class in ../IPython/lib/pretty.py : 用于stdout的对象的漂亮打印发生在../IPython/lib/pretty.py中 RepresentationPrinter类中的pretty()函数中:

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

However, before pretty() is called, IPython calls the __call__(self,obj) method in ../IPython/core/formatters.py . 但是,在调用pretty()之前,IPython会在../IPython/core/formatters.py中调用__call__(self,obj)方法。 You will notice this as the topmost stack in the Traceback Exception error and the pretty() function above is called on line 702: 您会注意到这是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)

In the pretty() function above the _safe_getattr(obj, '__class__', None) or type(obj) line is interesting. _safe_getattr(obj, '__class__', None) or type(obj)行上方的pretty()函数中,很有趣。 The definition for this function says it is a safe implementation of getarr() which means that if an Exception is raised while getting the attribute for this object, it will return None : 此函数的定义表示这是getarr()的安全实现,这意味着如果在获取此对象的属性时引发了Exception,它将返回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

In the pretty() function, the value of _safe_getattr(obj, '__class__', None) or type(obj) is stored in obj_class . pretty()函数中, _safe_getattr(obj, '__class__', None) or type(obj)值存储在obj_class Later, in the same function, this variable is passed to _get_mro() . 稍后,在同一函数中,此变量将传递给_get_mro() This is shown in the second stack of the Traceback Exception on line 382: 这在第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

The job of _get_mro(obj_class) is to get an MRO(method resolution order) for obj_class . 的工作_get_mro(obj_class)是得到一个MRO(方法解析顺序) obj_class In Python 3, all classes are new style and have a __mro__ attribute. 在Python 3中,所有类都是新样式,并具有__mro__属性。 However, old style class definitions have been retained for backward compatibility and do not have this attribute. 但是,保留了旧样式类定义是为了向后兼容,并且没有此属性。 Your class is defined with the old-style syntax. 您的类是使用旧式语法定义的。 You can read more about NewClass v/s OldClass here . 您可以在此处阅读有关NewClass v / s OldClass的更多信息。 In the definition for _get_mro(obj_class) , your code falls into the try block for old-style syntax and errors out. _get_mro(obj_class)的定义中,您的代码属于try块,以获取旧式语法和错误信息。 This is the latest and the bottommost stack in the Traceback Exception: 这是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:

So what's happening : 所以发生了什么

Let us use all that we have learned and understand what is really happening behind the scenes. 让我们使用我们所学到的所有知识,了解幕后真正发生的事情。 I've modified your code below to make use of the above functions from the IPython module. 我在下面修改了您的代码,以利用IPython模块中的上述功能。 You should try this on the IPython console/Jupyter notebook: 您应该在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

I've defined an attribute test_attr which stores a string 'test_string' as you've mentioned all attributes are str . 我已经定义了一个属性test_attr ,它存储了一个字符串'test_string',正如您提到的,所有属性都是str The getattr_res variable stores the value for invoking _safe_getattr(a, 'test_attr') which is same as invoking getattr(a, 'test_attr') which basically calls __getattribute__ in your code: getattr_res变量存储用于调用_safe_getattr(a, 'test_attr')的值,该值与调用_safe_getattr(a, 'test_attr')值相同getattr(a, 'test_attr')后者基本上在代码中调用__getattribute__

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

As you will observe getattr_res is of type string and string objects do not have the __mro__ attribute. 如您getattr_res的类型为字符串,字符串对象没有__mro__属性。 We should have a class object to get the 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__'

This Exception looks familiar isn't it? 这个异常看起来很熟悉,不是吗? The call to IPython's _safe_getattr(obj, '__class__', None) function invokes __getattribute__ in your code which returns a string object which does not have a __mro__ attribute and even if _get_mro(obj_class) attempts execution in the try block we get an AttributeError because we know that str objects do not have a '__name__' attribute: 调用IPython的_safe_getattr(obj, '__class__', None) __getattribute__ _safe_getattr(obj, '__class__', None) __getattribute__ _safe_getattr(obj, '__class__', None)函数会在您的代码中调用__getattribute__ ,该函数会返回一个不具有__mro__属性的字符串对象,即使_get_mro(obj_class)尝试在try块中执行,我们_get_mro(obj_class)得到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__'

How do we fix this : 我们如何解决这个问题

In IPython, it is possible to add our own pretty printing rules for the objects in our class. 在IPython中,可以为类中的对象添加我们自己的漂亮打印规则。 Inspired by the docs for module lib.pretty , I've modified the code and defined a _repr_pretty_(self, p, cycle) function that is explicitly called in __getattribute__ (after the type check) to display the object in the desired format. 模块lib.pretty文档启发,我修改了代码并定义了一个_repr_pretty_(self, p, cycle)函数,该函数在__getattribute__ (类型检查后)中显式调用,以所需格式显示对象。 If the attribute is a string, it simply returns the string again: 如果属性是字符串,则只需再次返回字符串:

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'

Note that cycle=False when calling _repr_pretty_() in __getattribute__(self, k) because attr is not an iterable. 注意,在__getattribute__(self, k)调用_repr_pretty_()时, cycle=False __getattribute__(self, k)因为attr是不可迭代的。

In general, it is recommended to add a __repr__ function to your class as it clearly shows the representation of objects in your class. 通常,建议在类中添加__repr__函数,因为它可以清楚地显示类中对象的表示形式。 You can read more about it here . 您可以在此处了解更多信息。

Conclusion : IPython standard output implements its own pretty printed __repr__ as opposed to the Python interpreter that utilizes the built-in repr() function for stdout. 结论 :与使用内置repr()函数用于stdout的Python解释器相反,IPython标准输出实现了自己漂亮的__repr__ In order to change the behavior of the stdout on IPython, one can add a _repr_pretty_() function to their class to display output as desired. 为了更改IPython上stdout的行为,可以向其类添加_repr_pretty_()函数以根据需要显示输出。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM