问题 lambda作为jinja2过滤器的参数?


我想在jinja2中有一个自定义过滤器,如下所示:

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

但是当我实现它时,我收到此错误:

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

看来jinja2的语法不允许lambdas作为参数?有一些不错的解决方法吗?现在,我在python中创建lambda然后将其作为变量传递给模板,但我宁愿能够在模板中创建它。


9144
2018-06-19 15:33


起源

请不要在模板中使用复杂的逻辑。它们不是为此而设计的。您应该将后端的大多数逻辑操作视为尽可能轻量级的模板。想象一下,在另一位开发人员找到类似的东西之后你会说些什么?考虑 构建过滤器。该 lambda 创建一个更难调试的匿名函数。 - boldnik
你能建议一种方法来使用内置过滤器做我想做的事吗? (即我有一个对象列表,我需要访问每个对象的子属性?)我不认为我想要做的事情在逻辑上比构建的更复杂在“地图”允许,但我不认为我可以用内置地图做我想要的。如果可以,请告诉我! - Lee
问题是 lambda as argument to jinja2 filter? 答案是 No, you cannot pass general Python expression to filter in Jinja2 template。似乎是正确的。现在,如果要在模板中的对象列表上使用自定义过滤器,问题是“为什么”。为什么不过滤对象 之前 将列表传递给模板?为什么需要过滤 后 将它传递给模板? - boldnik
因为我的过滤器正在模板化的东西,我认为它应该在模板而不是控制器。我的主要对象Suggestion有一个属性contacts,它是一个Contact对象列表,每个Contact都有属性name和url。我想构造一个逗号分隔(带有'和',如果合适的话)链接名称的表示。 - Lee
好吧,我试过用 lambda 在Jinja2模板中,它似乎无法正常工作。我仍然建议在控制器中使用所有逻辑。你应该只传递应该在那里显示的数据 - 只需构造一个 dict 并使用它 join()。这不是单页应用,视图中的任何更改都将在您刷新页面后进行。并考虑有人阅读您的代码,并试图了解您为什么这样做。 - boldnik


答案:


不,你不能通过一般的Python表达式来过滤Jinja2模板

混淆来自jinja2模板在许多方面与Python语法类似,但您应将其视为具有完全独立语法的代码。

Jinja2有严格的规则,可以预期在模板的哪个部分,它通常不允许python代码,它期望精确类型的表达式,这是非常有限的。

这符合概念,表示和模型应分开,因此模板不允许太多逻辑。无论如何,与许多其他模板选项相比,Jinja2是非常允许的,并且在模板中允许相当多的逻辑。


6
2018-06-19 15:47





我有一个解决方法,我正在排序一个dict对象:

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作为字符串传递,但是您必须添加自定义过滤器才能执行此操作。


6
2017-10-28 00:47



很好的解决方案,但可能值得考虑的一个方面是模板的可读性。如果模板的输出可以很容易地从仅读取其中的过滤器和命令中辨别出来,那么这对于有效设计来说是一个很好的启发式算法。 - dreftymac