Вызов функций во вложенном списке Python

#python #recursion #nested-loops #nested-lists

Вопрос:

Мне нужно перебирать вложенные списки в python, где элементы списка являются вызовами функций (первый элемент в списке-функция, все остальные элементы являются аргументами этой функции). Эти аргументы также сами по себе могут быть функциями с аргументами, отсюда и вложенность. Список также может содержать одну функцию без аргументов. Вот так: ['foo','arg1','arg2'] Или: [['foo','arg1','arg2'],['bar'],['baz','arg1']] Или: ['foo','arg1',['baz','arg1']] И т. Д.

Вот пример использования фиктивных функций суммы и продукта… Сумма складывается, произведение умножается; Если бы мы оценили функции, то получили бы 10.

[«сумма»,[«продукт»,2,3],4]

Я могу просто передать функцию и аргументы другой функции, которая выполняет фактический «вызов» моих безопасных функций, но у меня возникают проблемы с рекурсивным повторением списка. Вот печальная попытка:

 def process_list(l):
        if isinstance(l,list):
            results = []
            for item in l:
                if isinstance(item,list):
                    func = item[0]
                    args = item[1:]
                    for idx, arg in enumerate(args):
                        if isinstance(arg,list):
                            args[idx] = process_list(arg)
                    results.append(function_caller_result(func,args)) #what if that's a list?
                else:
                    results.append(item)
            return results
        return l


 

Любая помощь здесь будет очень признательна! Заранее спасибо! 🙂

Комментарии:

1. [['foo','arg1','arg2'],['bar'],['baz','arg1']] каким будет результат? foo вернулся бы (предположим) 1 , bar вернулся 2 baz бы , вернулся 3 бы , так что вы ожидали [1, 2, 3] бы ? второй и третий аргумент теперь не имеет функции, в которую они могут быть переданы, в любом случае, если первый элемент в списке или вложенном списке не является функцией, это приведет к случаям, подобным тому, что я объяснил, надеюсь, я ясно дал понять, например : [['foo', 1, 2], [['foo' ,1, 2], 1, 2], [['foo' ,1, 2], 1, 2]]

Ответ №1:

Вы можете попробовать эту рекурсивную функцию, которая работает для некоторых тестов, в которых решение Ajax1234 не работает.

Я заметил, что их ввод имеет форму [['sum',['product',2,3],4]] , а ['sum',['product',2,3],4] не показан в вашем вопросе.

Это решение использует ваш список входных ['sum',['product',2,3],4] данных вместе с другими тестами.

 from operator import add, sub, mul, truediv

funcs = {'sum': add, 'minus': sub, 'product': mul, 'divide': truediv}  # you can use your functions instead of this

def process(seq):
    if isinstance(seq, list):
        return funcs[seq[0]](*(process(i) for i in seq[1:]))
    return seq

print(process(['sum',['product',2,3],4]))
print(process(['sum',['product',['minus', 2, 3],3],4]))
print(process(['sum',['product',['minus', 2, 3],3],['divide', 3, 4]]))
 

Выход

 10
1
-2.25
 

Если вы хотите один лайнер, то

 def process(seq):
    return seq if not isinstance(seq, list) else funcs[seq[0]](*map(process, seq[1:]))
 

Редактировать:

Это не сработает в случае, который я объяснил в комментарии, решение Ajax1234 решает эту проблему и объясняет причину изменения списка ввода и логики.

Ответ №2:

Вы пытаетесь создать своего рода рекурсивную функцию, но вручную. Это не сработает, если вложенность больше, чем вы закодировали.

Вместо этого напишите рекурсивную функцию:

 my_list = ['sum',['product',2,3],4]

def process(l):
    l = l.copy()
    for item in l:
        if isinstance(item,list):
            l.replace(item, process(item))
    return function_caller_result(*l) 
    # suppose l = [1, [2], '3']
    # func(*l) => func(1, [2], '3')

result = process(my_list)
 

Ответ №3:

Вы можете использовать рекурсию при распаковке:

 eval_d = {'sum':lambda x, y:x y, 'product':lambda x, y:x*y} #dict to store function names with an associated callable
data = [['sum',['product',2,3],4]]
def eval_f(d):
   r = [eval_d[a](*[i if not isinstance(i, list) else eval_f([i]) for i in b]) 
        for a, *b in d]
   #r now stores the result from the function calls
   return sum(r) #for this example to work, a cumulate sum is returned, but you can also just return the list

print(eval_f(data))
 

Выход:

 10
 

Цель этого решения-работать в сценариях, в которых eval_f передается список списков с сигнатурами функций [['foo','arg1','arg2'],['bar'],['baz','arg1']] , т. е. Таким образом, отдельные вызовы функций должны быть заключены в список, чтобы гарантировать, что шаблон будет сохранен: ['foo','arg1','arg2'] будет передан eval_f и [['foo','arg1','arg2']] .

Комментарии:

1. ваше объяснение также заставило меня осознать некоторую ошибку в моем решении, так что спасибо за это, у меня есть вопрос, поскольку [['sum', 1, 2], ['sum', 1, 2], ['sum', 1, 2]] ваше решение дает 9 , но как было принято решение по sum всем промежуточным результатам? [3, 3, 3] ? Это то, что объясняет последний комментарий? Английский не является моим основным языком, поэтому я, возможно, неправильно истолковал

2. @python_user Я включил только окончательное суммирование return sum(r) , чтобы функция eval_f дала разумный результат. Основываясь на коде операции, r сам по себе, вероятно, должен быть возвращен на практике , но , поскольку у нас нет реализаций foo , bar , и baz , имеет больше смысла предоставить решение, которое выполняет базовую оценку выражений, в стиле общего ввода операции.