python jinja2 高级
高层api
class jinja2.Environment([options])
Enviroment:环境,是Jinjia2的核心组件,它包含重要的共享变量,如配置、过滤器、测试、全局变量、模板加载器等。上述的使用模板加载器就是使用到了loader参数来加载我们指定的模板资源。
构造方法中的属性
- block_start_string
标记块开始的字符串。 默认为{%
。 - block_end_string
标识块结束的字符串。默认为%}
- variable_start_string
标记打印语句开头的字符串,默认为{{
- variable_end_string
标识打印语句结束的字符串, 默认为}}
- comment_start_string
标识注释的开始的字符串, 默认为{#
- comment_end_string
表示注释的结束的字符串,默认为#}
- line_statement_prefix
如果给定和一个字符串,这将用作基于行的语句的前缀, 相当于是是{% %}
的另一种写法,如下当设置了此属性为#
时,这两个含义是一样的
<ul>
# for item in seq
<li>{{ item }}</li>
# endfor
</ul>
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
- line_comment_prefix
2.2中新增,如果给定和一个字符串,这将用作基于行的注释的前缀, 相当于是{# #}
的另一种写法。当设置此属性为##
, 以下两种写法都是可以的:
<ul>
# for item in seq
<li>{{ item }}</li> ## test
# endfor
</ul>
<ul>
{% for item in seq %} {# test #}
<li>{{ item }}</li>
{% endfor %}
</ul>
需要注意的是line_statement_prefix
和line_comment_prefix
最好不要在同一行出现,尤其是设置的类似的时候。
- trim_blocks
如果将其设置为 True 删除块后的第一个换行符(块,而不是变量标记!)。默认为false;去除block之间的一个换行符,当换行符多的情况下也只会去除一个换行符,转换示例如下:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
- lstrip_blocks
如果将其设置为 True,则前导空格和制表符将从行首剥离到块中。默认为false, 。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
- newline_sequence
开始换行符的序列。 必须是\r
、\n
或\r\n
之一。 默认值为\n
,这对于 Linux 和 OS X 系统以及 Web 应用程序来说是一个有用的默认值。相当于是每行的行尾是啥,由于不同的系统对于换行符要求有所不同,
Unix系统里,每行结尾只有“<换行>”,即"\n";Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;Mac系统里,每行结尾是“<回车>”,即"\r";。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。
- keep_trailing_newline
渲染模板时保留尾随换行符。 默认值为 False,这会导致从模板末尾删除单个换行符(如果存在),是针对于模板内容的末尾换行符,当设置为True时会删除模板内容的一个末尾换行符,转换如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li>1</li>
<li>3</li>
</ul>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
</html>
如上述所示,少了一个换行符
- extensions
要使用的 Jinja 扩展列表。 这可以是作为字符串或扩展类的导入路径 - optimized
是否启用优化器, 默认为真。 - undefined
未定义或其子类,用于表示模板中未定义的值。当未传值到对应模板中时的默认值 - finalize
可用于在输出变量表达式之前处理其结果的可调用对象。 例如,可以在这里将 None 隐式转换为空字符串。可以拿到传递给模板的值然后做处理。 - autoescape
如果设置为 true,则默认启用 XML/HTML 自动转义功能。 有关自动转义的更多详细信息,下方会有所体现。 从 Jinja 2.4 开始,这也可以是传递模板名称的可调用对象,并且必须根据默认情况下启用自动转义返回 True 或 False。 - loader
此环境的模板加载器。 - cache_size
缓存的大小。 默认情况下这是 400,这意味着如果加载了超过 400 个模板,加载器将清除最近最少使用的模板。 如果缓存大小设置为 0,模板会一直重新编译,如果缓存大小为 -1,则不会清理缓存。jinja2 2.8从50改为了400。 - auto_reload
一些加载器从模板源可能更改的位置(即:文件系统或数据库)加载模板。 如果每次请求模板时将 auto_reload 设置为 True(默认),则加载器会检查源是否更改,如果是,它将重新加载模板。 为了获得更高的性能,可以禁用它。这是为了防止模板在使用过程中出现变动,从而重新加载模板,类似于热插拔。 - bytecode_cache
如果设置为字节码缓存对象,该对象将为内部 Jinja 字节码提供缓存,这样如果模板没有更改,就不必解析它们。
方法
- overlay([options])
创建一个新的覆盖环境,与当前环境共享所有数据,除了缓存和覆盖的属性。 无法删除覆盖环境的扩展。 覆盖的环境会自动获取它所链接的环境的所有扩展以及可选的额外扩展。
创建覆盖应该在初始环境完全设置之后发生。 并非所有属性都真正关联,有些属性只是被复制过来,因此对原始环境的修改可能无法通过。 - undefined([hint, obj, name, exc])
- add_extension(extension)
为当前环境添加扩展 - compile_expression(source, undefined_to_none=True)
2.1中新增
一个方便的辅助方法,它返回一个可调用对象,该可调用对象接受作为变量出现在表达式中的关键字参数。 如果被调用,则返回表达式的结果。默认情况下,如果表达式返回未定义的值,则返回值将转换为 None。 这可以通过将 undefined_to_none 设置为 False 来更改。 - compile_templates(target, extensions=None, filter_func=None, zip='deflated', log_function=None, ignore_errors=True, py_compile=False)
找到加载器可以找到的所有模板,编译它们并将它们存储在目标中。 如果 zip 为 None,而不是在 zipfile 中,模板将存储在一个目录中。 默认情况下,使用 deflate zip 算法。 要切换到存储算法,可以将 zip 设置为“已存储”。extensions 和 filter_func 被传递给 list_templates()。 返回的每个模板都将编译到目标文件夹或 zipfile。默认情况下,模板编译错误被忽略。 如果提供了日志功能,则会记录错误。 如果您希望模板语法错误中止编译,您可以将 ignore_errors 设置为 False,您将收到语法错误异常。 - extend(**attributes)
如果项目尚不存在,则将它们添加到环境实例中。 扩展使用它来注册回调和配置值,而不会破坏继承。 - from_string(source, globals=None, template_class=None)
从字符串中加载模板,返回一个模板对象 - get_or_select_template(template_name_or_list, parent=None, globals=None)
2.3中新增
如果给出了模板名称的可迭代对象,则进行类型检查并分派给select_template()
,否则分派给get_template()
。 - get_template(name, parent=None, globals=None)
从加载器加载模板。 如果配置了加载器,则此方法向加载器询问模板并返回模板。 如果 parent 参数不是 None,则在加载前调用 join_path() 以获取真实的模板名称。globals 参数可用于提供模板范围的全局变量。 这些变量在渲染时在上下文中可用。如果模板不存在,则会引发 TemplateNotFound 异常。 - list_templates(extensions=None, filter_func=None)
2.4新增
返回当前环境下的所有模板列表,这个需要加载器支持,如果模板文件夹还有其他文件,可以使用filter_func
参数做过滤。 - select_template(names, parent=None, globals=None)
2.3中新增
与get_template类似,不过会在失败前尝试多个模板,最后返回模板失败。
返回一个环境的模板列表,需要加载器支持
classjinja2.Template
核心的模板对象,由Enviroment
对象产生,对于一个模板对象来说,是不可修改的。
- globals
该模板的全局变量字典。修改这个字典是不安全的,因为它可能与其它模板或 加载这个模板的环境共享。 - name
模板的加载名。如果模板从字符串加载,这个值为 None 。 - filename
模板在文件系统上的文件名,如果没有从文件系统加载,这个值为 None 。 - render([context])
传入dict()或者以关键字参数形式传入,渲染模板文件并返回数据。 - generate([context])
与render
类似,不过是针对于大文件进行渲染, 逐行逐句,返回一个生成器 - stream([context])
与generate
类似。不过返回TemplateStream
对象 - make_module(vars=None, shared=False, locals=None)
此方法在不带参数调用时类似于模块属性,但它会在每次调用时评估模板而不是缓存它。 也可以提供一个字典,然后将其用作上下文。 参数与 new_context() 方法的参数相同。 - module
模板作为模块。 这用于模板运行时中的导入,但如果想要从 Python 层访问导出的模板变量也很有用:
classjinja2.environment.TemplateStream
模板流的工作方式与普通的 Python 生成器非常相似,但它可以缓冲多个项目以减少总迭代次数。 默认情况下,输出是无缓冲的,这意味着对于模板中的每条无缓冲指令,都会产生一个 unicode 字符串。如果在缓冲区大小为 5 的情况下启用缓冲,则将五个项目组合成一个新的 unicode 字符串。 如果您通过 WSGI 将大模板流式传输到客户端,这在每次迭代后刷新,这主要是有用的。
- disable_buffering()
禁止缓存 - dump(fp, encoding=None, errors='strict')
将完整的流转储到文件或类似文件的对象中。 默认情况下会写入 unicode 字符串,如果要在写入之前进行编码,请指定编码 - enable_buffering(size=5)
启用缓冲。 在产生它们之前缓冲大小项目。
自动转义
自动转义就是自动帮你将特殊的字符替换成转义符号。HTML(或者XML, XHTML)的特殊字符有 &, >, <, “, ‘。如下
<h1>hello</h1>
当开启自动转义后,将无法正常显示标签的含义。因此,在某些情况下,我们需要关闭自动转义或者对特定的文件格式做自动转义,可以使用Environment
中的autoescape
自定义转义格式要求,如下只对html、html、xml做自动转义:
def guess_autoescape(template_name):
if template_name is None or '.' not in template_name:
return False
ext = template_name.rsplit('.', 1)[1]
return ext in ('html', 'htm', 'xml')
env = Environment(autoescape=guess_autoescape,
loader=PackageLoader('mypackage'),
extensions=['jinja2.ext.autoescape'])
标识符的说明
在Jinjia2中,python3以上,有效的标识符必须匹配 [a-zA-Z_][a-zA-Z0-9_]*
, 也就是必须由字母、数字、下划线组成。但是对于过滤器(filters
)和测试(tests
)来说,过滤器和测试标识符的正则表达式是 [a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*
,相比有效标识符,多了个.
。
未定义类型
待更新
上下文
- classjinja2.runtime.Context
jinja2中的上下文,主要用于管理保存模板中的变量,上下文是不可变的并且只支持只读字典读取操作。
加载器
从类似于文件、数据库、其他资源中加载模板,并会保存在内存中,但是默认缓存有大小限制,由Environment
的cache_size
控制,加载器也可以自己重写,只需要继承BaseLoader
并重载 get_source
,基础的示例如下:
from jinja2 import BaseLoader, TemplateNotFound
from os.path import join, exists, getmtime
class MyLoader(BaseLoader):
def __init__(self, path):
self.path = path
def get_source(self, environment, template):
path = join(self.path, template)
if not exists(path):
raise TemplateNotFound(template)
mtime = getmtime(path)
with open(path) as f:
source = f.read()
return source, path, lambda: mtime == getmtime(path)
方法
- get_source(environment, template)
获取模板的模板源、文件名和重新加载帮助器。它传递了环境和模板名称,并且必须返回一个元组,元组形式为(source, filename, uptodate)
,如果找不到模板,则会出现TemplateNotFound
错误。
source: 模板中的字符串
filename:模板所在的路径字符串
uptodate:模板是否出现修改后,进行自动重新加载 - load(environment, name, globals=None)
加载一个模板,从缓存中查找,加载器(例如:class:PrefixLoader
或:class:ChoiceLoader
),不会调用此方法,而是直接调用get_source
内置的加载器
1.classjinja2.FileSystemLoader(searchpath, encoding='utf-8', followlinks=False)
从文件系统加载模板路径,是文件系统加载的首选方式。推荐使用
- searchpath: 查找的路径,可以是路径字符串、路径对象、路径列表
- encoding: 加载模板的编码,默认为
utf-8
- followlinks: 是否按照路径中的符号链接。默认为
false
2.classjinja2.PackageLoader(package_name, package_path='templates', encoding='utf-8')
从python的包中加载模板,此方法对于路径要求较为严苛,类似于代码中的导入
- package_name:加载的包名
- package_path:包名中的模板文件夹,默认为
templates
- encoding: 加载模板的编码,默认为
utf-8
3.classjinja2.DictLoader(mapping)
从python中的字典导入,将所有的模板文件以{模板文件名称:模板文件路径}
的形式存放于字典中
- mapping:需要加载的字典,键值对可为多个
4.classjinja2.FunctionLoader(load_func)
从方法中加载模板,一般在方法中做判断,返回模板文件
- load_func:需要加载的方法
def load_template(name):
if name == 'index.html':
return '...'
loader = FunctionLoader(load_template)
5.classjinja2.PrefixLoader(mapping, delimiter='/')
- mapping: 传入字典,以
将不同的模板文件夹分类,添加前缀,方便后续调用模板操作。例如
loader = PrefixLoader({
'app1': PackageLoader('mypackage.app1'),
'app2': PackageLoader('mypackage.app2')
})
此时使用app1/template.html
就可以获取到mypackage.app1
下面的模板文件了
6.classjinja2.ChoiceLoader(loaders)
依次查找模板,直到找到存在的模板停止
loader = ChoiceLoader([
FileSystemLoader('/path/to/user/templates'),
FileSystemLoader('/path/to/system/templates')
])
7.classjinja2.ModuleLoader(path)
从预编译的模板里加载模板,模板可以使用compile_templates
进行预编译,然后指定路径即可
- path:预编译模板的路径
字节码缓存
有利于加载模板时使用
实用工具
- jinja2.environmentfilter(f)
- jinja2.contextfilter(f)
- jinja2.evalcontextfilter(f)
装饰器,被修饰的方法或函数的第一个参数可以获取过滤器的当前环境、上下文、求值上下文,方便后续操作,例如查看是否开启自动转义、当前的模板文件及模板文件列表等。 - jinja2.environmentfunction(f)
- jinja2.contextfunction(f)
- jinja2.evalcontextfunction(f)
装饰器,被修饰的函数或方法的第一个参数可以获取当前的环境、上下文、求值上下文,例如查看是否开启自动转义、当前的模板文件及模板文件列表等。
上述六个方法都是类似的,都是为了获取环境、上下文等,在jinja3.0之后被pass_environment
,pass_eval_context
,pass_context
替代,下面的自定义过滤器中有描述。 - jinja2.escape(s)
jinja2 3.1.0之后,需from markupsafe import escape
导入
把字符串 s 中 & 、 < 、 > 、 ' 和 " 转换为HTML安全的序列。如果你需要在 HTML 中显示可能包含这些字符的文本,可以使用它,就是相当于转义。
import pathlib
from jinja2 import FileSystemLoader, pass_environment
from markupsafe import escape
template_path = (
pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)
env = Environment(
trim_blocks=True, lstrip_blocks=True, loader=FileSystemLoader(template_path)
)
@pass_environment
def fun(env: Environment, val):
print(env.autoescape)
return val
env.filters["fun"] = fun
template = env.get_template("template2.html")
data = template.render(seq=["<h1>1<h1>", "<h2>2<h2>", "<h3>3<h3>"])
print(data)
html_path = pathlib.Path(__file__).parent.joinpath("test.html")
with open(html_path, mode="w", encoding="utf-8") as w_f:
w_f.write(data)
False
False
False
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li><h1>1<h1></li>
<li><h2>2<h2></li>
<li><h3>3<h3></li>
</ul>
</body>
</html>
从结果看,默认是未开启自动转义的,因此结果的html标签是正常的,当对val添加escape
之后,修改如下
@pass_environment
def fun(env: Environment, val):
print(env.autoescape)
return escape(val)
False
False
False
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li><h1>1<h1></li>
<li><h2>2<h2></li>
<li><h3>3<h3></li>
</ul>
</body>
</html>
结果发现,html标签被转义了
- jinja2.Markup([string])
将字符串标记为可以安全地包含在 HTML/XML 输出中,而无需转义。
jinja2 3.1.0之后,需from markupsafe import Markup
导入,Markup可以对于字符串进行转义和不转义操作,也可以对于字符串进行一些字符串操作,例如首字母大写,是否是数字、字母等,
如下,和上方的escape
类似,当然也可以使用unescape
进行不转义操作
import pathlib
from jinja2 import FileSystemLoader, pass_environment
from markupsafe import Markup
template_path = (
pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)
env = Environment(
trim_blocks=True, lstrip_blocks=True, loader=FileSystemLoader(template_path)
)
@pass_environment
def fun(env: Environment, val):
print(env.autoescape)
return Markup.escape(val)
env.filters["fun"] = fun
template = env.get_template("template2.html")
data = template.render(seq=["<h1>1<h1>", "<h2>2<h2>", "<h3>3<h3>"])
print(data)
html_path = pathlib.Path(__file__).parent.joinpath("test.html")
with open(html_path, mode="w", encoding="utf-8") as w_f:
w_f.write(data)
异常
自定义过滤器
自定义过滤器只是常规的 Python 函数,过滤器左边作为第一个参数,其余的参数作 为额外的参数或关键字参数传递到过滤器。
例如在过滤器 {{ 42|myfilter(23) }}
中,函数被以 myfilter(42, 23)
调用, 注册过滤器一般有两种情况
**1.不依赖于上下文及环境
例如存在如下模板文件, fun为我们自定义的过滤器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
{% for item in seq %} {# test #}
<li>{{ item|fun() }}</li>
{% endfor %}
</ul>
</body>
</html>
python代码
我们设置了过滤器返回值大于3的值
import pathlib
from jinja2 import FileSystemLoader
def fun(val):
return val > 3
template_path = (
pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)
env = Environment(
trim_blocks=True, lstrip_blocks=True, loader=FileSystemLoader(template_path)
)
env.filters["fun"] = fun
template = env.get_template("template2.html")
data = template.render(seq=[1, 2, 3])
print(data)
结果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li>False</li>
<li>False</li>
<li>False</li>
</ul>
</body>
</html>
2.依赖上下文环境,需要上下文环境相关参数,
contextfilter
是上下文过滤器,传入的第一个参数为上下文context
evalcontextfilter
是环境过滤器,传入的第一个参数为eval_context
environmentfilter
:是求值上下文过滤器,传入的第一个参数为environment
需要注意,python3.0之后,
pass_context
替代 contextfunction
和 contextfilter
.
pass_eval_context
替代 evalcontextfunction
和 evalcontextfilter
pass_environment
替代 environmentfunction
和 environmentfilter
.
例如在设置自动转义的情况下不希望将部分标签进行转义, 使用方法如下:
import pathlib
from jinja2 import FileSystemLoader, pass_environment, pass_eval_context, pass_context
template_path = (
pathlib.Path(__file__).parent.joinpath("template_demo").joinpath("templates")
)
env = Environment(
trim_blocks=True, lstrip_blocks=True, loader=FileSystemLoader(template_path)
)
@pass_environment
def fun(env: Environment, val):
print(env.block_end_string)
print(env.filters)
return val > 3
env.filters["fun"] = fun
template = env.get_template("template2.html")
data = template.render(seq=[1, 2, 3])
print(data)
自定义过滤器注意事项
- 当在自定的过滤器的函数中使用到Markup中的方法时,需要对数据进行转义再调用方法,否则将出现报错, 例如
Markup.upper(Markup.escape(val))
求值上下文
求值上下文(缩写为 eval context 或 eval ctx ),2,4版本添加,就是上述自定义过滤器的evalcontextfilter
,它目前主要使用于启用或关闭自动转义,官方推荐使用求值上下文来获取转义状态。
from jinja2.nodes import EvalContext
@pass_eval_context
def fun(eval_ctx: EvalContext, val):
return eval_ctx.autoescape
自定义测试
与自定义过滤器类似,也是通过注册的方式,不过是tests
关键字,一般用于模板的类型与一致性检查
扩展
Jinja2 支持扩展来添加过滤器、测试、全局变量或者甚至是处理器,首先知道如何添加一个扩展。
添加扩展
目前添加扩展有三种方式,但都是基于Enviroment
进行设置。
1.使用Enviroment
中的extensions
设置扩展
env.extensions = []
2.在创建Enviroment
时指定extensions
env = Environment(
extensions=[],
loader=FileSystemLoader(TEMPLATE_PATH),
trim_blocks=True,
lstrip_blocks=True,
)
3.在创建完Enviroment
后,手动调用add_extension
添加扩展 jinja2.5新增
env.add_extension("")
常用扩展
- 表达式语句: jinja2.ext.do
“do”又叫做表达式语句扩展,向模板引擎添加了一个简单的 do 标签,其工作如同一个变量表达式,只是忽略返回值。
{% do sep.append("test")%}
{% for item in sep %}
{{item}}
{% endfor %}
如果没有do的话,sep变量将会显示未定义,do给人的感觉就是获取到了上下文。这段的结果sep中将会添加一个test字段。
- 循环控制(可以使用break以及continue):jinja2.ext.loopcontrols
默认情况下,jinja2 for循环中不支持break
和continue
,但添加完此插件后,就可以使用了,很方便。
{% for item in sep %}
{% if item is even %}
{% continue %}
{% endif %}
{{item}}
{% endfor %}
- With 语句(定义一个块级作用域):jinja2.ext.with_; Jinja2.3新增, Jinja2.9内置
with中会形成一个块级作用域,内部变量无法被外部访问到。
{% with foo = 42 %}
{{ foo }}
{% endwith %}
{{foo}}
{% with %}
{% set foo = 42 %}
{{ foo }}
{% endwith %}