IPython 使用自定义 __getattribute__ 时出现 REPL 错误
IPython REPL error when using a custom __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
在获取 class 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>
在 IPython 中,标准输出显示对象的漂亮打印 __repr__
表示。而在 Python 中,标准输出 print
是对象的 __repr__
表示,简而言之,print(repr(obj))
.
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:
另一方面,IPython 有自己的显示标准输出的实现,并在显示之前漂亮地打印对象的 __repr__
。 stdout 对象的漂亮打印发生在位于 RepresentationPrinter
class 中的 pretty()
函数中 ../IPython/lib/pretty.py :
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()
的安全实现,这意味着如果在获取此对象的属性时引发异常,它将 return 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 中,所有class 都是新样式 并且具有__mro__
属性。但是,old style class 定义已被保留用于向后兼容并且没有此属性。您的 class 是使用旧式语法定义的。您可以阅读有关 NewClass v/s OldClass here 的更多信息。在 _get_mro(obj_class)
的定义中,您的代码落入 old-style 语法的 try 块中并出错。这是 Traceback Exception 中最新和最底部的堆栈:
~/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 console/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',正如您提到的所有属性都是 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__
属性。我们应该有一个 class 对象来获取 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__
,其中 return 是一个没有 __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中,可以为class中的对象添加我们自己的漂亮打印规则。受 docs for module lib.pretty 的启发,我修改了代码并定义了一个 _repr_pretty_(self, p, cycle)
函数,该函数在 __getattribute__
中显式调用(在类型检查之后)以所需格式显示对象。如果属性是一个字符串,它只是再次 returns 字符串:
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
不是可迭代的。
一般来说,建议在您的 class 中添加一个 __repr__
函数,因为它可以清楚地显示您的 class 中对象的表示。您可以阅读更多相关信息 here.
结论:IPython 标准输出实现了自己漂亮的打印 __repr__
而不是 Python 使用内置的解释器repr()
标准输出函数。为了改变 IPython 上标准输出的行为,可以将 _repr_pretty_()
函数添加到 class 以根据需要显示输出。
我有一个自定义 __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
在获取 class 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>
在 IPython 中,标准输出显示对象的漂亮打印 __repr__
表示。而在 Python 中,标准输出 print
是对象的 __repr__
表示,简而言之,print(repr(obj))
.
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:
另一方面,IPython 有自己的显示标准输出的实现,并在显示之前漂亮地打印对象的 __repr__
。 stdout 对象的漂亮打印发生在位于 RepresentationPrinter
class 中的 pretty()
函数中 ../IPython/lib/pretty.py :
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()
的安全实现,这意味着如果在获取此对象的属性时引发异常,它将 return 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 中,所有class 都是新样式 并且具有__mro__
属性。但是,old style class 定义已被保留用于向后兼容并且没有此属性。您的 class 是使用旧式语法定义的。您可以阅读有关 NewClass v/s OldClass here 的更多信息。在 _get_mro(obj_class)
的定义中,您的代码落入 old-style 语法的 try 块中并出错。这是 Traceback Exception 中最新和最底部的堆栈:
~/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 console/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',正如您提到的所有属性都是 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__
属性。我们应该有一个 class 对象来获取 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__
,其中 return 是一个没有 __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中,可以为class中的对象添加我们自己的漂亮打印规则。受 docs for module lib.pretty 的启发,我修改了代码并定义了一个 _repr_pretty_(self, p, cycle)
函数,该函数在 __getattribute__
中显式调用(在类型检查之后)以所需格式显示对象。如果属性是一个字符串,它只是再次 returns 字符串:
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
不是可迭代的。
一般来说,建议在您的 class 中添加一个 __repr__
函数,因为它可以清楚地显示您的 class 中对象的表示。您可以阅读更多相关信息 here.
结论:IPython 标准输出实现了自己漂亮的打印 __repr__
而不是 Python 使用内置的解释器repr()
标准输出函数。为了改变 IPython 上标准输出的行为,可以将 _repr_pretty_()
函数添加到 class 以根据需要显示输出。