Возможность реализовать несколько стратегий репрезентации?

Есть ли в 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__()

Как бы вы указали, какой repr вы хотите использовать?

President James K. Polk 14.07.2024 21:14

@PresidentJamesK.Polk Корневой вызов будет указывать на это (в примере: repr() против alternate_repr()). Но, возможно, было бы лучше представить функцию repr, принимающую ключ для использования, если он доступен (т. е. repr(...) против repr(..., key = "__alternate_repr__")).

mara004 14.07.2024 21:16

Какое это имеет отношение к интерфейсу repr? Если вы хотите написать свой собственный метод для отображения вашего класса, вы можете просто сделать это.

wjandrea 14.07.2024 21:21

@wjandrea Мой хрустальный шар говорит, что ОП хочет чего-то, что просто работает, например. f"{foo=!r}".

AKX 14.07.2024 21:23

@mara004 Но alternate_repr не существует... Так ты хочешь написать свой протокол? Но вы можете это сделать не используя дурацкие имена.

wjandrea 14.07.2024 21:24

@mara004 Знаете ли вы о _repr_html_ и других методах, используемых в IPython/Jupyter? Возможно, это то, чего вы действительно хотите.

wjandrea 14.07.2024 21:26

@wjandrea В сложном графе объектов это означало бы необходимость повторной реализации рекурсивного перемещения и отображения контейнера, чего я хочу избежать.

mara004 14.07.2024 21:31

@mara004 mara004 Если я правильно вас понимаю, вы могли бы выделить это в метод, например _traverse, который просто передавал бы содержащиеся объекты для использования __repr__ и __alternate_repr__

wjandrea 14.07.2024 21:35
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
8
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы, вероятно, захотите, чтобы 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]

Это умная идея, спасибо!

mara004 14.07.2024 21:33

Pandas делает что-то подобное с помощью option_context() , но у него также есть неконтекстный установщик опций.

wjandrea 14.07.2024 21:41

IIUC, эквивалентом OP alternate_repr будет def special_repr(obj: object) -> str: with with_repr_mode("special"): return repr(obj)

wjandrea 14.07.2024 22:34

Вы имеете в виду что-то вроде этого?

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

Не совсем, это не приведет к правильной рекурсии.

mara004 14.07.2024 21:28

Не используйте дандер-имена, они зарезервированы.

wjandrea 14.07.2024 21:28

По крайней мере, это демонстрирует, что repr сам по себе не является чем-то особенным; это просто функция, которая ищет определенный метод для своего аргумента. (Другие функции, такие как len, copy, deepcopy и т. д., работают так же.) Единственное, что особенного в repr, это то, что сам интерпретатор Python (в интерактивном режиме) автоматически вызывает repr по значению выражения, оцененного в командной строке.

chepner 14.07.2024 21:30

@ mara004 Здесь это не показано, но класс __alt_repr__ должен будет вызывать alt_repr для любых содержащихся объектов. Именно так работает обычай __repr__.

wjandrea 14.07.2024 21:32

@wjandrea Тогда как бы вы передали это по всему контейнеру? Скажем, если у объекта есть список и мы вызываем для него alt_repr(), он больше не будет применяться к элементам в списке. Поэтому нам придется самостоятельно пройти по списку, вызвать alt_repr() для каждого элемента и построить строку списка вручную. И то же самое для любых других контейнеров, таких как dict или set. Вы можете видеть, насколько это будет утомительно и что придется заново реализовать существующую семантику (например, скобки и разделение запятыми для списка). И вы не сможете поддерживать общие итерации без потери их собственного представления.

mara004 14.07.2024 21:47

@mara004 Ааа, понятно! Теперь я понимаю цель вопроса! Вы должны отредактировать это! :)

wjandrea 14.07.2024 21:49

@mara004 mara004 Теперь, когда я прочитал это снова, я вижу, что вы, по сути, показали это уже в коде, но это не объяснено словами. Еще я зациклился на alternate_repr несуществовании.

wjandrea 14.07.2024 21:54

Мне пришло в голову, что мы могли бы временно заменить встроенные функции на alt_repr, как показано в этом посте. Хотя это позволило бы избежать накладных расходов в случае воспроизведения по умолчанию, в альтернативном случае это привело бы к значительно большим накладным расходам, поскольку нам пришлось бы проверять __alt_repr__ на каждом отдельном объекте. Вероятно, это причина того, что repr() не предоставляет аргумента для поиска альтернативных функций, поэтому я полагаю, что подход @AKX — лучший вариант.

mara004 14.07.2024 22:34

Я также задавался вопросом, можем ли мы добавить __alt_repr__ в базовый класс object, по умолчанию repr(self), чтобы мы могли просто вызывать __alt_repr__ для всех объектов (также временно исправляя встроенные функции repr). Казалось бы, это самое затратное решение, но я не знаю, как изменить object таким образом.

mara004 14.07.2024 23:03

Что я забыл указать в другом моем комментарии, проблема потери собственного представления объекта возникает не только с общими итерациями, но фактически с любым пользовательским классом (который является контейнером) - скажем, классом attrs или подкласс примитива (например, списка). При повторной реализации обхода графа объектов вы потеряете либо альтернативное представление, либо собственное представление класса. Так что это определенно не тот путь.

mara004 15.07.2024 00:34

Другие вопросы по теме