Github上最受欢迎的Python框架Flask入门

flask最近终于发布了它的1.0版本更新,从项目开源到最近的1.0版本flask已经走过了8个年头。

# 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!

flask的诞生于2010年的愚人节,本来它只是作者无意间写的一个小玩具,没想到它却悄悄流行起来了。漫长的8年时间,flask一直没有发布一个严肃的正式版本,但是却不能阻挡它成了github上最受好评的Python Web框架。

flask内核内置了两个最重要的组件,所有其它的组件都是通过易扩展的插件系统集成进来的。这两个内置的组件分别是werkzeug和jinja2。

werkzeug是一个用于编写Python WSGI程序的工具包,它的结构设计和代码质量在开源社区广受褒扬,其源码被尊为Python技术领域最值得阅读的开源库之一。

# 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!

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! by Alex Martelli, the author of 《Python in a Nutshell》 && 《Python Cookbook》

jinja2是一个功能极为强大的模板系统,它完美支持unicode中文,每个模板都运行在安全的沙箱环境中,使用jinja2编写的模板代码非常优美。

{% 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的神奇魅力。

安装flask

pip install flask

圆周率计算API

圆周率可以使用正整数的平方倒数之和求得,当这个级数趋于无限时,值会越来越接近圆周率。

# 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,说明缓存结果不再因为进程重启而丢失。

MethodView

写过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来解决这个问题。

小试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
}

本文分享自微信公众号 - 码洞(codehole)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-06-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏python3

python .pyc是个什么啥?

我初学Python时,听到的关于Python的第一句话就是,Python是一门解释性语言,我就这样一直相信下去,直到发现了*.pyc文件的存在。如果是解释型语言...

16910
来自专栏python3

python 变量和作用域

python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

13630
来自专栏python3

python 文件操作2

Somehow, it seems the love I knew was always the most destructive kind

16510
来自专栏python3

python 元组

当程序的变量,不希望被修改的时候,就可以用到了。比如数据库连接信息,如果被修改了,就会造成程序异常。

13010
来自专栏IT探索

tensorflow安装及使用笔记

step2,通过pip安装tensorflow:pip install tensorflow=1.5

10130
来自专栏python3

python 三级菜单

用户选择菜单的时候,如果输入中文,打字比较麻烦,菜单列表需要有一个编号。由于字典没有编号,列表是有索引,这里可以用做编号。所以需要将zone.keys()的结果...

17720
来自专栏python3

python 列表(List)

Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。

21910
来自专栏python3

python 购物车程序

23320
来自专栏python3

python 入门知识拾遗

Python 3最重要的新特性大概要算是对文本和二进制数据作了更为清晰的区分。文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示。Pyt...

11340
来自专栏python3

python 字典(Dictionary)

字典一种key - value 的数据类型,使用就像我们上学用的字典,通过笔划、字母来查对应页的详细内容。

9010

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励