将lambda作为jinja2过滤器的参数?
我想在jinja2中创建一个自定义过滤器,像这样:
{{ my_list|my_real_map_filter(lambda i: i.something.else)|some_other_filter }}
但是当我实现这个过滤器时,我遇到了这个错误:
TemplateSyntaxError: expected token ',', got 'i'
看起来jinja2的语法不允许把lambda表达式作为参数?有没有什么好的解决办法?目前,我是在Python中创建lambda,然后把它作为变量传递给模板,但我更希望能直接在模板中创建它。
3 个回答
我最近也遇到了同样的问题,我需要在我的 Ansible
模板中创建一个 list
(列表)和 dict
(字典),但这在基本的过滤器中没有包含。
这是我找到的解决方法:
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
是一个 string
(字符串)的 list
,但它也可以用任何类型的 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
希望这对某些人有帮助
我有一个解决办法,我在对一个字典对象进行排序:
registers = dict(
CMD = dict(
address = 0x00020,
name = 'command register'),
SR = dict(
address = 0x00010,
name = 'status register'),
)
我想要遍历这个注册字典,但需要按地址排序。所以我需要一种方法来根据“地址”字段进行排序。为此,我创建了一个自定义过滤器,并将lambda表达式作为字符串传递,然后我使用Python内置的eval()函数来生成真正的lambda:
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
所以你可以将lambda作为字符串传递,但你需要添加一个自定义过滤器来实现这一点。
不,你不能在Jinja2模板中直接使用普通的Python表达式来进行过滤。
这里的困惑主要是因为Jinja2模板在很多方面和Python的语法很相似,但你要把它当作一种完全独立的代码语法来看待。
Jinja2有严格的规则,规定了在模板的不同部分可以期待什么内容,通常不允许直接使用Python代码,而是需要特定类型的表达式,这些表达式的种类是相对有限的。
这和一个概念有关,那就是展示和模型应该分开,所以模板不应该包含太多逻辑。不过,相比于其他很多模板选项,Jinja2还是比较宽松的,允许在模板中使用相当多的逻辑。