admin管理员组

文章数量:1530266

Python Flask框架

一、简介


Flask是一个基于Python的Web开发框架,它以灵活、微框架著称,Flask本身不具备太多功能,但是通过丰富的第三方插件,可以轻松地应对现实开发中复杂的需求。

学习Flask框架,我们可以开发一个服务器,与前端进行交互

Flask有3个主要依赖:路由、调试和Web服务器网关接口(WSGI,Web server gateway interface)子系统由Werkzeug提供;模板系统由Jinja2提供;命令行集成由Click提供。这些依赖全都是Flask的开发者Armin Ronacher开发的。

二、学习资源推荐

Flask文档

  • 英文文档:https://flask.palletsprojects/en/2.3.x/
  • 中文文档:https://dormousehole.readthedocs.io/en/latest/
  • Flask Web开发入门:https://www.bookstack/read/head-first-flask/chapter02-README.md

三、环境搭建

1.python环境

读者可以先查看下计算机中现有的Python版本,按照系统来分,可以用以下方式查看:

  • Windows系统:按Win+R快捷键,输入cmd,按Enter键,在打开的命令行终端输入python,即可看到现有的Python版本,如图所示。


从图可以看到Windows系统安装的Python版本是3.10.2,读者可以查看下自己的计算机上安装的Python版本,建议最好安装python3.6以上的版本。

可以到官网:https://www.python/下载最新版本的Python,下载后直接安装即可。

  • Mac系统:打开终端,输入python3,然后按Enter键即可查看

2.安装Flask库

安装Flask 非常简单,只要在系统的终端软件中输入以下命令,然后按Enter键即可安装。

    $ pip install flask

3.开发软件


许多软件都可以用来开发Flask项目,如Sublime Text、Visual Studio Code等,但是最专业的软件还是PyCharm。PyCharm是一个集成开发环境(integrated development environment,简称IDE),它提供了许多方便快捷的功能,如断点调试、版本控制等

PyCharm是JetBrains公司出品的一款专门针对Python编程的软件,它有两大版本:一个是PyCharm Professional,即专业版;另一个是PyCharm Community,即社区版,这两大版本的主要区别如下。

  • PyCharm Professional:功能最全,适合开发任何类型的Python程序,包括做一些前端项目开发,但是需要收费。
  • PyCharm Community:适合开发爬虫、数据分析、GUI等纯Python程序。对Python Web(如Flask和Django等)开发不够友好,没有足够的代码提示。好处是开源免费。

我们需要开发Flask项目,所以选择PyCharm Professional版本。关于它的收费问题,如果读者是学生,可以用学校提供的教育邮箱账号(一般以edu结尾)去申请免费授权(申请网址: https://www.jetbrains/community/education/#students)。

如果读者是企业开发者,可以跟公司申请购买正版授权。如果您既不是学生又不想购买正版PyCharm Professional版本,则可以退而求其次选择PyCharm Community版本,也完全可以学习本书的内容,只是一些代码提示没有那么智能(PyCharm Professional有30天试用期)。

四、应用的基本结构

1.初始化

所有Flask应用都必须创建一个应用实例。Web服务器使用一种名为Web服务器网关接口(WSGI,Web server gateway interface,读作“wiz-ghee”)的协议,把接收自客户端的所有请求都转交给这个对象处理。应用实例是Flask类的对象

	from flask import Flask
	app = Flask(__name__)

Flask类的构造函数只有一个必须指定的参数,即应用主模块或包的名称。在大多数应用中,Python的__name__变量就是所需的值。

Flask用_name_参数确定应用的位置,进而找到应用中其他文件的位置,例如图像和模板

2.路由与视图函数

客户端(例如Web浏览器)把请求发送给Web服务器,Web服务器再把请求发送给Flask应用实例。应用实例需要知道对每个URL的请求要运行哪些代码,所以保存了一个URL到Python函数的映射关系。处理URL和函数之间关系的程序称为路由

在Flask应用中定义路由的最简便方式,是使用应用实例提供的app.route装饰器。下面的例子说明了如何使用这个装饰器声明路由:

	@app.route('/')
	def index():
	    return 'Hello World!'

装饰器是Python语言的标准特性。惯常用法是把函数注册为事件处理程序,在特定事件发生时调用。

index()这样处理入站请求的函数称为视图函数。如果应用部署在域名为www.example的服务器上,在浏览器中访问http://www.example后,会触发服务器执行index()函数。这个函数的返回值称为响应,是客户端接收到的内容。如果客户端是Web浏览器,响应就是显示给用户查看的文档。视图函数返回的响应可以是包含HTML的简单字符串。

3.一个完整的应用

	from flask import Flask
	app = Flask(__name__)
	
	@app.route('/')
	def index():
	    return '<h1>Hello World!</h1>'

4.定义URL

绝大部分网站都不可能只有首页一个页面,以一个最简单的博客网站为例,博客页面相关的有博客列表、博客详情等,用户页面相关的有注册、登录、个人中心等。

所以在制作网站时,需要定义许多不同的URL来满足不同页面的访问需求,而URL总体上来讲又分为两种,第一种是无参数的URL,第二种是有参数的URL,下面分别进行讲解这两种URL的定义。

定义无参数的URL

无参数URL是指在URL定义的过程中,不需要定义参数。这里以个人中心为例,如定义个人中心的URL为/profile,可以使用以下代码实现。

	@app.route('/profile')
	def profile():
		return '这是个人中心'

定义有参数的URL

有些时候,在访问某个URL时需要携带一些参数。如获取博客详情时,需要把博客的id传过去,那么博客详情的URL可能为/blog/13,其中13为博客的id。假如获取第10页的博客列表,那么博客列表的URL可能为/blog/list/10,其中10为页码。

在Flask中,如果URL中携带了参数,那么视图函数也必须定义相应的形参来接收URL中的参数。这里以博客详情的URL为例,示例代码如下:

    @app.route("/blog/<blog_id>")
    def blog_detail(blog_id):
       return "您查找的博客id为:"%blog_id

通过以上代码可以看到,URL中多了一对尖括号,并且尖括号中多了一个blog_id,这个blog_id就是参数。然后在视图函数blog_detail中,也相应定义了一个blog_id的形参,当浏览器访问这个URL时,Flask接收到请求后,会自动解析URL中的参数blog_id,把它传给视图函数blog_detail,在视图函数中,开发者就可以根据blog_id从数据库中查找到具体的博客数据返回给浏览器。

URL中的参数类型

这里着重讲解参数类型any的使用。例如,现在要实现一个获取某个分类的博客列表,但是博客分类只能是python、flask、django之一,用any就可以轻松实现。

    @app.route("/blog/list/<any(python,flask,django):category>")
    def blog_list_with_category(category):
        return "您获取的博客分类为:%s"%category

在浏览器中访问/blog/list/python,因为博客分类python被包含在了备选值中,所以可以正常显示内容

5.请求和响应循环


开发了一个简单的Flask应用之后,你或许希望进一步了解Flask的工作方式。下面将介绍这个框架的一些设计理念。

应用和请求上下文

Flask从客户端收到请求时,要让视图函数能访问一些对象,这样才能处理请求。请求对象就是一个很好的例子,它封装了客户端发送的HTTP请求

要想让视图函数能够访问请求对象,一种直截了当的方式是将其作为参数传入视图函数,不过这会导致应用中的每个视图函数都多出一个参数。除了访问请求对象,如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。

为了避免大量可有可无的参数把视图函数弄得一团糟,Flask使用上下文临时把某些对象变为全局可访问。有了上下文,便可以像下面这样编写视图函数:

	from flask import request
	
	@app.route('/')
	def index():
	    user_agent = request.headers.get('User-Agent')
	    return '<p>Your browser is {}</p>'.format(user_agent)

注意,在这个视图函数中我们把request当作全局变量使用。事实上,request不可能是全局变量。试想,在多线程服务器中,多个线程同时处理不同客户端发送的不同请求时,每个线程看到的request对象必然不同。Flask使用上下文让特定的变量在一个线程中全局可访问,与此同时却不会干扰其他线程。

线程是可单独管理的最小指令集。进程经常使用多个活动线程,有时还会共享内存或文件句柄等资源。多线程Web服务器会创建一个线程池,再从线程池中选择一个线程处理接收到的请求。

在Flask中有两种上下文:应用上下文和请求上下文。

Flask上下文全局变量

Flask在分派请求之前激活(或推送)应用和请求上下文,请求处理完成后再将其删除。应用上下文被推送后,就可以在当前线程中使用current_app和g变量。类似地,请求上下文被推送后,就可以使用request和session变量。如果使用这些变量时没有激活应用上下文或请求上下文,就会导致错误

下述Python shell会话演示了应用上下文的使用方法:

	>>> from hello import app
	>>> from flask import current_app
	>>> current_app.name
	Traceback (most recent call last):
	...
	RuntimeError: working outside of application context
	>>> app_ctx = app.app_context()
	>>> app_ctx.push()
	>>> current_app.name
	'hello'
	>>> app_ctx.pop()

在这个例子中,没激活应用上下文之前就调用current_app.name会导致错误,但推送完上下文之后就可以调用了。注意,获取应用上下文的方法是在应用实例上调用app.app_context()。

请求对象

Flask通过上下文变量request对外开放请求对象。这个对象非常有用,包含客户端发送的HTTP请求的全部信息。Flask请求对象中最常用的属性和方法见表

Flask请求对象

请求钩子

有时在处理请求之前或之后执行代码会很有用。例如,在请求开始时,我们可能需要创建数据库连接或者验证发起请求的用户身份。为了避免在每个视图函数中都重复编写代码,Flask提供了注册通用函数的功能,注册的函数可在请求被分派到视图函数之前或之后调用

请求钩子通过装饰器实现。Flask支持以下4种钩子。

  1. before_request

注册一个函数,在每次请求之前运行,实例代码如下:

    @app.before_first_request
    def first_request():
        print ('first time request')
  1. before_first_request

注册一个函数,只在处理第一个请求之前运行。可以通过这个钩子添加服务器初始化任务。实例代码如下:

    @app.before_request
    def before_request():
        if not hasattr(g,'user'):
            setattr(g,'user','xxxx')
  1. after_request

注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。

  1. teardown_request

注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行。实例代码如下:

    @app.teardown_appcontext
    def teardown(exc=None):
        if exc is None:
            db.session.commit()
        else:
            db.session.rollback()

在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量g。例如,before_request处理程序可以从数据库中加载已登录用户,并将其保存到g.user中。随后调用视图函数时,便可以通过g.user获取用户。

request对象

在Flask项目中,如果要获取客户端提交上来的数据,可以通过全局线程安全对象flask.request实现。flask.request对象封装了许多属性和方法,常用的属性和方法如表

常用的属性和方法

flask.request对象完整的属性和方法,请参考Flask官方文档:https://flask.palletsprojects/en/2.0.x/api/#incoming-request-data。

页面重定向

页面重定向,下文简称重定向。重定向在页面中体现的操作是,浏览器会从一个页面自动跳转到另外一个页面。例如,用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此重定向到登录页面。重定向分为永久性重定向和暂时性重定向。

  • 永久性重定向:HTTP的状态码是301,多用于旧网址已被废弃,要转到一个新的网址,确保用户正常的访问。
  • 暂时性重定向:HTTP的状态码是302,表示页面的暂时性跳转。如访问一个需要权限的网址,但是当前用户没有登录,这时候就应该重定向到登录页面,并且是暂时性的重定向。

在Flask中,重定向是通过flask.redirect(location,code=302)函数来实现的,其中location表示需要重定向到哪个URL,code代表状态码,默认是302,即暂时性重定向。

下面用一个简单的案例来说明这个函数的用法。

    from flask import Flask,url_for,redirect
    app = Flask(__name__)
    @app.route('/login')
    def login():
        return 'login page'
    @app.route('/profile')
    def profile():
        name = request.args.get('name')
        if not name:
            # 如果没有name,说明没有登录,重定向到登录页面
            return redirect("/login")
        else:
            return name

从以上代码可看出,在访问/profile时,如果没有通过查询字符串的方式传递name参数,那么就会被重定向到/login。如访问/profile?name=admin可以看到,浏览器中显示admin,但是如果直接访问/profile,就会被重定向到/login。读者可自行尝试。

6.Jinja2模板

前面介绍的视图函数返回的都是一个字符串,而在实际网站开发中,为了让网页更加美观,需要渲染一个有富文本标签的页面,通常包含大量的HTML代码,如果把这些HTML代码用字符串的形式写在视图函数中,后期的代码维护将变成一场噩梦。

因此,在Flask中,渲染HTML通常会交给模板引擎来实现,而Flask中默认配套的模板引擎是Jinja2,Jinja2是一个高效、可扩展的模板引擎。Jinja2可以独立于Flask使用,如被Django使用。

渲染模板

首先在templates文件夹下创建index.html文件,然后输入以下代码。

在使用PyCharm Professional版创建完一个Flask项目后,默认会生成一个templates文件夹,如果没有修改模板查找路径,默认会在这个文件夹下寻找模板文件。模板文件可以是任意纯文本格式的文件,如TXT、HTML、XML等,但是为了让项目更规范,也为了与前端开发者更无缝地协作,一般都是用HTML文件来写模板代码。

注意:如果读者用的是非PyCharm Professional版创建的Flask项目,则可以手动创建templates文件夹。

<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>首页</title>
    </head>
    <body>
    <h1>这是首页</h1>
    </body>
    </html>

如果学过HTML的话,想必读者对这段代码就不陌生了,没错,这就是HTML的基本语法。如果读者不会HTML语法也不要慌张,这里主要只是介绍模板的基本用法,读者可以直接复制粘贴代码先用起来。后期作者也会更新相关的教程。

接下来在视图函数中使用render_template函数渲染index.html模板。在app.py中,将原来的hello_world视图函数修改为以下代码。

    from flask import Flask,render_template
    ...
    @app.route('/')
    def index():
        return render_template("index.html")

render_template默认会从当前项目的templates文件夹下寻找index.html文件,读取后进行解析,再渲染成HTML代码返回给浏览器。

在浏览器中访问http://127.0.0.1:5000,可以看到如图所示的效果。


从图中可以看到,“这是首页”4个字已经是一级标题了,原因是模板中给“这是首页”4个字外面套了一个h1标签,至此我们就完成了一个最简单的模板渲染。

渲染变量

HTML文件中的有些数据是需要动态地从数据库中加载的,不能直接在HTML中写死。一般的做法是,在视图函数中把数据先提取好,然后使用render_template渲染模板时传给模板,模板再读取并渲染出来。

在模板中使用的{{ name }}结构表示一个变量,这是一种特殊的占位符,告诉模板引擎这个位置的值从渲染模板时使用的数据中获取。

示例代码如下:

    @app.route("/variable")
    def variable():
        hobby = "游戏"
        return render_template("variable.html",hobby=hobby)

以上代码中渲染了一个variable.html模板,这个模板文件的创建接下来会具体讲解。除模板名称外,还给render_template传递了一个hobby关键字参数,后续在模板中就可以使用这个变量了。

现在再在templates文件夹下创建一个variable.html模板文件(注意:要记得先删掉template_folder参数),然后输入以下代码。

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>变量使用</title>
    </head>
    <body>
      <h1>我的兴趣爱好是:{{ hobby }}</h1>
    </body>
    </html>

从以上代码中可以看到,把变量放到两对花括号中即可使用变量。“游戏”从视图函数中通过render_template传过去的,并不是在HTML中写死的,所以变量的使用可以让同一个HTML模板渲染无数个不同的页面。

过滤器和测试器

在Python中,如果需要对某个变量进行处理,可以通过函数来实现,而在模板中,则是通过过滤器来实现的。过滤器本质上也是函数,但是在模板中使用的方式是通过管道符号(|)来调用的。

例如,有一个字符串类型的变量name,要获取它的长度,可以通过{{ name|length }}来获取,Jinja2会把name当作第1个参数传给length过滤器底层对应的函数。

length是Jinja2内置好的过滤器,Jinja2中内置了许多好用的过滤器,如果内置的过滤器不能满足需求,还可以自定义过滤器。

Jinja2中内置过滤器如下。

  1. abs(value):获取value的绝对值。
  2. default(value,default_value,boolean=False):如果value没有定义,则返回第2个参数default_value。如果要让value在被判断为False的情况下使用default_value,则应该将后面的boolean参数设置为False。先看以下示例。
    <div>default过滤器:{{ user|default('admin') }}</div>

如果user没有定义,那么将会使用admin作为默认的值。再看以下示例。

    <div>default过滤器:{{ ""|default('admin',boolean=True) }}</div>

因为" "(空字符串)在使用if判断时,返回的是False,这时如果要使用默认值admin,就必须加上boolean=True参数。

  1. escape(value):将一些特殊字符,如&、<、>、"、'进行转义。因为Jinja2默认开启了全局转义,所以在大部分情况下无须手动使用这个过滤器去转义,只有在关闭了转义的情况下,会需要使用到它。

  2. filesizeformat(value,binary=False):将值格式化成方便阅读的单位。如13KB、4.1MB、102Bytes等。默认是Mega、Giga,也就是每个相邻单位换算是1000倍。如果第2个参数设置为True,那么相邻单位换算是1024倍。

  3. first(value):返回value序列的第1个元素。

  4. float(value,default=0.0):将value转换为浮点类型,如果转换失败会返回0.0。(7)format(value,*args,**kwargs):格式化字符串,示例代码如下。

    {{ "%s,%s"|format(greeting,name) }}

内置过滤器还有很多,读者可自行去官方文档上查找,上文有链接

控制结构

Jinja2提供了多种控制结构,可用来改变模板的渲染流程。本节通过简单的例子介绍其中最有用的一些控制结构。

下面这个例子展示如何在模板中使用条件判断语句:

	{% if user %}
	    Hello, {{ user }}!
	{% else %}
	    Hello, Stranger!
	{% endif %}

另一种常见需求是在模板中渲染一组元素。下例展示了如何使用for循环实现这一需求:

	<ul>
	    {% for comment in comments %}
	        <li>{{ comment }}</li>
	    {% endfor %}
	</ul>

Jinja2还支持宏。宏类似于Python代码中的函数。例如:

	{% macro render_comment(comment) %}
	    <li>{{ comment }}</li>
	{% endmacro %}
	
	<ul>
	    {% for comment in comments %}
	        {{ render_comment(comment) }}
	    {% endfor %}
	</ul>

为了重复使用宏,可以把宏保存在单独的文件中,然后在需要使用的模板中导入:

	{% import 'macros.html' as macros %}
	<ul>
	    {% for comment in comments %}
	        {{ macros.render_comment(comment) }}
	    {% endfor %}
	</ul>

需要在多处重复使用的模板代码片段可以写入单独的文件,再引入所有模板中,以避免重复:

{% include 'common.html' %}

另一种重复使用代码的强大方式是模板继承,这类似于Python代码中的类继承。首先,创建一个名为base.html的基模板:

	<html>
	<head>
	    {% block head %}
	    <title>{% block title %}{% endblock %} - My Application</title>
	    {% endblock %}
	</head>
	<body>
	    {% block body %}
	    {% endblock %}
	</body>
	</html>

基模板中定义的区块可在衍生模板中覆盖。Jinja2使用block和endblock指令在基模板中定义内容区块。在本例中,我们定义了名为head、title和body的区块。注意,title包含在head中。下面这个示例是基模板的衍生模板:

	{% extends "base.html" %}
	{% block title %}Index{% endblock %}
	{% block head %}
	    {{ super() }}
	    <style>
	    </style>
	{% endblock %}
	{% block body %}
	<h1>Hello, World!</h1>
	{% endblock %}

extends指令声明这个模板衍生自base.html。在extends指令之后,基模板中的3个区块被重新定义,模板引擎会将其插入适当的位置。如果基模板和衍生模板中的同名区块中都有内容,衍生模板中的内容将显示出来。在衍生模板的区块里可以调用super(),引用基模板中同名区块里的内容。上例中的head区块就是这么做的。

五、总结

到此,如果你认真读完了本篇文章,并进行了简单的实现,那么恭喜你,你已经是一名合格的Flask框架的使用者了。

如果你还想继续深入研究的话,那么请持续关注博主,博主后期会持续更新Flask框架与表单的结合,Flask框架与数据库的结合。以及通过实践项目来巩固掌握的知识点。

如果你对前端感兴趣的话,博主后期也会更新HTML,CSS,JavaScript,以及Jquery,bootstrap等前端框架相关内容。

本文标签: 框架后端入门基础Python