переменная repr в python в зависимости от глубины вызова - PullRequest
2 голосов
/ 04 февраля 2020

У меня есть (плохая?) Привычка отображать классы в Python подобных структурах в Matlab, где каждый атрибут печатается вместе со своим значением в красивой чистой компоновке. Это делается путем реализации метода __repr__ в классе.

При работе с объектами внутри словарей или списков этот стиль отображения может немного отвлекать. В этом случае я хотел бы сделать более простой c показ.

Вот предполагаемый псевдокод:

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)

Ответы [ 2 ]

2 голосов
/ 04 февраля 2020

__repr__ предназначен для обеспечения короткого, часто программируемого c отображения объекта. Он используется как метод выбора для всех встроенных контейнеров для отображения элементов. Поэтому вы должны переопределить __repr__, чтобы обеспечить ваш короткий вывод.

__str__ - это функция, предназначенная для полного причудливого отображения объекта. Это то, что обычно появляется, когда вы print объект. Вы также можете вызвать его, позвонив str. Вы должны поместить свой длинный причудливый вывод в __str__, а не __repr__.

Единственное изменение, которое вам нужно будет сделать, это явно вызвать print(obj) или str(obj), а не repr(obj) или просто obj в REPL. Как показывает превосходный ответ @ kaya3 , проверка стека не очень вам поможет, и, на мой взгляд, не будет чистым решением, даже если оно и будет.

2 голосов
/ 04 февраля 2020

Это странная вещь, которую нужно делать, и обычно функция или метод должны делать то же самое, кто бы ни вызывал ее. Но в этом случае __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 'd в список. (Кадр __repr__ в стеке принадлежит классу ReprRaises, а не 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. Это можно, по крайней мере, записать так, чтобы каждый класс мог определить свое собственное поведение:

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

Единственный способ, которым я могу придумать, сделать это с меньшим количеством нажатий клавиш, это перегрузить унарный оператор, как обычно бесполезный унарный плюс :

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