将lambda作为jinja2过滤器的参数?

15 投票
3 回答
13690 浏览
提问于 2025-04-18 10:23

我想在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 个回答

1

我最近也遇到了同样的问题,我需要在我的 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

希望这对某些人有帮助

8

我有一个解决办法,我在对一个字典对象进行排序:

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作为字符串传递,但你需要添加一个自定义过滤器来实现这一点。

10

不,你不能在Jinja2模板中直接使用普通的Python表达式来进行过滤。

这里的困惑主要是因为Jinja2模板在很多方面和Python的语法很相似,但你要把它当作一种完全独立的代码语法来看待。

Jinja2有严格的规则,规定了在模板的不同部分可以期待什么内容,通常不允许直接使用Python代码,而是需要特定类型的表达式,这些表达式的种类是相对有限的。

这和一个概念有关,那就是展示和模型应该分开,所以模板不应该包含太多逻辑。不过,相比于其他很多模板选项,Jinja2还是比较宽松的,允许在模板中使用相当多的逻辑。

撰写回答