Есть ли в Python способ реализовать несколько стратегий представления в пользовательских классах, обеспечить разные стили их отображения, и все это рекурсивно в произвольных структурах данных, чтобы вложенные объекты были представлены с использованием стратегии родительского вызова, если она доступна? ? Так...
class SomeClass:
def __init__(self, objects):
self._objects = objects
def __repr__(self):
return f"Recursive default repr: {self._objects}"
def __alternate_repr__(self):
return f"Recursive alternate repr: {self._objects}"
a = SomeClass(["list", "of", "values"])
b = SomeClass(dict(nested_class=a))
c = SomeClass([b, 1, "..."])
repr(c) # c,b,a shown using __repr__()
alternate_repr(c) # c,b,a shown using __alternate_repr__()
@PresidentJamesK.Polk Корневой вызов будет указывать на это (в примере: repr()
против alternate_repr()
). Но, возможно, было бы лучше представить функцию repr, принимающую ключ для использования, если он доступен (т. е. repr(...)
против repr(..., key = "__alternate_repr__")
).
Какое это имеет отношение к интерфейсу repr
? Если вы хотите написать свой собственный метод для отображения вашего класса, вы можете просто сделать это.
@wjandrea Мой хрустальный шар говорит, что ОП хочет чего-то, что просто работает, например. f"{foo=!r}"
.
@mara004 Но alternate_repr
не существует... Так ты хочешь написать свой протокол? Но вы можете это сделать не используя дурацкие имена.
@mara004 Знаете ли вы о _repr_html_ и других методах, используемых в IPython/Jupyter? Возможно, это то, чего вы действительно хотите.
@wjandrea В сложном графе объектов это означало бы необходимость повторной реализации рекурсивного перемещения и отображения контейнера, чего я хочу избежать.
@mara004 mara004 Если я правильно вас понимаю, вы могли бы выделить это в метод, например _traverse
, который просто передавал бы содержащиеся объекты для использования __repr__
и __alternate_repr__
Вы, вероятно, захотите, чтобы repr()
делал правильные вещи (например, при вызове из другого кода библиотеки), и этот код не будет знать о __special_repr__
или чем-то еще, поэтому здесь вам понадобятся еще некоторые обходные пути.
Использование contextvars
гарантирует безопасность асинхронности/поточности.
import contextlib
import contextvars
## Repr mode plumbing.
_repr_mode = contextvars.ContextVar("repr_mode", default = "normal")
def get_repr_mode():
return _repr_mode.get()
@contextlib.contextmanager
def with_repr_mode(mode):
try:
token = _repr_mode.set(mode)
yield
finally:
_repr_mode.reset(token)
## Client classes using the repr mode.
class SomeClass(str):
def __repr__(self):
if get_repr_mode() == "special":
return f"SPECIAL ALL CAPS REPR! {self.upper()}"
return super().__repr__()
class SomeContainer(list):
def __repr__(self):
if get_repr_mode() == "special":
return f"SPECIAL REPR!: {super().__repr__()}"
return super().__repr__()
## Demo code.
lst = SomeContainer([SomeClass("hello"), SomeClass("world")])
print(repr(lst))
with with_repr_mode("special"):
print(repr(lst))
Это распечатывает
['hello', 'world']
SPECIAL REPR!: [SPECIAL ALL CAPS REPR! HELLO, SPECIAL ALL CAPS REPR! WORLD]
Это умная идея, спасибо!
Pandas делает что-то подобное с помощью option_context() , но у него также есть неконтекстный установщик опций.
IIUC, эквивалентом OP alternate_repr
будет def special_repr(obj: object) -> str: with with_repr_mode("special"): return repr(obj)
Вы имеете в виду что-то вроде этого?
class Foo:
def __alt_repr__(self) -> str:
return "FooAlt()"
class FooSub(Foo): ...
class Bar: ...
def alt_repr(obj: object) -> str:
if hasattr(obj, "__alt_repr__"):
return obj.__alt_repr__()
return repr(obj)
print(alt_repr(Foo())) # FooAlt()
print(alt_repr(FooSub())) # FooAlt()
print(alt_repr(Bar())) # <__main__.Bar object at 0x...>
Не совсем, это не приведет к правильной рекурсии.
Не используйте дандер-имена, они зарезервированы.
По крайней мере, это демонстрирует, что repr
сам по себе не является чем-то особенным; это просто функция, которая ищет определенный метод для своего аргумента. (Другие функции, такие как len
, copy
, deepcopy
и т. д., работают так же.) Единственное, что особенного в repr
, это то, что сам интерпретатор Python (в интерактивном режиме) автоматически вызывает repr
по значению выражения, оцененного в командной строке.
@ mara004 Здесь это не показано, но класс __alt_repr__
должен будет вызывать alt_repr
для любых содержащихся объектов. Именно так работает обычай __repr__
.
@wjandrea Тогда как бы вы передали это по всему контейнеру? Скажем, если у объекта есть список и мы вызываем для него alt_repr(), он больше не будет применяться к элементам в списке. Поэтому нам придется самостоятельно пройти по списку, вызвать alt_repr() для каждого элемента и построить строку списка вручную. И то же самое для любых других контейнеров, таких как dict или set. Вы можете видеть, насколько это будет утомительно и что придется заново реализовать существующую семантику (например, скобки и разделение запятыми для списка). И вы не сможете поддерживать общие итерации без потери их собственного представления.
@mara004 Ааа, понятно! Теперь я понимаю цель вопроса! Вы должны отредактировать это! :)
@mara004 mara004 Теперь, когда я прочитал это снова, я вижу, что вы, по сути, показали это уже в коде, но это не объяснено словами. Еще я зациклился на alternate_repr
несуществовании.
Мне пришло в голову, что мы могли бы временно заменить встроенные функции на alt_repr
, как показано в этом посте. Хотя это позволило бы избежать накладных расходов в случае воспроизведения по умолчанию, в альтернативном случае это привело бы к значительно большим накладным расходам, поскольку нам пришлось бы проверять __alt_repr__
на каждом отдельном объекте. Вероятно, это причина того, что repr()
не предоставляет аргумента для поиска альтернативных функций, поэтому я полагаю, что подход @AKX — лучший вариант.
Я также задавался вопросом, можем ли мы добавить __alt_repr__
в базовый класс object
, по умолчанию repr(self)
, чтобы мы могли просто вызывать __alt_repr__
для всех объектов (также временно исправляя встроенные функции repr). Казалось бы, это самое затратное решение, но я не знаю, как изменить object
таким образом.
Что я забыл указать в другом моем комментарии, проблема потери собственного представления объекта возникает не только с общими итерациями, но фактически с любым пользовательским классом (который является контейнером) - скажем, классом attrs
или подкласс примитива (например, списка). При повторной реализации обхода графа объектов вы потеряете либо альтернативное представление, либо собственное представление класса. Так что это определенно не тот путь.
Как бы вы указали, какой
repr
вы хотите использовать?