DRY — код дублируется в рекурсивной функции: заменитель строк в словарях (python)

Я создаю заменитель токена json, который работает следующим образом:

ввод: {"greet": "hello {{name}}"} и токены {"name": "Lorenzo"}

вывод: {"greet": "hello Lorenzo"}


Поскольку я хочу, чтобы функция работала также с вложенными диктовками/списками, я использовал рекурсию для расширения функциональности более сложных входных данных, таких как:

ввод: {"a": {"b": "x{{ppp}}z"}} и токены {"ppp": "y"})

вывод: {"a": {"b": "xyz"}}

(другие тесты/примеры использования доступны здесь)


Даже если код работает, меня не устраивает мое текущее решение, потому что я нарушил принцип DRY.

Я уверен, что смогу сделать свой код чище: есть идеи или предложения? Вот что я пробовал:

def __replace(input_str: str, pytokens: dict):
    for k, v in pytokens.items():
        input_str = re.sub("{{" + k + "}}", str(v), input_str)
    return input_str


def __json_replacer(pyinput, pytokens: dict):
    # TODO DRY: code is doubled, code this part again
    if isinstance(pyinput, str):
        return __replace(pyinput, pytokens)

    if isinstance(pyinput, list):
        for i in range(len(pyinput)):
            pyinput[i] = __json_replacer(pyinput[i], pytokens)
        return pyinput

    if not isinstance(pyinput, dict):
        return pyinput

    res = {}
    for k, v in pyinput.items():
        if isinstance(v, str):
            v = __replace(v, pytokens)

        if isinstance(v, list):
            for i in range(len(v)):
                v[i] = __json_replacer(v[i], pytokens)

        if isinstance(v, dict):
            v = __json_replacer(v, pytokens)

        res[k] = v

    return res

Примечание: проект с открытым исходным кодом! Если вы предоставите решение, не стесняйтесь открывать PR и брать кредиты :) Ссылка на проект GitHub

Почему в 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
0
57
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Вы можете сократить

if not isinstance(pyinput, dict):
    return pyinput

res = {}
for k, v in pyinput.items():
    if isinstance(v, str):
        v = __replace(v, pytokens)

    if isinstance(v, list):
        for i in range(len(v)):
            v[i] = __json_replacer(v[i], pytokens)

    if isinstance(v, dict):
        v = __json_replacer(v, pytokens)

    res[k] = v

к

if isinstance(pyinput, dict):
    return {k: __json_replacer(v, pytokens) for k, v in pyinput.items()}

Затем это делегирует проверку списка приведенному выше коду, поэтому внесите небольшое изменение в обработчик списка. Изменить это

if isinstance(pyinput, list):
    for i in range(len(pyinput)):
        pyinput[i] = __json_replacer(pyinput[i], pytokens)
    return pyinput

к

if isinstance(pyinput, list):
    return [__json_replacer(v, pytokens) for v in pyinput]

Собираем это вместе

def __json_replacer(pyinput, pytokens):
    if isinstance(pyinput, str):
        return __replace(pyinput, pytokens)

    if isinstance(pyinput, list):
        return [__json_replacer(v, pytokens) for v in pyinput]

    if isinstance(pyinput, dict):
        return {k: __json_replacer(v, pytokens) for k, v in pyinput.items()}

    return pyinput

Бег

>>> __json_replacer({"a": {"b": "x{{ppp}}z"}}, {"ppp": "y"})
# {'a': {'b': 'xyz'}}

Как и ожидалось.


Я бы не советовал рассматривать весь объект как строку и выполнять замену строки, поскольку в конечном итоге вы можете выполнять операции замены и ключей, что может иметь непредвиденные последствия (например, дублирование или повреждение ключей).

Это намного чище (и все мои тесты проходят!): именно то, что я искал. Не стесняйтесь брать кредиты, открывая PR (ссылка на проект выше в вопросе), спасибо! P.S.: Да, я согласен: рассматривать весь объект как строку не вариант

Lorenzo Mogicato 15.04.2023 19:02

@LorenzoMogica, спасибо, сделал пиар github.com/lorenzua02/json-replacer/pull/1

cs95 15.04.2023 19:11

Вам не нужно рассматривать весь ввод как dict. Вы можете прочитать весь файл как string и просто использовать функцию __replace().

почти похоже, что json (как строка) может быть шаблоном jinja.

rasjani 15.04.2023 13:11

Это может привести к непреднамеренному изменению ключей, если они имеют «ppp» или любой другой токен, что приведет к непредвиденным последствиям (подумайте о дублированных ключах и т. д.). Это следует упомянуть в ответе как потенциальное предостережение, поскольку оно не идеально.

cs95 15.04.2023 18:12

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