python 中的可变 repr 基于调用深度

variable repr in python based on call depth

我有一个(坏?)习惯,在 Python 中显示 classes 就像 Matlab 中的结构一样,其中每个属性及其值都以漂亮干净的布局打印。这是通过在 class.

中实现 __repr__ 方法来完成的

在字典或列表中处理对象时,这种显示方式可能会让人分心。在这种情况下,我想做一个更基本的显示。

这是设想的伪代码:

def __repr__(self):
    if direct_call():
        return do_complicated_printing(self)
    else:
        #something simple that isn't a ton of lines/characters
        return type(self)

在此代码中 direct_call() 表示这不是作为另一个显示调用的一部分调用的。也许这可能需要在堆栈中寻找 repr ?我将如何实施直接呼叫检测?

所以我可能有这样的东西:

>>> data
<class my_class> with properties:

        a: 1
   cheese: 2
     test: 'no testing'

但在列表中我想要这样的显示:

>>> data2 = [data, data, data, data]
>>> data2
[<class 'my_class'>,<class 'my_class',<class 'my_class'>,<class 'my_class'>]

我知道我可以通过调用一些执行此操作的函数来强制显示这种类型,但我希望 my_class 能够控制这种行为,而无需用户额外的询问为此。

换句话说,这不是解决方案:

>>> print_like_I_want(data2)

想做这件事很奇怪,通常一个函数或方法应该做同样的事情,无论谁调用它。但在这种情况下,__repr__ 只是为了程序员的方便,所以方便似乎是一个足够好的理由让它按照你要求的方式工作。

然而,不幸的是,您想要的实际上是不可能的,因为无论出于何种原因,list.__repr__ 方法在堆栈中不可见。我在 Python 3.5.2 和 Python 3.8.1:

中进行了测试
>>> class ReprRaises:
...     def __repr__(self):
...         raise Exception()
... 
>>> r = ReprRaises()
>>> r
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __repr__
Exception
>>> [r]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __repr__
Exception

如您所见,无论 repr 中的对象是否在列表中,堆栈都是相同的。 (堆栈上的 __repr__ 帧属于 ReprRaises class,而不是 list。)

我还使用 inspect.stack:

进行了测试
>>> import inspect
>>> class ReprPrints:
...     def __repr__(self):
...         print(*inspect.stack(), sep='\n')
...         return 'foo'
>>> r = ReprPrints()
>>> r
FrameInfo(frame=<frame object at 0x7fcbe4a38588>, filename='<stdin>', lineno=3, function='__repr__', code_context=None, index=None)
FrameInfo(frame=<frame object at 0x7fcbe44fb388>, filename='<stdin>', lineno=1, function='<module>', code_context=None, index=None)
foo
>>> [r]
FrameInfo(frame=<frame object at 0x7fcbe4a38588>, filename='<stdin>', lineno=3, function='__repr__', code_context=None, index=None)
FrameInfo(frame=<frame object at 0x7fcbe44fb388>, filename='<stdin>', lineno=1, function='<module>', code_context=None, index=None)
[foo]

同样,对象本身与列表中的对象之间的调用堆栈没有明显区别;所以您的__repr__没有什么可检查的。


因此,您可以获得的最接近的是某种 print_like_I_want 函数。这个 可以 至少以一种让每个 class 定义自己的行为的方式编写:

def pp(obj):
    try:
        _pp = obj._pp
    except AttributeError:
        print(repr(obj))
    else:
        print(_pp())

我能想到的减少按键次数的唯一方法是重载一元运算符,例如通常无用的 unary plus:

>>> class OverloadUnaryPlus:
...     def __repr__(self):
...         return 'foo'
...     def __pos__(self):
...         print('bar')
... 
>>> obj = OverloadUnaryPlus()
>>> obj
foo
>>> +obj
bar

__repr__ 旨在提供一个简短的、通常是程序化的对象显示。它被用作所有内置容器显示元素的首选方法。因此,您应该覆盖 __repr__ 以提供您的简短输出。

__str__ 是用于完整显示对象的函数。当您 print 一个对象时,它通常会出现。您也可以通过调用 str 来触发它。你应该把你的长花式输出放在 __str__,而不是 __repr__.

您唯一需要做的修改是在 REPL 中显式调用 print(obj)str(obj) 而不是 repr(obj)obj。正如 所示,堆栈检查不会对您有多大帮助,而且在我看来,即使它有帮助也不是一个干净的解决方案。