Работая во Flask, я всегда сталкиваюсь с этим паттерном в своих шаблонах:
{% if a.complicated_function() %}
A has a complicated value : {{a.complicated_function()}}
{% endif %}
Как видите, complicated_function
вызывается дважды. Большую часть времени complicated_function
делает несколько обращений к базе данных. Это очень неэффективно.
Одним из способов решения этой проблемы является кэширование значения функции. Однако это означает, что мне нужно реализовать механизм кэширования, а также нужно помнить об обновлении кеша, если он изменяется другими функциями.
Другой вариант — присвоить значение complicated_function
переменной в шаблоне следующим образом:
{% set value=a.complicated_function() %}
{% if value %}
A has a complicated value : {{value}}
{% endif %}
Это работает, но я не очень доволен дополнительной строкой set
. Если у меня есть несколько значений для оценки, я должен добавить несколько строк set
. Кроме того, если в коде есть несколько вложенных блоков, функция set
столкнется с некоторыми проблемами области видимости переменных.
Я хотел более чистое решение.
Сначала я попытался написать macro
для решения этой проблемы. Код выглядит следующим образом:
{# The macro #}
{% macro check_empty(value) %}
{% if value %}
{{ caller(content) }}
{% endif %}
{% endmacro %}
{# Example of how to call the macro #}
{% call(value) check_empty(a.complicated_function()) %}
A has a complicated value : {{value}}
{% endcall %}
С помощью этого макроса мне удалось удалить строку set
, однако весь переход туда-сюда value
через метод call
читать довольно сложно. У меня есть ощущение, что мои фронтенд-дизайнеры будут этому не очень рады и, скорее всего, будут сбиты с толку. Другая проблема заключается в том, что если мне нужно проверить более 1 значения, мне нужно улучшить функцию и написать еще более сложные коды.
Кажется, нет другого выбора, кроме написания расширения Jinja!
Я взглянул на пример на странице документации Jinja. Боже, это сложно! Я понятия не имел, что это за parser
, node
, stream
и body
. После нескольких часов проб и ошибок у меня появилась более четкая (но все еще размытая) картина всего процесса разбора шаблона. Короче говоря, вот код расширения:
from jinja2 import nodes
from jinja2.ext import Extension
class RequiredBlock(Extension):
# a set of names that trigger the extension.
tags = set(['required'])
def parse(self, parser):
# Create a normal With node first
# Borrowing the codes from parser.py,
# the only difference is the end tag is `endrequired`
# instead of `endwith`
with_node = nodes.With(lineno=next(parser.stream).lineno)
targets = []
values = []
while parser.stream.current.type != 'block_end':
if targets:
parser.stream.expect('comma')
target = parser.parse_assign_target()
target.set_ctx('param')
targets.append(target)
parser.stream.expect('assign')
values.append(parser.parse_expression())
with_node.targets = targets
with_node.values = values
with_node.body = parser.parse_statements(('name:endrequired',), drop_needle=True)
# Manually create a If node
if_node = nodes.If()
# If only one variable is required, assigned that variable to test if it is empty
if len(values) == 1:
test = values[0]
else:
# If more than one variables are required, concat them into a And node
test = nodes.And(left=values[0], right=values[1])
for i in range(2, len(values)):
test = nodes.And(left=test, right=values[i])
if_node.test = test
# else_ attribute cannot be None
if_node.else_ = []
# Assign with_node as the body of the if_node, to nest them
if_node.body = [with_node]
return if_node
#This is how to add the extension in flask
app.jinja_env.add_extension('package.RequiredBlock')
#This is how to use it in the template
{% required value=a.complicated_function() %}
A has a complicated value : {{value}}
{% endrequired %}
Я создал парсер для своего пользовательского тега required
. Парсер представляет собой комбинацию блока if
и блока with
. Когда синтаксический анализатор встречает тег, он оценивает все переменные в строке назначения и обрабатывает только коды body
между тегами required
и endrequired
, если все переменные не пусты.
ПРИМЕЧАНИЕ. 0 (ноль), ‘’(пустая строка), [](пустой список) и {}(пустой словарь) считаются ложными, что соответствует соглашению Python.
Расширение также поддерживает проверку нескольких переменных, например:
#This is how to use it in the template {% required va=a.complicated_function(), vb=b.complicated_function(), vc=c.complicated_function()%}A has a complicated value : {{va}} B has a complicated value : {{vb}}
Chas a complicated value : {{vc}} {% endrequired %}
Однако у кода есть один недостаток: для работы значения должны быть присвоены переменной. Это связано с тем, как ведет себя блок with
. Следующий код выдаст вам ошибку
{# Assuming you already have set the value of b or passed in from the environment #}
{% required va=a.complicated_function(), b %}
B is not empty
A has a complicated value : {{va}}
{% endrequired %}
Вы должны написать что-то вроде этого, что выглядит немного глупо.
{# Assuming you already have set the value of b or passed in from the environment #}
{% required va=a.complicated_function(), b=b %}
B is not empty
A has a complicated value : {{va}}
{% endrequired %}
Он еще не идеален, но на данный момент он очень хорошо служит цели. Пожалуйста, дайте мне знать, что есть более разумный способ добиться этого.
Пожалуйста, подпишитесь на тег Horlu Tech на Medium или на хэштег #HorluTech в Твиттере, чтобы не пропустить новые технические достижения!