Tornado Python Web应用程序框架简介

在这个由四部分组成的系列文章的前两篇文章中,比较了不同的Python Web框架,我们介绍了PyramidFlask Web框架。 我们已经两次构建了同一个应用程序,并看到了完整的DIY框架与包含更多电池的框架之间的异同。

现在让我们看一个稍微不同的选项: Tornado framework 。 Tornado在大多数情况下与Flask一样简单,但有一个主要区别:Tornado专为处理异步流程而构建。 在我们在本系列中构建的应用程序中,这种特殊的调料并不是非常有用,但是我们将看到我们可以在哪里使用它以及它在更一般的情况下如何工作。

让我们继续在前两篇文章中设置的模式,并从设置和配置开始。

龙卷风启动和配置

如果您一直遵循本系列文章,那么我们首先要做的事情就不足为奇了。


   
   
$ mkdir tornado_todo
$ cd tornado_todo
$ pipenv install --python 3.6
$ pipenv shell
(tornado-someHash) $ pipenv install tornado

创建一个setup.py以安装我们的应用程序:

 (tornado-someHash) $ touch setup.py 

   
   
# setup.py
from setuptools import setup , find_packages

requires = [
    'tornado' ,
    'tornado-sqlalchemy' ,
    'psycopg2' ,
]

setup (
    name = 'tornado_todo' ,
    version = '0.0' ,
    description = 'A To-Do List built with Tornado' ,
    author = '<Your name>' ,
    author_email = '<Your email>' ,
    keywords = 'web tornado' ,
    packages = find_packages ( ) ,
    install_requires = requires ,
    entry_points = {
        'console_scripts' : [
            'serve_app = todo:main' ,
        ] ,
    } ,
)

因为Tornado不需要任何外部配置,所以我们可以直接研究编写将运行我们的应用程序的Python代码。 让我们进入内部todo目录,并用我们需要的前几个文件填充它。


   
   
todo/
    __init__.py
    models.py
    views.py

像Flask和Pyramid一样,Tornado具有一些将在__init__.py中央配置。 从tornado.web ,我们将导入Application对象。 这将处理用于路由和视图的连接,包括我们的数据库(到达时)和运行Tornado应用程序所需的任何其他设置。


   
   
# __init__.py
from tornado. web import Application

def main ( ) :
    """Construct and serve the tornado application."""
    app = Application ( )

像Flask一样,Tornado是主要是DIY框架。 在构建应用程序时,我们必须设置应用程序实例。 由于Tornado使用自己的HTTP服务器为应用程序提供服务,因此我们还必须设置如何为应用程序提供服务。 首先,我们使用tornado.options.define定义要侦听的端口。 然后,我们实例化Tornado的HTTPServer ,并传递Application对象的实例作为其参数。


   
   
# __init__.py
from tornado. httpserver import HTTPServer
from tornado. options import define , options
from tornado. web import Application

define ( 'port' , default = 8888 , help = 'port to listen on' )

def main ( ) :
    """Construct and serve the tornado application."""
    app = Application ( )
    http_server = HTTPServer ( app )
    http_server. listen ( options. port )

当使用define函数时,最终会在options对象上创建属性。 第一个参数位置上的所有内容将是属性的名称,而分配给default关键字参数的内容将是该属性的值。

例如,如果我们将属性potato而不是port命名,则可以通过options.potato访问其值。

HTTPServer上调用listen尚未启动服务器。 我们必须再做一个步骤,才能拥有一个可以监听请求并返回响应的工作应用程序。 我们需要一个输入输出循环。 值得庆幸的是,Tornado带有tornado.ioloop.IOLoop形式的tornado.ioloop.IOLoop


   
   
# __init__.py
from tornado. httpserver import HTTPServer
from tornado. ioloop import IOLoop
from tornado. options import define , options
from tornado. web import Application

define ( 'port' , default = 8888 , help = 'port to listen on' )

def main ( ) :
    """Construct and serve the tornado application."""
    app = Application ( )
    http_server = HTTPServer ( app )
    http_server. listen ( options. port )
    print ( 'Listening on http://localhost:%i' % options. port )
    IOLoop. current ( ) . start ( )

我喜欢某处的print声明,该声明可以告诉我何时为我的应用程序提供服务,但这就是我。 您可以选择不使用print行。

我们从IOLoop.current().start()开始I / O循环。 让我们更多地讨论输入,输出和异步性。

Python和I / O循环中的异步基础

首先,请允许我说我绝对,肯定,肯定且安全地不是异步编程方面的专家。 就像我写的所有东西一样,接下来的事情源于我对该概念的理解的局限性。 由于我是人类,因此可能存在深深的缺陷。

异步程序的主要问题是:

  • 数据如何进入?
  • 数据如何输出?
  • 什么时候可以让某些程序在不引起我全部注意力的情况下运行?

由于具有全局解释器锁 (GIL),Python在设计上是一种单线程语言。 对于Python程序必须执行的每个任务,其执行线程将在该任务的整个过程中全神贯注于该任务。 我们的HTTP服务器是用Python编写的。 因此,当接收到数据(例如,HTTP请求)时,服务器的唯一焦点是传入数据。 这意味着,在大多数情况下,处理和处理数据所需的任何过程都将完全消耗服务器的执行线程,从而阻止其他潜在数据被接收,直到服务器完成所需的操作为止。

在许多情况下,这并不太成问题; 一个典型的Web请求-响应周期仅需几分之一秒。 随之而来的是,构建HTTP服务器所基于的套接字可以维护待处理传入请求的积压。 因此,如果某个请求在该套接字正在处理其他事情的同时进入,则可能只是在排队之前稍等一会儿才被处理。 对于低到中等流量的站点,不到一秒钟的时间就没什么大不了的,您可以使用多个已部署的实例以及诸如NGINX的负载均衡器来为较大的请求负载分配流量。

但是,如果您的平均响应时间花费不到一秒钟的时间怎么办? 如果您使用传入请求中的数据来启动一些长时间运行的过程(例如机器学习算法或一些大型数据库查询),该怎么办? 现在,您的单线程Web服务器开始积累无法解决的请求积压,其中的一些由于简单的超时而将被丢弃。 这不是一个选择,特别是如果您希望定期将您的服务视为可靠的。

但是,如果结构正确,那么只要您指定某个函数应具有的能力,异步Python程序就可以“搁置”长期运行的任务。 然后,当搁置的任务完成并准备好恢复时,可以向您的异步控制器发出警报,仅在需要时管理它们的执行,而不会完全阻止对新输入的处理。

那有点行话,所以让我们用一个人类的例子来演示。

带回家

我经常发现自己试图在家里花费很少的时间完成多项杂务。 在给定的一天,积压的杂务可能看起来像:

  • 煮一餐(准备20分钟,煮40分钟)
  • 洗碗(60分钟)
  • 洗涤和干燥衣物(每次洗涤30分钟,每次装载90分钟干燥)
  • 真空地板(30分钟)

如果我是一个传统的同步程序,我将自己亲自完成每个任务。 在考虑处理其他任何事情之前,每个任务都需要我全神贯注才能完成,因为如果没有我的积极关注,什么也做不了。 因此,我的执行顺序可能类似于:

  1. 完全专注于准备和烹饪餐点,包括等待食物只是……做饭(60分钟)。
  2. 将脏盘子转移到水槽(经过65分钟)。
  3. 洗净所有盘子(经过125分钟)。
  4. 我要全神贯注地开始洗衣,包括等待洗衣机完成,然后将衣物转移到烘干机上,以及等待烘干机完成(经过250分钟)。
  5. 用真空吸尘器清理地板(经过280分钟)。

从头到尾完成我的琐事需要4个小时40分钟。

除了努力工作,我还应该像异步程序一样聪明地工作。 我的家里到处都是机器,无需我不断努力就能为我完成工作。 同时,我可以切换我注意什么可积极需要它现在

我的执行顺序可能改为:

  1. 将衣服放进洗衣机,然后启动洗衣机(5分钟)。
  2. 在洗衣机运行时,准备食物(经过25分钟)。
  3. 准备食物后,开始烹饪食物(经过30分钟)。
  4. 在烹饪食物时,将衣物从洗衣机移入烘干机,然后启动烘干机(经过35分钟)。
  5. 当烘干机运行且食物仍在烹饪时,用吸尘器吸尘地板(经过65分钟)。
  6. 用地板吸尘后,将食物从火炉上取下来,然后装入洗碗机(经过70分钟)。
  7. 运行洗碗机(完成后130分钟)。

现在我只剩下2小时10分钟了。 即使我有更多的时间在工作之间进行切换(总共要多花10到20分钟),但如果我等待按顺序执行每个任务,我的时间仍将减少一半。 这是将程序结构化为异步的能力。

那么I / O循环从何而来?

异步Python程序的工作原理是从某个外部源(输入)接收数据,并且在该过程需要时,将该数据卸载到某个外部工作器(输出)以进行处理。 当该外部过程完成时,将通知主Python程序。 然后,程序将获取该外部处理(输入)的结果,并继续进行愉快的工作。

只要该数据没有交到主Python程序中,该主程序就可以自由地用于其他任何事情。 这包括等待全新的输入(例如HTTP请求)和处理长时间运行的过程的结果(例如机器学习算法的结果,长时间运行的数据库查询)。 主程序虽然仍然是单线程的,但它变为事件驱动的,并针对该程序处理的特定事件而触发动作。 I / O循环是侦听这些事件并指示应如何处理的主要工作人员。

我知道,我们走了很长一段路才能得到这个解释,但是我希望在这里传达的是这不是魔术,也不是某种复杂的并行处理或多线程工作。 全局解释器锁定仍然存在; 主程序中任何长时间运行的进程仍将阻止其他任何事情的发生。 该程序还是单线程的。 但是,通过将繁琐的工作外部化,我们可以使该线程仅关注其需要注意的内容。

这有点像我上面的异步杂务。 当准备食物完全需要我的注意力时,这就是我要做的。 但是,当我可以通过做饭来让火炉为我工作,用洗碗机洗碗,用洗衣机和干衣机来处理衣物时,我的精力可以转移到其他事情上。 当我收到一条我的长期运行任务已完成并准备再次进行处理的警报时,如果有空,我可以拿起该任务的结果,然后做下一步需要做的事情。

龙卷风的路线和美景

尽管经历了谈论Python异步的所有麻烦,但我们将稍作停留,首先编写一个基本的Tornado视图。

与我们在Flask和Pyramid实现中看到的基于函数的视图不同,Tornado的视图都是基于类的 。 这意味着我们将不再使用单独的独立函数来规定如何处理请求。 相反,传入的HTTP请求将被捕获并分配为我们定义的类的属性。 然后,其方法将处理相应的请求类型。

让我们从在屏幕上打印“ Hello,World”的基本视图开始。 我们为Tornado应用程序构造的每个基于类的视图都必须继承tornado.webRequestHandler对象。 这将设置我们需要(但不想编写)来接受请求并构建格式正确的HTTP响应的所有底层逻辑。


   
   
from tornado. web import RequestHandler

class HelloWorld ( RequestHandler ) :
    """Print 'Hello, world!' as the response body."""

    def get ( self ) :
        """Handle a GET request for saying Hello World!."""
        self . write ( "Hello, world!" )

因为我们要处理GET请求,所以我们声明(确实覆盖了) get方法。 我们不提供任何返回内容,而是提供文本或JSON可序列化的对象,并使用self.write将其写入响应主体。 之后,我们让RequestHandler承担发送响应之前必须完成的其余工作。

就目前而言,该视图与Tornado应用程序本身没有实际联系。 我们必须回到__init__.py并稍微更新main函数。 这是新的热点:


   
   
# __init__.py
from tornado. httpserver import HTTPServer
from tornado. ioloop import IOLoop
from tornado. options import define , options
from tornado. web import Application
from todo. views import HelloWorld

define ( 'port' , default = 8888 , help = 'port to listen on' )

def main ( ) :
    """Construct and serve the tornado application."""
    app = Application ( [
        ( '/' , HelloWorld )
    ] )
    http_server = HTTPServer ( app )
    http_server. listen ( options. port )
    print ( 'Listening on http://localhost:%i' % options. port )
    IOLoop. current ( ) . start ( )

我们该怎么办?

我们将HelloWorld视图从views.py文件导入到脚本顶部的__init__.py中。 然后,我们将路由视图对列表添加为Application实例化的第一个参数。 每当我们要在应用程序中声明路由时,都必须将其绑定到视图。 如果需要,可以将同一视图用于多条路线,但是每条路线都必须始终有一个视图。

我们可以使用在setup.py启用的serve_app命令运行我们的应用程序,以确保一切正常。 检查http://localhost:8888/并显示“您好,世界!”。

当然,在这个领域我们还有更多可以做的事情,但让我们继续进行模型研究。

连接数据库

如果要保留数据,则需要连接数据库。 与Flask一样,我们将使用称为tornado-sqlalchemy的特定于框架SQLAlchemy变体。

为什么要使用它而不是仅使用裸露的SQLAlchemy ? 好, tornado-sqlalchemy具有直接SQLAlchemy的所有优点,因此我们仍然可以使用通用Base声明模型,并使用我们已经习惯的所有列数据类型和关系。 除了我们从习惯中已经知道的内容之外, tornado-sqlalchemy还为其数据库查询功能提供了一种可访问的异步模式,专门用于与Tornado的现有I / O循环配合使用。

我们通过将tornado-sqlalchemypsycopg2添加到setup.py到所需软件包的列表中来设置阶段,然后重新安装软件包。 在models.py ,我们声明我们的模型。 此步骤看起来与我们在Flask和Pyramid中已经看到的完全一样,因此我将跳过完整类的声明,仅提出Task模型的必要条件。


   
   
# this is not the complete models.py, but enough to see the differences
from tornado_sqlalchemy import declarative_base

Base = declarative_base

class Task ( Base ) :
    # and so on, because literally everything's the same...

我们仍然必须将tornado-sqlalchemy连接到实际应用程序。 在__init__.py ,我们将定义数据库并将其集成到应用程序中。


   
   
# __init__.py
from tornado. httpserver import HTTPServer
from tornado. ioloop import IOLoop
from tornado. options import define , options
from tornado. web import Application
from todo. views import HelloWorld

# add these
import os
from tornado_sqlalchemy import make_session_factory

define ( 'port' , default = 8888 , help = 'port to listen on' )
factory = make_session_factory ( os . environ . get ( 'DATABASE_URL' , '' ) )

def main ( ) :
    """Construct and serve the tornado application."""
    app = Application ( [
        ( '/' , HelloWorld )
    ] ,
        session_factory = factory
    )
    http_server = HTTPServer ( app )
    http_server. listen ( options. port )
    print ( 'Listening on http://localhost:%i' % options. port )
    IOLoop. current ( ) . start ( )

就像我们在金字塔中传递的会话工厂一样,我们可以使用make_session_factory来获取数据库URL并生成一个对象,其唯一目的是为我们的视图提供与数据库的连接。 然后,通过使用session_factory关键字参数将新创建的factory传递到Application对象中,将其绑定到我们的应用程序中。

最后,初始化和管理数据库的方式与对Flask和Pyramid进行的方式相同(即,单独的DB管理脚本,针对Base对象的操作等)。 它看起来非常相似,我在这里不再赘述。

重访观点

您好,World总是很适合学习基础知识,但是我们需要一些真实的,特定于应用程序的视图。

让我们从信息视图开始。


   
   
# views.py
import json
from tornado. web import RequestHandler

class InfoView ( RequestHandler ) :
    """Only allow GET requests."""
    SUPPORTED_METHODS = [ "GET" ]

    def set_default_headers ( self ) :
        """Set the default response header to be JSON."""
        self . set_header ( "Content-Type" , 'application/json; charset="utf-8"' )

    def get ( self ) :
        """List of routes for this API."""
        routes = {
            'info' : 'GET /api/v1' ,
            'register' : 'POST /api/v1/accounts' ,
            'single profile detail' : 'GET /api/v1/accounts/<username>' ,
            'edit profile' : 'PUT /api/v1/accounts/<username>' ,
            'delete profile' : 'DELETE /api/v1/accounts/<username>' ,
            'login' : 'POST /api/v1/accounts/login' ,
            'logout' : 'GET /api/v1/accounts/logout' ,
            "user's tasks" : 'GET /api/v1/accounts/<username>/tasks' ,
            "create task" : 'POST /api/v1/accounts/<username>/tasks' ,
            "task detail" : 'GET /api/v1/accounts/<username>/tasks/<id>' ,
            "task update" : 'PUT /api/v1/accounts/<username>/tasks/<id>' ,
            "delete task" : 'DELETE /api/v1/accounts/<username>/tasks/<id>'
        }
        self . write ( json. dumps ( routes ) )

那么,什么改变了? 让我们从上到下。

SUPPORTED_METHODS类属性已添加。 这将仅迭代该视图接受的请求方法。 任何其他方法将返回405状态代码。 当我们创建HelloWorld视图时,我们并没有指定它,主要是出于懒惰。 如果没有此类属性,则该视图将响应任何尝试访问绑定到该视图的路由的请求。

声明了set_default_headers方法,该方法设置传出HTTP响应的默认标头。 我们在此声明以确保发送回的任何响应的"Content-Type""application/json"

我们增加了json.dumps(some_object)到的参数self.write ,因为它可以很容易地构建传出响应的主体内容。

现在完成了,我们可以继续将它连接到__init__.py的本地路由。


   
   
# __init__.py
from tornado. httpserver import HTTPServer
from tornado. ioloop import IOLoop
from tornado. options import define , options
from tornado. web import Application
from todo. views import InfoView

# add these
import os
from tornado_sqlalchemy import make_session_factory

define ( 'port' , default = 8888 , help = 'port to listen on' )
factory = make_session_factory ( os . environ . get ( 'DATABASE_URL' , '' ) )

def main ( ) :
    """Construct and serve the tornado application."""
    app = Application ( [
        ( '/' , InfoView )
    ] ,
        session_factory = factory
    )
    http_server = HTTPServer ( app )
    http_server. listen ( options. port )
    print ( 'Listening on http://localhost:%i' % options. port )
    IOLoop. current ( ) . start ( )

我们知道,将需要编写更多视图和路线。 每一个都将根据需要放入“ Application路由”列表中。 每个都还需要一个set_default_headers方法。 最重要的是,我们将创建send_response方法,该方法的任务是将响应以及我们要为给定响应设置的任何自定义状态代码打包在一起。 由于每个方法都需要两种方法,因此我们可以创建一个包含它们的基类,我们的每个视图都可以从这些基类继承。 这样,我们只需编写一次即可。


   
   
# views.py
import json
from tornado. web import RequestHandler

class BaseView ( RequestHandler ) :
    """Base view for this application."""

    def set_default_headers ( self ) :
        """Set the default response header to be JSON."""
        self . set_header ( "Content-Type" , 'application/json; charset="utf-8"' )

    def send_response ( self , data , status = 200 ) :
        """Construct and send a JSON response with appropriate status code."""
        self . set_status ( status )
        self . write ( json. dumps ( data ) )

对于我们即将编写的类似TaskListView的视图,我们还需要与数据库的连接。 我们需要tornado_sqlalchemySessionMixin才能在每个视图类中添加数据库会话。 我们可以将其折叠到BaseView以便默认情况下,从其继承的每个视图都可以访问数据库会话。


   
   
# views.py
import json
from tornado_sqlalchemy import SessionMixin
from tornado. web import RequestHandler

class BaseView ( RequestHandler , SessionMixin ) :
    """Base view for this application."""

    def set_default_headers ( self ) :
        """Set the default response header to be JSON."""
        self . set_header ( "Content-Type" , 'application/json; charset="utf-8"' )

    def send_response ( self , data , status = 200 ) :
        """Construct and send a JSON response with appropriate status code."""
        self . set_status ( status )
        self . write ( json. dumps ( data ) )

只要我们正在修改此BaseView对象,我们就应该解决一个奇怪的问题,即当我们考虑将数据发布到此API时。

当Tornado(从v.4.5开始)使用客户端的数据并将其组织起来以供应用程序使用时,它将所有传入的数据保留为字节串。 但是,这里所有代码都假定使用Python 3,因此我们要使用的唯一字符串是Unicode字符串。 我们可以在此BaseView类中添加另一个方法,该方法的作用是将传入数据转换为Unicode,然后再在视图中的其他任何地方使用它。

如果要在适当的view方法中使用该数据之前先对其进行转换,则可以重写view类的本机prepare方法。 它的工作是在运行view方法之前运行。 如果我们重写prepare方法,则可以设置一些逻辑来运行,以便每当收到请求时都执行字节串到Unicode的转换。


   
   
# views.py
import json
from tornado_sqlalchemy import SessionMixin
from tornado. web import RequestHandler

class BaseView ( RequestHandler , SessionMixin ) :
    """Base view for this application."""

    def prepare ( self ) :
        self . form_data = {
            key: [ val. decode ( 'utf8' ) for val in val_list ]
            for key , val_list in self . request . arguments . items ( )
        }

    def set_default_headers ( self ) :
        """Set the default response header to be JSON."""
        self . set_header ( "Content-Type" , 'application/json; charset="utf-8"' )

    def send_response ( self , data , status = 200 ) :
        """Construct and send a JSON response with appropriate status code."""
        self . set_status ( status )
        self . write ( json. dumps ( data ) )

如果有任何数据输入,可以在self.request.arguments词典中找到它。 我们可以通过键访问该数据并将其内容(总是一个列表)转换为Unicode。 因为这是基于类的视图,而不是基于函数的视图,所以我们可以将修改后的数据存储为实例属性,以供以后使用。 我在这里将其称为form_data ,但它也可以轻松称为potato 。 关键是我们可以存储已提交给应用程序的数据。

异步查看方法

现在,我们已经构建了BaseView ,我们可以构建将从其继承的TaskListView

正如您可能从本节标题中看到的那样,这是所有有关异步性的内容的来源TaskListView将处理GET请求以返回任务列表,而POST请求则以给定表单数据的形式创建新任务。 首先让我们看一下处理GET请求的代码。


   
   
# all the previous imports
import datetime
from tornado. gen import coroutine
from tornado_sqlalchemy import as_future
from todo. models import Profile , Task

# the BaseView is above here
class TaskListView ( BaseView ) :
    """View for reading and adding new tasks."""
    SUPPORTED_METHODS = ( "GET" , "POST" , )

    @ coroutine
    def get ( self , username ) :
        """Get all tasks for an existing user."""
        with self . make_session ( ) as session:
            profile = yield as_future ( session. query ( Profile ) . filter ( Profile. username == username ) . first )
            if profile :
                tasks = [ task. to_dict ( ) for task in profile . tasks ]
                self . send_response ( {
                    'username' : profile . username ,
                    'tasks' : tasks
                } )

这里的第一个主要部分是@coroutine装饰器,它是从tornado.gen导入的。 任何具有与调用堆栈的正常流程不同步的行为的Python可调用对象实际上是“协同例程”; 可以与其他例程一起运行的例程。 以我的家务为例,几乎每个家务都是例行公事。 有些阻止了例行程序(例如,打扫地板),但是该例行程序只是阻止了我开始或参加其他任何事情的能力。 它不会阻止其他已经启动的例程继续运行。

Tornado提供了多种利用协同例程构建应用程序的方法,包括允许我们在函数调用上设置锁定,同步异步例程的条件以及用于手动修改控制I / O循环的系统。

在这里使用@coroutine装饰器的唯一方法是允许get方法将SQL查询作为后台进程进行处理,并在查询完成后恢复,而不会阻止Tornado I / O循环处理其他传入数据源。 这就是该实现的所有“异步”信息:带外数据库查询。 显然,如果我们想展示异步Web应用程序的魔力和奇迹,则“待办事项列表”是不可能的。

但是,这就是我们要构建的,所以让我们看看我们的方法如何利用该@coroutine装饰器。 很好地混入BaseView声明中的SessionMixin向我们的视图类添加了两个方便的数据库感知属性: sessionmake_session 。 它们的名称相似,并且实现了相当相似的目标。

self.session属性是一个关注数据库的会话。 在请求-响应周期结束时,就在视图将响应发送回客户端之前,已提交对数据库所做的任何更改,并且会话已关闭。

self.make_session是上下文管理器和生成器,可动态构建和返回全新的会话对象。 第一个self.session对象仍然存在; make_session创建一个新的。 make_session生成器还已经将其上下文(即,缩进级别)一结束就提交和关闭其创建的会话的逻辑融入了自身。

如果检查源代码,则分配给self.session的对象类型和self.make_session生成的对象类型之间没有区别 。 不同之处在于它们的管理方式。

使用make_session上下文管理器,生成的会话仅属于该上下文,在该上下文内开始和结束。 您可以使用make_session上下文管理器在同一视图中打开,修改,提交和关闭多个数据库会话。

self.session更为简单,在您进入view方法并在将响应发送回客户端之前提交时,会话已经打开。

尽管阅读docs片段PyPI示例均指定了上下文管理器的使用,但是self.session对象或self.make_session所生成的session self.make_session上都是异步的。 我们开始考虑tornado-sqlalchemy内置的异步行为的时候到了。

tornado-sqlalchemy as_future软件包为我们提供了as_future函数。 as_future的工作是包装由tornado-sqlalchemy会话构造的查询并产生其返回值。 如果view方法使用@coroutine装饰,则现在使用此yield as_future(query)模式将使包装的查询成为异步后台进程。 I / O循环接管工作,等待查询的返回值和as_future创建的future对象的as_future

要访问as_future(query)的结果,必须从中yield 。 否则,您只会得到一个未解析的生成器对象,并且对查询无能为力。

此视图方法中的其他所有内容在课程中几乎都是一样的,反映了我们在Flask和Pyramid中已经看到的内容。

post方法看起来非常相似。 为了保持一致,让我们看看post方法的外观以及它如何处理使用BaseView构造的self.form_data


   
   
@ coroutine
def post ( self , username ) :
    """Create a new task."""
    with self . make_session ( ) as session:
        profile = yield as_future ( session. query ( Profile ) . filter ( Profile. username == username ) . first )
        if profile :
            due_date = self . form_data [ 'due_date' ] [ 0 ]
            task = Task (
                name = self . form_data [ 'name' ] [ 0 ] ,
                note = self . form_data [ 'note' ] [ 0 ] ,
                creation_date = datetime . now ( ) ,
                due_date = datetime . strptime ( due_date , '%d/%m/%Y %H:%M:%S' ) if due_date else None ,
                completed = self . form_data [ 'completed' ] [ 0 ] ,
                profile_id = profile . id ,
                profile = profile
            )
            session. add ( task )
            self . send_response ( { 'msg' : 'posted' } , status = 201 )

就像我说的,这是关于我们期望的:

  • get方法相同的查询模式
  • 使用来自form_data数据填充的新Task对象的实例的form_data
  • 将新Task对象添加到数据库会话中(但不会提交,因为它由上下文管理器处理!)
  • 将响应发送回客户端

因此,我们为Tornado Web应用程序奠定了基础。 其他所有内容(例如,数据库管理和更完整应用程序的更多视图)实际上与我们在Flask和Pyramid应用程序中已经看到的相同。

关于使用正确的工具完成正确工作的想法

随着我们继续浏览这些Web框架,我们开始看到的是它们都可以有效地解决相同的问题。 对于此类待办事项列表,任何框架都可以完成任务。 但是,某些Web框架比其他框架更适合某些工作,具体取决于“更适合”对您和您的需求的含义。

虽然龙卷风显然能够处理金字塔或Flask可以处理的相同工作,但将其用于这样的应用程序实际上是一种浪费。 就像驾车离家一个街区。 是的,它可以完成“旅行”的工作,但是短途旅行并不是您选择在脚踏车上骑车还是骑脚踏车的原因。

根据文档,Tornado被称为“ Python Web框架和异步网络库”。 Python Web框架生态系统中很少有人喜欢它。 如果您要完成的工作以任何方式,形状或形式要求(或将大大受益于)异步性,请使用Tornado。 如果您的应用程序需要处理多个长期连接而又不牺牲太多性能,请选择“ Tornado”。 如果您的应用程序包含多个应用程序,并且需要线程感知以准确处理数据,请访问Tornado。 那是最有效的地方。

用您的汽车来做“汽车用品”。 使用其他运输方式来做其他事情。

向前看一点透视

说到使用正确的工具完成正确的工作,在选择框架时,请记住应用程序的范围和规模,包括现在和将来。 到目前为止,我们仅研究了适用于中小型Web应用程序的框架。 本系列的下一部分也是最后一部分,将介绍最流行的Python框架之一Django,它适用于可能会变得更大的大型应用程序。 同样,尽管从技术上讲它可以并且将解决“待办事项列表”问题,但请记住,它并不是真正的框架。 我们仍然会按照进度来展示如何使用它来构建应用程序,但是我们必须牢记该框架的意图以及它在其体系结构中的体现:

  • Flask:意味着小型,简单的项目; 使我们轻松构造视图并将其快速连接到路线; 可以封装在一个文件中而不必大惊小怪
  • 金字塔:意味着可能会发展的项目; 包含一些启动和运行的配置; 可以轻松地将应用程序组件的不同领域划分并扩展到任意深度,而不会忽略中央应用程序
  • 龙卷风:对受益于精确和故意的I / O控制的项目的意图; 允许协同例程并轻松公开可以控制如何接收请求/发送响应以及何时进行这些操作的方法
  • Django :(我们将看到)意味着可能会变得更大的大事情; 大型的附件和模组生态系统; 在配置和管理方面非常有见地,以便使所有不同的部分保持一致

无论您是从本系列的第一篇文章开始阅读以来还是以后再阅读,感谢您的阅读! 请随时提出问题或评论。 下次我会再见的。

Python BDFL大声疾呼

我必须在信用到期的地方给予信用。 大规模的感谢欠吉多·范罗苏姆不仅仅是为了创造我喜欢的编程语言更多。

PyCascades 2018期间,我很幸运不仅发表了本系列文章的演讲, 还很荣幸被邀请参加演讲者的晚宴。 我整晚都坐在吉多旁边,向他提问。 这些问题之一是,异步在Python中如何工作,他花了点时间花了些时间向我解释,以使我可以开始理解这个概念。 后来他向我发了一条推文,向我介绍了一个学习Python异步的绝妙资源,后来我在三个月内阅读了三遍,然后写了这篇文章。 你真是个帅哥,吉多!

翻译自: https://opensource.com/article/18/6/tornado-framework

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值