用python求阶乘的和知乎_知乎后端主力框架Tornado入门体验

Tornado在知乎广为使用,当你用Chrome打开网页版本的知乎,使用开发者工具仔细观察Network里面的请求,就会发现有一个特别的状态码为101的请求,它是用浏览器的websocket技术和后端服务器建立了长连接用来接收服务器主动推送过来的通知消息。这里的后端服务器使用的就是tornado服务器。Tornado服务器除了可以提供websocket服务外,还可以提供长连接服务,HTTP短链接服务,UDP服务等。Tornado服务器由facebook开源,在掌阅的后端也广为使用。

这样一个强大的Tornado框架,究竟该如何使用,本文将带领读者循序渐进深入学习tornado作为web服务器的基础使用。

Hello, World

import tornado.ioloop

import tornado.web

class MainHandler(tornado.web.RequestHandler):

def get(self):

self.write("Hello, world")

def make_app():

return tornado.web.Application([

(r"/", MainHandler),

])

if __name__ == "__main__":

app = make_app()

app.listen(8888)

tornado.ioloop.IOLoop.current().start()

这是官方提供了Hello, World实例,执行python hello.py,打开浏览器访问http://localhost:8888/就可以看到服务器的正常输出Hello, world。

一个普通的tornado web服务器通常由四大组件组成。

ioloop实例,它是全局的tornado事件循环,是服务器的引擎核心,示例中tornado.ioloop.IOLoop.current()就是默认的tornado ioloop实例。

app实例,它代表着一个完成的后端app,它会挂接一个服务端套接字端口对外提供服务。一个ioloop实例里面可以有多个app实例,示例中只有1个,实际上可以允许多个,不过一般几乎不会使用多个。

handler类,它代表着业务逻辑,我们进行服务端开发时就是编写一堆一堆的handler用来服务客户端请求。

路由表,它将指定的url规则和handler挂接起来,形成一个路由映射表。当请求到来时,根据请求的访问url查询路由映射表来找到相应的业务handler。

这四大组件的关系是,一个ioloop包含多个app(管理多个服务端口),一个app包含一个路由表,一个路由表包含多个handler。ioloop是服务的引擎核心,它是发动机,负责接收和响应客户端请求,负责驱动业务handler的运行,负责服务器内部定时任务的执行。

当一个请求到来时,ioloop读取这个请求解包成一个http请求对象,找到该套接字上对应app的路由表,通过请求对象的url查询路由表中挂接的handler,然后执行handler。handler方法执行后一般会返回一个对象,ioloop负责将对象包装成http响应对象序列化发送给客户端。

同一个ioloop实例运行在一个单线程环境下。

阶乘服务

下面我们编写一个正常的web服务器,它将提供阶乘服务。也就是帮我们计算n!的值。服务器会提供阶乘的缓存,已经计算过的就存起来,下次就不用重新计算了。使用Python的好处就是,我们不用当心阶乘的计算结果会溢出,Python的整数可以无限大。

# fact.py

import tornado.ioloop

import tornado.web

class FactorialService(object): # 定义一个阶乘服务对象

def __init__(self):

self.cache = {} # 用字典记录已经计算过的阶乘

def calc(self, n):

if n in self.cache: # 如果有直接返回

return self.cache[n]

s = 1

for i in range(1, n):

s *= i

self.cache[n] = s # 缓存起来

return s

class FactorialHandler(tornado.web.RequestHandler):

service = FactorialService() # new出阶乘服务对象

def get(self):

n = int(self.get_argument("n")) # 获取url的参数值

self.write(str(self.service.calc(n))) # 使用阶乘服务

def make_app():

return tornado.web.Application([

(r"/fact", FactorialHandler), # 注册路由

])

if __name__ == "__main__":

app = make_app()

app.listen(8888)

tornado.ioloop.IOLoop.current().start()

执行python fact.py ,打开浏览器,键入http://localhost:8888/fact?n=50,可以看到浏览器输出了

608281864034267560872252163321295376887552831379210240000000000,如果我们不提供n参数,访问http://localhost:8888/fact,可以看到浏览器输出了400: Bad Request,告诉你请求错误,也就是参数少了一个。

使用Redis

上面的例子是将缓存存在本地内存中,如果换一个端口再其一个阶乘服务,通过这个新端口去访问的话,对于每个n,它都需要重新计算一遍,因为本地内存是无法跨进程跨机器共享的。

所以这个例子,我们将使用Redis来缓存计算结果,这样就可以完全避免重复计算。另外我们将不在返回纯文本,而是返回一个json,同时在响应里增加字段来说名本次计算来源于缓存还是事实计算出来的。另外我们提供默认参数,如果客户端没有提供n,那就默认n=1。

import json

import redis

import tornado.ioloop

import tornado.web

class FactorialService(object):

def __init__(self):

self.cache = redis.StrictRedis("localhost", 6379) # 缓存换成redis了

self.key = "factorials"

def calc(self, n):

s = self.cache.hget(self.key, str(n)) # 用hash结构保存计算结果

if s:

return int(s), True

s = 1

for i in range(1, n):

s *= i

self.cache.hset(self.key, str(n), str(s)) # 保存结果

return s, False

class FactorialHandler(tornado.web.RequestHandler):

service = FactorialService()

def get(self):

n = int(self.get_argument("n") or 1) # 参数默认值

fact, cached = self.service.calc(n)

result = {

"n": n,

"fact": fact,

"cached": cached

}

self.set_header("Content-Type", "application/json; charset=UTF-8")

self.write(json.dumps(result))

def make_app():

return tornado.web.Application([

(r"/fact", FactorialHandler),

])

if __name__ == "__main__":

app = make_app()

app.listen(8888)

tornado.ioloop.IOLoop.current().start()

当我们再次访问http://localhost:8888/fact?n=50,可以看到浏览器输出如下

{"cached": false, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50}

,再刷新一下,浏览器输出{"cached": true, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50},可以看到cached字段由true编程了false,表明缓存确实已经保存了计算的结果。我们重启一下进程,

再次访问这个连接,观察浏览器输出,可以发现结果的cached依旧等于true。说明缓存结果不再是存在本地内存中了。

圆周率计算服务

接下来我们再增加一个服务,计算圆周率,圆周率的计算公式有很多种,我们用它最简单的。

我们在服务里提供一个参数n,作为圆周率的精度指标,n越大,圆周率计算越准确,同样我们也将计算结果缓存到Redis服务器中,避免重复计算。

# pi.py

import json

import math

import redis

import tornado.ioloop

import tornado.web

class FactorialService(object):

def __init__(self, cache):

self.cache = cache

self.key = "factorials"

def calc(self, n):

s = self.cache.hget(self.key, str(n))

if s:

return int(s), True

s = 1

for i in range(1, n):

s *= i

self.cache.hset(self.key, str(n), str(s))

return s, False

class PiService(object):

def __init__(self, cache):

self.cache = cache

self.key = "pis"

def calc(self, n):

s = self.cache.hget(self.key, str(n))

if s:

return float(s), True

s = 0.0

for i in range(n):

s += 1.0/(2*i+1)/(2*i+1)

s = math.sqrt(s*8)

self.cache.hset(self.key, str(n), str(s))

return s, False

class FactorialHandler(tornado.web.RequestHandler):

def initialize(self, factorial):

self.factorial = factorial

def get(self):

n = int(self.get_argument("n") or 1)

fact, cached = self.factorial.calc(n)

result = {

"n": n,

"fact": fact,

"cached": cached

}

self.set_header("Content-Type", "application/json; charset=UTF-8")

self.write(json.dumps(result))

class PiHandler(tornado.web.RequestHandler):

def initialize(self, pi):

self.pi = pi

def get(self):

n = int(self.get_argument("n") or 1)

pi, cached = self.pi.calc(n)

result = {

"n": n,

"pi": pi,

"cached": cached

}

self.set_header("Content-Type", "application/json; charset=UTF-8")

self.write(json.dumps(result))

def make_app():

cache = redis.StrictRedis("localhost", 6379)

factorial = FactorialService(cache)

pi = PiService(cache)

return tornado.web.Application([

(r"/fact", FactorialHandler, {"factorial": factorial}),

(r"/pi", PiHandler, {"pi": pi}),

])

if __name__ == "__main__":

app = make_app()

app.listen(8888)

tornado.ioloop.IOLoop.current().start()

因为两个Handler都需要用到redis,所以我们将redis单独抽出来,通过参数传递进去。另外Handler可以通过initialize函数传递参数,在注册路由的时候提供一个字典就可以传递任意参数了,字典的key要和参数名称对应。我们运行python pi.py,打开浏览器访问http://localhost:8888/pi?n=200,可以看到浏览器输出{"cached": false, "pi": 3.1412743276, "n": 1000},这个值已经非常接近圆周率了。

阅读更多Python高级文章,关注公众号「码洞」

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值