如何在Twisted中使用Jinja?

3 投票
3 回答
2837 浏览
提问于 2025-04-17 05:40

我正在用Python开发一个讨论软件,打算使用Twisted、Storm和Jinja这几个工具。问题是,Jinja这个工具并不是为了和Twisted或者异步的socket库一起使用而设计的,而我选择Twisted的原因是它的性能很好,所以我不打算用Flask。

那么,我该怎么让Twisted用Jinja来渲染网页呢?

3 个回答

0

我觉得Tornado模板系统(它有点像Jinja2模板,因为它类似于Django)可以在不使用Tornado的情况下单独使用:

我们尝试清理代码,以减少模块之间的相互依赖,所以理论上你应该可以在你的项目中独立使用任何模块,而不需要使用整个包。

Tornado模板

2

这里有一个示例,展示了如何实现方案3,并且支持基本的延迟返回函数:

from jinja2 import Template
from twisted.internet import threads, reactor, defer

def inThread(f):
    def new_f(*args, **kwargs):
        return threads.deferToThread(f, *args, **kwargs)
    return new_f


def fromThread(f):
    def new_f(*args, **kwargs):
        return threads.blockingCallFromThread(reactor, lambda: defer.maybeDeferred(f, *args, **kwargs))
    return new_f


class DeferredTemplate(Template):
    def render(self, **kw):
        hooked_kw = {}
        for k, v in kw.iteritems():
            # decorate the callable so that they are run in the main thread
            if callable(v):
                v = fromThread(v)
            hooked_kw[k] = v
        return inThread(Template.render)(self, **hooked_kw)

from twisted.trial import unittest
class TestJinjaDeferred(unittest.TestCase):
    @defer.inlineCallbacks
    def test_basic(self):
        def getHello():
            d = defer.Deferred()
            reactor.callLater(0.0, lambda: d.callback("Hello"))
            return d

        def getWorldSync():
            return "world"

        template = DeferredTemplate("{{ getHello() }} {{ getWorldSync() }}")
        res = yield template.render(getHello=getHello, getWorldSync=getWorldSync)
        self.assertEqual(u"Hello world", res)
15

你可以用Jinja来渲染网页,就像在Twisted中使用其他Python库一样。你只需要调用它就行。这在Twisted中是可以正常工作的,不过如果Jinja做了一些会阻塞的操作,可能会遇到性能问题。需要注意的是,使用阻塞库在Twisted中也是可以的,可以通过deferToThread来实现,或者如果不影响性能的话,可以直接阻塞主循环。所以,我推测你的问题其实是关于如何在不阻塞的情况下使用Jinja。

Jinja是一个模板库,这意味着它会读取一个模板,执行一些视图逻辑,然后输出一些HTML内容。所以,有三个地方可能会导致阻塞:

  1. 读取模板,
  2. 写入结果,
  3. 运行视图逻辑(你的应用代码),

我对Jinja不太了解,所以不清楚每个API的具体结构,也不能告诉你该怎么做,但我猜这部分应该不难;所以,我会给你一个关于第三方模板库和Twisted的一般性回答。

我会逐一解决这些问题,虽然不一定按顺序:

1. 读取模板

这里最合理的做法是不去担心它。读取模板的速度通常很快。这些文件一般很小,操作系统几乎肯定会把它们放在文件系统缓存中。除非你做了一些疯狂的事情,比如把它们放在NFS上,否则你几乎不可能在读取时阻塞。如果你分析应用程序后发现这个是个问题——比如说,你的磁盘非常慢或者使用了远程文件系统——那么可以在启动时把模板读取到cStringIO或类似的地方,然后再传给Jinja。

3. 写入响应

网页的大小一般不大,Twisted也没有提供阻塞的API来写入套接字。相反,它提供了一种API,会把整个结果缓存在内存中,直到可以写出为止。我的建议是,在写入响应时,基本上可以和读取模板时做同样的事情:除非你的输出非常大,否则在响应传给客户端时,消耗一点内存是没问题的。

2. 运行你的视图逻辑

这是你最可能遇到问题的地方。Jinja可能不处理Deferred的结果。但实际上,直接给你带来问题的并不是Jinja,而是Storm。Storm期望在访问某些属性时能够阻塞,以进行数据库查询。与数据库的交互会导致阻塞,这是大多数Web应用中最主要的阻塞I/O来源。所以你需要决定如何处理这个问题。你有几个选择:

  1. 直接在主线程中执行,不用担心。也许你的应用是为10个人的工作组设计的,数据库是本地的。没错,你的I/O会阻塞,但如果它仍然满足性能要求,那又有什么关系呢?并不是每个应用都需要扩展到极限。
  2. deferToThread调用中预先获取Storm中的所有数据,确保Jinja只访问内存中的对象。这样你可以在主线程中运行渲染器,在执行数据库I/O的Deferred的回调中。如果你只访问内存中的对象,渲染器可能会花一点时间,但这没关系。这个问题让我写了一篇关于“阻塞”和“运行”区别的文章,你可能想去看看。
  3. 在一个线程或子进程中完成整个渲染,把它当作程序的一个阻塞组件。这会失去一些使用Twisted的好处,但这仍然是一个可行的策略,可以将阻塞的Jinja/Storm组件和非阻塞的纯Twisted组件结合起来,实际的聊天消息转发部分。

如果这些选项都不适合你,Twisted自11.0版本以来就包含了一个支持Deferred的模板库。你可以考虑使用twisted.web.template作为Jinja的替代方案。

撰写回答