在Jinja巨集裡使用*args和**kwargs

greylihui發表於2019-02-28

這段時間有多個讀者問關於Jinja巨集定義時的引數接受問題。這一點在《Flask Web開發實戰》裡沒有介紹,這篇文章作為一個補充。

一個不符合直覺的設定

在某個晴朗的早晨,你開啟電腦,想在你的專案Jinja模板裡編寫一個巨集來簡化操作。按照直覺,你可能會像定義Python函式那樣來定義巨集,傳入**kwargs來讓它接收任意數量的關鍵字引數,比如:

{% macro say_hello(**kwargs) %}
     ...
{% endmacro %}複製程式碼

或是傳入*args讓它接收任意數量的位置引數:

{% macro say_hello(*args) %}
     ...
{% endmacro %}複製程式碼

遺憾的是,上面的呼叫會分別獲得下面的錯誤資訊:

jinja2.exceptions.TemplateSyntaxError: expected token `name`, got `**`複製程式碼
jinja2.exceptions.TemplateSyntaxError: expected token `name`, got `*`複製程式碼

在Jinja巨集裡接收額外的關鍵字引數和位置引數

在Jinja中,巨集預設會自動接收額外的關鍵字引數和位置引數,並在巨集內部提供kwargsvarargs特殊變數來獲取它們。具體來說,在定義巨集的時候,不需要進行任何宣告。在巨集的內部,你可以直接使用kwargs字典來獲取額外的關鍵字引數;同樣的,你可以使用varargs元組來獲取額外傳入的位置引數。

下面是使用kwargs的示例:

{% macro say_hello() %}
    <p>Hello, {{ kwargs[`name`] }}!</p>
{% endmacro %}

{# 呼叫示例 #}
{{ say_hello(name=`Grey`)}}複製程式碼

你可以把這個字典傳遞給其他函式,比如url_for():

{% macro nav_link(endpoint, text) %}
    <a href="{{ url_for(endpoint, **kwargs) }}">{{ text }}</a>
{% endmacro %} 

{# 呼叫示例 #}
{{ nav_link(`index`, `Home`, foo=`value1`, bar=`value2`)}}複製程式碼

下面是使用varargs的示例:

{% macro say_hello() %}
    <p>Hello, {{ varargs[0] }}!</p>
{% endmacro %}

{# 呼叫示例 #}
{{ say_hello(`Grey`)}}複製程式碼

提示 在巨集內部,如果kwargs字典裡沒有對應的鍵,那麼會返回空字串,而不是丟擲KeyError異常;如果向varargs元組索引一個超出範圍的下標值,也會返回空值,而不會丟擲IndexError異常。

隱藏的陷阱

雖然巨集自動處理額外傳入的關鍵字引數和位置引數,但是這裡有一個隱藏的小陷阱。如果你在呼叫一個巨集的時候傳入了額外的關鍵字引數和位置引數,但是巨集的內部並沒有使用它們,這時就會出錯。比如下面使用kwargs的示例:

{% macro say_hello() %}
    <p>Hello!</p>
{% endmacro %} 

{# 呼叫示例 #} 
{{ say_hello(name=`Grey`)}}複製程式碼

呼叫巨集的時候傳入了name引數,但是巨集內部並沒有使用它,這時Jinja會丟擲下面的異常:

TypeError: macro `say_hello` takes no keyword argument `name`複製程式碼

類似的是位置引數,你會獲得下面的異常:

TypeError: macro `say_hello` takes not more than 1 argument(s)複製程式碼

所以,如果你想讓某個巨集接收額外的關鍵字引數或位置引數,你就分別需要在這個巨集內部至少呼叫一次(access)kwargs字典或是varargs元組。一般情況下,你並不需要擔心這個問題。

本文隸屬於《Flask Web開發實戰》番外文章系列。

相關文章