лямбда в качестве аргумента для фильтра jinja2?

#python #lambda #jinja2

#python #лямбда #джинджа2

Вопрос:

Я бы хотел иметь пользовательский фильтр в jinja2 следующим образом:

 {{ my_list|my_real_map_filter(lambda i: i.something.else)|some_other_filter }}
 

Но когда я его реализую, я получаю эту ошибку:

 TemplateSyntaxError: expected token ',', got 'i'
 

Похоже, синтаксис jinja2 не допускает использования лямбд в качестве аргументов? Есть ли какой-нибудь хороший обходной путь? На данный момент я создаю лямбда-выражение в python, а затем передаю его в шаблон в качестве переменной, но я бы предпочел просто создать его в шаблоне.

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

1. Пожалуйста, никогда не используйте сложную логику в шаблонах. Они не предназначены для этого. Вы должны использовать как можно более легкие шаблоны для выполнения большинства логических операций в серверной части. Представьте, что бы вы сказали, обнаружив нечто подобное у другого разработчика? Рассмотрим встроенные фильтры . lambda Создает анонимную функцию, которую гораздо сложнее отлаживать.

2. Можете ли вы предложить способ сделать то, что я хочу, используя встроенные фильтры? (т.Е. у меня есть список объектов, и мне нужно получить доступ к атрибуту дочернего элемента каждого из объектов?) Я не думаю, что то, что я пытаюсь сделать, действительно более сложно в логическом смысле, чем то, что позволяет встроенная «карта», но я не думаю, что могу делать то, что хочу, со встроенной картой. Пожалуйста, покажите мне, смогу ли я!

3. Вопрос lambda as argument to jinja2 filter? и ответ No, you cannot pass general Python expression to filter in Jinja2 template . Кажется, правильно. Теперь, если вы хотите использовать пользовательский фильтр для списка объектов в шаблоне, возникает вопрос «почему». Почему бы не фильтровать объекты перед передачей списка в шаблон? Зачем вам нужно фильтровать после передачи его в шаблон?

4. Поскольку мой фильтр выполняет шаблонные действия, я думаю, что он должен быть в шаблоне, а не в контроллере. В моем предложении основного объекта есть атрибут contacts, который представляет собой список объектов Contact, и у каждого контакта есть имя атрибута и URL. Я хочу создать разделенное запятыми (с ‘и’, если это уместно) представление связанных имен.

5. Ну, я пытался использовать lambda в шаблонах Jinja2, и, похоже, это работает не так, как вы ожидаете. Я бы все же предложил использовать всю логику в контроллере. Вы должны передать в шаблон только те данные , которые должны быть там показаны — просто создайте a dict и используйте его с. join() Это не одностраничное приложение, и любые изменения в представлении будут после обновления страницы. И подумайте, что кто-то читает ваш код и пытается понять, почему вы это сделали.

Ответ №1:

Нет, вы не можете передать общее выражение Python для фильтрации в шаблоне Jinja2

Путаница возникает из-за того, что шаблоны jinja2 во многих аспектах похожи на синтаксис Python, но вы должны воспринимать его как код с полностью независимым синтаксисом.

Jinja2 имеет строгие правила, что можно ожидать в какой части шаблона, и, как правило, он не допускает код python как есть, он ожидает точных типов выражений, которые довольно ограничены.

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

Ответ №2:

У меня есть обходной путь, я сортирую объект dict:

 registers = dict(
    CMD = dict(
        address = 0x00020,
        name = 'command register'),
    SR = dict(
        address = 0x00010,
        name = 'status register'),
)
 

Я хотел перебрать регистр dict, но отсортировать по адресу. Итак, мне нужен был способ сортировки по полю «адрес». Чтобы сделать это, я создал пользовательский фильтр и передал лямбда-выражение в виде строки, затем я использую встроенный в Python eval() для создания реального лямбда-выражения:

 def my_dictsort(value, by='key', reverse = False):

    if by == 'key':
        sort_by = lambda x: x[0].lower() # assumes key is a str

    elif by == 'value':
        sort_by = lambda x: x[1]

    else:
        sort_by = eval(by)   # assumes lambda string, you should error check

    return sorted(value, key = sort_by, reverse = reverse)
 

С помощью этой функции вы можете внедрить ее в среду jinja2 следующим образом:

 env = jinja2.Environment(...)
env.filters['my_dictsort'] = my_dictsort
env.globals['lookup'] = lookup            # queries a database, returns dict
 

А затем вызовите его из своего шаблона:

 {% for key, value in lookup('registers') | my_dict_sort("lambda x:x[1]['address']") %}
{{"""
    static const unsigned int ADDR_{key} = 0x0{address:04X}; // {name}
""" | format(key = key, address = value['address'], name = value['name']) 
}}
{% endfor %}
 

Вывод:

 static const unsigned int ADDR_SR = 0x00010; // status register
static const unsigned int ADDR_CMD = 0x00020; // command register
 

Таким образом, вы можете передать лямбду в виде строки, но для этого вам придется добавить пользовательский фильтр.

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

1. Хорошее решение, но один аспект, который, возможно, стоит рассмотреть, — это удобочитаемость шаблона. Если выходные данные шаблона можно легко распознать, просто прочитав содержащиеся в нем фильтры и команды, это хорошая эвристика для эффективного проектирования.

Ответ №3:

недавно мне пришлось столкнуться с той же проблемой, мне пришлось создать list оф dict в моем Ansible шаблоне, и это не включено в базовые фильтры.

Вот моя работа вслух :

 def generate_list_from_list(list, target, var_name="item"):
    """
    :param list: the input data
    :param target: the applied transformation on each item of the list
    :param var_name: the name of the parameter in the lambda, to be able to change it if needed
    :return: A list containing for each item the target format
    """

    # I didn't put the error handling to keep it short
    # Here I evaluate the lambda template, inserting the name of the parameter and the output format
    f = eval("lambda {}: {}".format(var_name, target)) 

    return [f(item) for item in list]

# ---- Ansible filters ----
class FilterModule(object):
    def filters(self):
        return {
            'generate_list_from_list': generate_list_from_list
        }
 

Затем я могу использовать его таким образом :

( my_input_list является list частью string , но он будет работать с list чем угодно)

 # I have to put quotes between the <target> parameter because it evaluates as a string
# The variable is not available at the time of the templating so it would fail otherwise
my_variable: "{{ my_input_list | generate_list_from_list("{ 'host': item, 'port': 8000 }") }}"
 

Или, если я хочу переименовать параметр lambda :

 my_variable: "{{ my_input_list | generate_list_from_list("{ 'host': variable, 'port': 8000 }", var_name="variable") }}"
 

Это выводит :

  • при вызове непосредственно в Python :
 [{'host': 'item1', 'port': 8000}, {'host': 'item2', 'port': 8000}]
 
  • при вызове в шаблоне (файл yaml в моем примере, но поскольку он возвращает список, вы можете делать все, что захотите, как только список будет преобразован) :
 my_variable:
-    host: item1
     port: 8000
-    host: item2
     port: 8000
 

Надеюсь, это кому-то поможет

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

1. Куда вы помещаете код Python?