#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, и, похоже, это работает не так, как вы ожидаете. Я бы все же предложил использовать всю логику в контроллере. Вы должны передать в шаблон только те данные , которые должны быть там показаны — просто создайте adict
и используйте его с.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?