# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
复制代码
运行python app.py
,打开浏览器访问http://localhost:5000/
就能够看到页面输出了Hello World!
html
flask的诞生于2010年的愚人节,原本它只是做者无心间写的一个小玩具,没想到它却悄悄流行起来了。漫长的8年时间,flask一直没有发布一个严肃的正式版本,可是却不能阻挡它成了github上最受好评的Python Web框架。python
flask内核内置了两个最重要的组件,全部其它的组件都是经过易扩展的插件系统集成进来的。这两个内置的组件分别是werkzeug和jinja2。git
werkzeug是一个用于编写Python WSGI程序的工具包,它的结构设计和代码质量在开源社区广受褒扬,其源码被尊为Python技术领域最值得阅读的开源库之一。github
# wsgi.py
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
复制代码
运行python wsgi.py
打开浏览器访问http://localhost:4000/
就能够看到页面输出了Hello World!
redis
Have you looked at werkzeug.routing? It's hard to find anything that's simpler, more self-contained, or purer-WSGI than Werkzeug, in general — I'm quite a fan of it!shell
by Alex Martelli, the author of 《Python in a Nutshell》 && 《Python Cookbook》json
jinja2是一个功能极为强大的模板系统,它完美支持unicode中文,每一个模板都运行在安全的沙箱环境中,使用jinja2编写的模板代码很是优美。flask
{% extends "layout.html" %} {% block body %} <ul> {% for user in users %} <li><a href="{{ user.url }}">{{ user.username }}</a></li> {% endfor %} </ul> {% endblock %} 复制代码
werkzeug和jinja2这两个库的共同特色是编写的代码赏心悦目,做者Armin Ronacher
选择这两个库来做为flask的基石说明做者有很是挑剔的代码品味。那么做者是谁呢,铛!他是一位来自澳大利亚的帅哥!浏览器
好,闲话少说言归正传,接下来咱们开始体验flask的神奇魅力。缓存
pip install flask
圆周率可使用正整数的平方倒数之和求得,当这个级数趋于无限时,值会愈来愈接近圆周率。
# flask_pi.py
import math
from flask import Flask, request
app = Flask(__name__)
@app.route("/pi")
def pi():
# 默认参数
n = int(request.args.get('n', '100'))
s = 0.0
for i in range(1, n):
s += 1.0/i/i
return str(math.sqrt(6*s))
if __name__ == '__main__':
app.run()
复制代码
运行python flask_pi.py
,打开浏览器访问http://localhost:5000/pi?n=1000000
,能够看到页面输出3.14159169866
,这个值同圆周率已经很是接近。
注意pi()
的返回值不能是浮点数,因此必须使用str
转换成字符串
再仔细观察代码,你还会注意到一个特殊的变量request
,它看起来彷佛是一个全局变量。从全局变量里拿当前请求参数,这很是奇怪。若是在多线程环境中,该如何保证每一个线程拿到的都是当前线程正在处理的请求参数呢?因此它不能是全局变量,它是线程局部变量,线程局部变量外表上和全局变量没有差异,可是在访问线程局部变量时,每一个线程获得的都是当前线程内部共享的对象。
为了不重复计算,咱们将已经计算的pi(n)
值缓存起来,下次就能够直接查询。同时咱们再也不只返回一个单纯的字符串,咱们返回一个json串,里面有一个字段cached用来标识当前的结果是否从缓存中直接获取的。
import math
import threading
from flask import Flask, request
from flask.json import jsonify
app = Flask(__name__)
class PiCache(object):
def __init__(self):
self.pis = {}
self.lock = threading.RLock()
def set(self, n, pi):
with self.lock:
self.pis[n] = pi
def get(self, n):
with self.lock:
return self.pis.get(n)
cache = PiCache()
@app.route("/pi")
def pi():
n = int(request.args.get('n', '100'))
result = cache.get(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
cache.set(n, result)
return jsonify({"cached": False, "result": result})
if __name__ == '__main__':
app.run()
复制代码
运行python flask_pi.py
,打开浏览器访问http://localhost:5000/pi?n=1000000
,能够看到页面输出
{
"cached": false,
"result": 3.141591698659554
}
复制代码
再次刷新页面,咱们能够观察到cached字段变成了true,说明结果确实已经缓存了
{
"cached": true,
"result": 3.141591698659554
}
复制代码
读者也许会问,为何缓存类PiCache须要使用RLock呢?这是由于考虑到多线程环境下Python的字典读写不是彻底线程安全的,须要使用锁来保护一下数据结构。
上面的缓存仅仅是内存缓存,进程重启后,缓存结果消失,下次计算又得从新开始。
if __name__ == '__main__':
app.run('127.0.0.1', 5001)
复制代码
若是开启第二个端口5001来提供服务,那这第二个进程也没法享受第一个进程的内存缓存,而必须从新计算。因此这里要引入分布式缓存Redis来共享计算缓存,避免跨进程重复计算,避免重启从新计算。
import math
import redis
from flask import Flask, request
from flask.json import jsonify
app = Flask(__name__)
class PiCache(object):
def __init__(self, client):
self.client = client
def set(self, n, result):
self.client.hset("pis", str(n), str(result))
def get(self, n):
result = self.client.hget("pis", str(n))
if not result:
return
return float(result)
client = redis.StrictRedis()
cache = PiCache(client)
@app.route("/pi")
def pi():
n = int(request.args.get('n', '100'))
result = cache.get(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
cache.set(n, result)
return jsonify({"cached": False, "result": result})
if __name__ == '__main__':
app.run('127.0.0.1', 5000)
复制代码
运行python flask_pi.py
,打开浏览器访问http://localhost:5000/pi?n=1000000
,能够看到页面输出
{
"cached": false,
"result": 3.141591698659554
}
复制代码
再次刷新页面,咱们能够观察到cached字段变成了true,说明结果确实已经缓存了
{
"cached": true,
"result": 3.141591698659554
}
复制代码
重启进程,再次刷新页面,能够看书页面输出的cached字段依然是true,说明缓存结果再也不由于进程重启而丢失。
写过Django的朋友们可能会问,Flask是否支持类形式的API编写方式,回答是确定的。下面咱们使用Flask原生支持的MethodView来改写一下上面的服务。
import math
import redis
from flask import Flask, request
from flask.json import jsonify
from flask.views import MethodView
app = Flask(__name__)
class PiCache(object):
def __init__(self, client):
self.client = client
def set(self, n, result):
self.client.hset("pis", str(n), str(result))
def get(self, n):
result = self.client.hget("pis", str(n))
if not result:
return
return float(result)
client = redis.StrictRedis()
cache = PiCache(client)
class PiAPI(MethodView):
def __init__(self, cache):
self.cache = cache
def get(self, n):
result = self.cache.get(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
self.cache.set(n, result)
return jsonify({"cached": False, "result": result})
# as_view提供了参数能够直接注入到MethodView的构造器中
# 咱们再也不使用request.args,而是将参数直接放进URL里面,这就是RESTFUL风格的URL
app.add_url_rule('/pi/<int:n>', view_func=PiAPI.as_view('pi', cache))
if __name__ == '__main__':
app.run('127.0.0.1', 5000)
复制代码
咱们实现了MethodView的get方法,说明该API仅支持HTTP请求的GET方法。若是要支持POST、PUT和DELETE方法,须要用户本身再去实现这些方法。
flask默认的MethodView挺好用,可是也不够好用,它没法在一个类里提供多个不一样URL名称的API服务。因此接下来咱们引入flask的扩展flask-classy来解决这个问题。
使用扩展的第一步是安装扩展pip install flask-classy
,而后咱们在同一个类里再加一个新的API服务,计算斐波那契级数。
import math
import redis
from flask import Flask
from flask.json import jsonify
from flask_classy import FlaskView, route # 扩展
app = Flask(__name__)
# pi的cache和fib的cache要分开
class PiCache(object):
def __init__(self, client):
self.client = client
def set_fib(self, n, result):
self.client.hset("fibs", str(n), str(result))
def get_fib(self, n):
result = self.client.hget("fibs", str(n))
if not result:
return
return int(result)
def set_pi(self, n, result):
self.client.hset("pis", str(n), str(result))
def get_pi(self, n):
result = self.client.hget("pis", str(n))
if not result:
return
return float(result)
client = redis.StrictRedis()
cache = PiCache(client)
class MathAPI(FlaskView):
@route("/pi/<int:n>")
def pi(self, n):
result = cache.get_pi(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
cache.set_pi(n, result)
return jsonify({"cached": False, "result": result})
@route("/fib/<int:n>")
def fib(self, n):
result, cached = self.get_fib(n)
return jsonify({"cached": cached, "result": result})
def get_fib(self, n): # 递归,n不能过大,不然会堆栈过深溢出stackoverflow
if n == 0:
return 0, True
if n == 1:
return 1, True
result = cache.get_fib(n)
if result:
return result, True
result = self.get_fib(n-1)[0] + self.get_fib(n-2)[0]
cache.set_fib(n, result)
return result, False
MathAPI.register(app, route_base='/') # 注册到app
if __name__ == '__main__':
app.run('127.0.0.1', 5000)
复制代码
访问http://localhost:5000/fib/100
,咱们能够看到页面输出了
{
"cached": false,
"result": 354224848179261915075
}
复制代码
访问http://localhost:5000/pi/10000000
,计算量比较大,因此多转了一回,最终页面输出了
{
"cached": false,
"result": 3.141592558095893
}
复制代码
阅读更多高级文章,关注微信订阅号「码洞」