Jinja2のWhitespace Control
- Python 3.6.5
- Jinja 2.10
Jinja2でfor
やif
を使ってテンプレートを書くと、出力される文字列に空行が多く入ってしまう。これはforやifのみが書いてある行にある改行も出力されてしまうから。
Jinja2にはWhitespace Controlという機能があり、この出力を制御できる。
Whitespace Controlでは+
と-
記号を使うのだが、空行削除の目的では-
をforやifにつける。e.g. {%- if ... %}
-
をつける場所は4か所あり、どこにつけるかによって挙動が変わる。具体的に出力を見るほうが理解しやすいため、4か所それぞれに-
をつけて出力を見ていく。
for文でのWhitespace Control挙動確認
用意したのは以下の通り何も設定しない場合と4パターン。
#!/usr/bin/env python
from jinja2 import Template
print('======================')
print('何も設定していない場合')
print(Template('''before for line.
{% for i in [1, 2, 3] %}
{{ i }}
{% endfor %}
after for line.''').render())
print('======================')
print('forの前に設定')
print(Template('''before for line.
{%- for i in [1, 2, 3] %}
{{ i }}
{% endfor %}
after for line.''').render())
print('======================')
print('forの後に設定')
print(Template('''before for line.
{% for i in [1, 2, 3] -%}
{{ i }}
{% endfor %}
after for line.''').render())
print('======================')
print('endforの前に設定')
print(Template('''before for line.
{% for i in [1, 2, 3] %}
{{ i }}
{%- endfor %}
after for line.''').render())
print('======================')
print('endforの後に設定')
print(Template('''before for line.
{% for i in [1, 2, 3] %}
{{ i }}
{% endfor -%}
after for line.''').render())
このPythonスクリプトを実行すると、出力は以下の通りになる。
======================
何も設定していない場合
before for line.
1
2
3
after for line.
======================
forの前に設定
before for line.
1
2
3
after for line.
======================
forの後に設定
before for line.
1
2
3
after for line.
======================
endforの前に設定
before for line.
1
2
3
after for line.
======================
endforの後に設定
before for line.
1
2
3
after for line.
for, endforともに何も設定していない場合
{% for ... %}
の行の改行がそのまま出力に反映されているため、各出力行の前に空行が出力されている。
ループの中にある構造は{{ i }}
だけでなく、for文と同じ行にある改行も含まれている。
for文と同じ行にある改行
{{ i }}改行
さらにendfor文の行末にも改行があるので、各出力行の前後に空行が出力されることになる。
before for line.
for行の改行
1
for行の改行
2
for行の改行
3
endfor行の改行
after for line.
forの前に設定されている場合
{%- for ... %}
の前の改行とスペースが削られている。つまりbefore for line.
の改行がなくなり、for文の行の改行が次にきている。
forの後に設定されている場合
{% for ... -%}
の後の改行とスペースが削られている。つまりfor文の行の改行と{{ i }}
自体のスペースがなくなっている。
endforの前に設定されている場合
{%- endfor %}
の前の改行とスペースが削られている。つまりループで毎回出力されているのは、for文の行の改行と{%- endfor %}
によって改行が削られた{{ i }}
となる。
ループごとの出力を[]
でくくってみる。
before for line.
[for行の改行
1][for行の改行
2][for行の改行
3]endfor行の改行
after for line.
endforの後に設定されている場合
{% endfor -%}
の後の改行とスペースが削られている。つまりendfor文の行の改行がなくなり、after for line.
が直後にきている。
パターンから見る原理原則
-
がついた箇所が制御文の前なのか後なのかで挙動が変わり、前であればその制御文の前の改行とスペースが、後であればその制御文の後の改行とスペースが消えてなくなるという仕組みになっていることが、4パターンの出力結果を見比べると把握しやすい。
for文でよく使うWhitespace Controlの設定場所
forとendforを消す
先ほどの出力結果パターンでいうと、多くの場合期待されるものは以下になるだろう。
before for line.
1
2
3
after for line.
「endforの前に設定」パターンが一番近いが、ループがfor行の改行から始まるために、二行目だけ空行ができてしまう。
before for line.
[for行の改行
1][for行の改行
2][for行の改行
3]endfor行の改行
after for line.
before for line.
行の改行が消せれば、二行目の改行が一行目に繰り上がり、期待する出力となる。そのため「forの前に設定」を組み合わせればいい。
print('======================')
print('forの前とendforの前に設定')
print(Template('''before for line.
{%- for i in [1, 2, 3] %}
{{ i }}
{%- endfor %}
after for line.''').render())
期待通りの出力結果が得られた。
======================
forの前とendforの前に設定
before for line.
1
2
3
after for line.
改行なしで連続で出力する
もうひとつの期待される出力は連続で1行で値を出力するもの
before for line.
123
after for line.
「forの後に設定」で{{ i }}
の前のスペースを、「endforの前に設定」で{{ i }}
の後の改行を、それぞれ削除できる。
print('======================')
print('forの後とendforの前に設定')
print(Template('''before for line.
{% for i in [1, 2, 3] -%}
{{ i }}
{%- endfor %}
after for line.''').render())
if文でのWhitespace Control挙動確認
if文の原理原則もfor文と同じだが、繰り返し構文ではないため、Whitespace Controlをendifの前につけたときと後につけたときで出力差がなくなっている。
print('======================')
print('何も設定していない場合')
print(Template('''before if line.
{% if True %}
True
{% endif %}
after if line.''').render())
print('======================')
print('ifの前に設定')
print(Template('''before if line.
{%- if True %}
True
{% endif %}
after if line.''').render())
print('======================')
print('ifの後に設定')
print(Template('''before if line.
{% if True -%}
True
{% endif %}
after if line.''').render())
print('======================')
print('endifの前に設定')
print(Template('''before if line.
{% if True %}
True
{%- endif %}
after if line.''').render())
print('======================')
print('endifの後に設定')
print(Template('''before if line.
{% if True %}
True
{% endif -%}
after if line.''').render())
出力は次の通り。
======================
何も設定していない場合
before if line.
True
after if line.
======================
ifの前に設定
before if line.
True
after if line.
======================
ifの後に設定
before if line.
True
after if line.
======================
endifの前に設定
before if line.
True
after if line.
======================
endifの後に設定
before if line.
True
after if line.
if文でよく使うWhitespace Controlの設定場所
こちらもfor文と同じ考えで問題ない。次の二つがよくある期待出力と設定位置となる。
======================
ifの前とendifの前に設定
before if line.
True
after if line.
======================
ifの後とendifの前に設定
before if line.
True
after if line.
trim_blocksとlstrip_blocks
毎回-
で挙動を制御してもいいのだが、trim_blocks
とlstrip_blocks
でテンプレート出力のデフォルト動作を変えることで、-
を制御文に設定するのと似た動作をさせられる。
#!/usr/bin/env python
from jinja2 import Environment
template_str = '''before for line.
{% for i in [1, 2, 3] %}
{{ i }}
{% endfor %}
after for line.'''
env = Environment()
tb_env = Environment(trim_blocks=True)
lb_env = Environment(lstrip_blocks=True)
tb_lb_env = Environment(trim_blocks=True, lstrip_blocks=True)
print('======================')
print('default settings')
print(env.from_string(template_str).render())
print('======================')
print('trim_blocks=True')
print(tb_env.from_string(template_str).render())
print('======================')
print('lstrip_blocks=True')
print(lb_env.from_string(template_str).render())
print('======================')
print('trim_blocks=True, lstrip_blocks=True')
print(tb_lb_env.from_string(template_str).render())
出力は以下の通り。
======================
default settings
before for line.
1
2
3
after for line.
======================
trim_blocks=True
before for line.
1
2
3
after for line.
======================
lstrip_blocks=True
before for line.
1
2
3
after for line.
======================
trim_blocks=True, lstrip_blocks=True
before for line.
1
2
3
after for line.
trim_blocks
は{% for ... %}改行
と{% endfor %}改行
の出力を消してくれる。lstrip_blocks
は{% for ... %}
と{% endfor %}
と同じ行の文頭にスペースがあれば、それを消してくれる。(スペースなので視覚的に見れないが、上記出力をカーソルで選択して反転させると、lstrip_blocks
をTrue
にしたときにforとendforの前にあるスペースが消えていることがわかる。)
両方のオプションを有効化すると、多くの場合期待される出力結果を得ることができる。
新規テンプレートを作成する場合にtrim_blocks
とlstrip_blocks
を有効にした方がいいのかどうかについて、個人的な結論はまだ出せていないが、以下の点を考慮して、-
を使う場合の挙動をよく把握しておくことが大切だと思う。
-
を使うほうが出力制御の幅が広いtrim_blocks
,lstrip_blocks
が有効化されていない既存のテンプレートを修正するときに、他に影響を与えず局所的に出力制御できる