Jinja中的多态宏
我想找到一种方法,可以让Jinja宏根据传入的对象类型调用不同的实现。简单来说,就是标准的Python方法多态性。目前,我正在使用一种看起来不太好看的变通方法,类似于这个:
{% macro menuitem(obj) %}
{% set type = obj.__class__.__name__ %}
{% if type == "ImageMenuItem" %}
{{ imagemenuitem(obj) }}
{% elif type == "FoobarMenuItem" %}
{{ foobarmenuitem(obj) }}
{% else %}
{{ textmenuitem(obj) }}
{% endif %}
{% endmacro %}
在纯Python中,可以通过修改模块环境来实现,比如使用globals()[x+'menuitem']
,虽然这样不太优雅,但效果很好。我尝试过用Jinja上下文做类似的事情,但后者似乎不包含宏定义。
还有什么更好的方法可以实现我想要的效果呢?
2 个回答
面向对象编程的核心就是:多态。
Create a presentation Layer for your objects:
class MenuPresentation:
def present(self):
raise NotImplementedException()
class ImageMenuPresentation(MenuPresentation):
def present(self):
return "magic url "
class TextMenuPresentation(MenuPresentation):
def present(self):
return "- text value here"
接下来就只是一个简单的问题:
{% macro menuitem(obj) %}
{{ obj.present() }}
{% endmacro %}
我现在解决了我的问题,方式和fabrizioM建议的类似,但有一个显著的不同:因为菜单项的展示可以(而且大多数情况下确实会)包含HTML,所以我不想在present
方法中直接处理HTML标记。因此,我最终在Python中实现了菜单定义,在Jinja中实现了展示,通过互相递归来连接这两者。
不同类型的菜单项通过不同的子类来表示:
class MenuItem(object):
def present(self, macromap):
return macromap[type(self).__name__](self, macromap)
class TextLink(MenuItem):
def __init__(self, url, text):
self.url, self.text = url, text
class Section(MenuItem):
def __init__(self, text, items):
self.text, self.items = text, items
class ImageLink(MenuItem):
...
上面提到的macromap
是一个字典,它将菜单项的类型映射到实现其展示的宏。所有这些都是在Jinja中定义的:
{% macro TextLink(l, macromap) %}
<a class="menuitem" href="{{l.url|escape}}">
{{ l.text|escape }}
</a>
{% endmacro %}
{% macro Section(s, macromap) %}
<div class="heading">{{s.text}}</div>
<ul class="items">
{% for item in s.items %}
<li>{{ item.present(macromap) }}</li>
{% endfor %}
</ul>
{% endmacro %}
{% set default_map = {'TextLink': TextLink, 'Section': Section, ...}
实际的菜单定义被清晰地表示为MenuItem
子类的树状结构:
main_menu = section("Main Menu", [
section("Product Line 1", [
TextLink("/products/...", "A product"),
...
]),
section(...),
])
为了开始展示,模板需要调用顶层部分的present
方法,并传入一个宏映射,以指定如何展示菜单,比如main_menu.present(default_map)
。在Section
宏中可以看到,菜单项可以请求它们的子项进行展示,这些子项的present
方法会再调用另一个Jinja宏,依此类推,形成递归。
虽然必须显式地传递宏映射看起来不太美观,但它带来了一个重要的好处:现在可以轻松渲染不同的菜单数据展示,而无需触碰菜单定义。例如,可以定义宏映射来渲染主网站菜单,或者为移动设备准备的变体(如果CSS不够用的话),或者XML网站地图,甚至是纯文本版本。(实际上,我们最终在网站菜单和网站地图的情况下使用了这个系统。)