137. Flask

第一个flask程序

Flask(__name__)

1
2
3
4
5
# 初始化一个Flask对象
# Flask()
# 需要传递一个参数 __name__
# 1. 方便flask框架去寻找资源
# 2. 方便flask插件比如Flask-Sqlalchemy出现错误的时候,好去寻找问题所在的位置

@app.route()

1
2
3
4
5
6
7
# @app.route是一个装饰器(可以看成url的后缀:127.0.0.1:5000/login 的 /login)
# @开头,并且在函数的上面,说明是装饰器
# 这个装饰器的作用是做一个url和视图函数(习惯性的命名,就是这里的hello_world函数)的映射
# 127.0.0.1:5000 -> 去请求hello_world这个函数然后将结果返回给浏览器
@app.route('/')
def hello_world():
return “我是第一个flask程序”

app.run():

启动一个应用服务器,来接收用户的请求。

类似于:

1
2
while (True):
listen()

设置debug模式

  1. app.run() 中传入一个关键字参数debugapp.run(debug=True), 就设置当前项目为 debug 模式
  2. debug 模式的两大功能
    • 当程序出现问题时,可以在也米那种看到错误信息和出错的位置
    • 只要修改了项目中的 Python 文件,程序会自动加载,不需要手动重新启动服务器

使用配置文件

  1. 新建一个 .py文件,这里设置为 config.py

  2. 然后设置大写,如:DEBUG=True

  3. app进行关联

    1
    2
    3
    4
    # 假设配置文件为config
    import config

    app.config.from_object(config)
  4. 还有许多的其他参数,都是放在这个配置文件中,比如 SECRET_KEYSQLALCHEMY 这些配置都是在这个文件中

URL传参

  1. 参数的作用:可以在相同的url,但是指定不同的参数,来加载不同的数据

  2. 在flask中如何使用参数

    1
    2
    3
    @app.route("/article/<id>")
    def article(id):
    return "您请求的参数是:%s" %id
    • 参数需要放在两个尖括号中
    • 视图函数中需要放和url中的参数同名的参数

反转URL

正转

通过url可以取到视图函数的内容

反转

  1. 从视图函数到url的转换叫做反转url(通过视图函数的名称,可以得到指向的url
  2. 用处
    • 在页面重定向的时候,会使用url反转
    • 在模板中也会使用url反转

函数讲解

  • url_for():
    • params:视图函数名称, 视图函数对应的参数(如果视图函数有参数的话),如 url_for("article", id="123")
    • return: 视图函数对应的url

页面跳转和重定向

  1. 用处:在用户访问一些需要登录的页面的时候,如果用户没有登录,那么可以让他重定向到登录页面

  2. 实现

    1
    2
    3
    4
    5
    6
    7
    8
    from flask import Flask, redirect, url_for

    @app.route("/question/<isLogin>/")
    def question(isLogin):
    if isLogin == '1':
    return "这是发布问答页面"
    else:
    return redirect(url_for("login"))

函数讲解

  • redirect()
    • params: url,如 redirect("/login123/")或者 redirect(url_for("login")),建议使用第二种,只要视图函数名字不变, @app.route(/login123/)里面的 login123怎么变都没关系

模板渲染和参数

如何渲染模板

  1. 模板放在 templates 文件夹下

  2. flask 中导入 render_template 函数

  3. 在试图函数中,使用 render_template 函数渲染模板。

    注意:只需要填写模板的名字,不要填写 templates 这个文件夹的路径。如果在 templates 文件夹下创建了一个新的文件夹,那么就要添加上文件名(render_template() 函数的文件名是相对路径)

模板传参

  • 如果只有一个或者少量参数,直接在 render_template() 函数中添加关键字参数就可以了
  • 如果有多个参数的时候,那么可以先把所有的参数放在字典中,然后在 render_template 中,使用 **字典名 把字典转换为关键参数传递进去,这样的代码更方便管理和应用

函数讲解

  • render_template()
    • params: 渲染文件名(渲染文件放在template文件夹下), username = 用户名 (username 保证和渲染文件中的变量名保持一致), … 。如 render_template("index.html", username = "Qeuroal")
    • 如果参数特别多的话,可以先定义一个字典(context),然后把字典传入函数内,如 render_template("index.html", **contet)

模板访问属性和字典

在模板中如果要使用一个变量,语法是 {{ params }}

  • 访问类的属性:
    • 和在Python文件中一致
    • 或者类似于这样: Person["name"]
  • 访问字典:
    • py文件一样: dict[key]
    • dict.key

实现

template1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.route("/")
def index():
class Person():
name = "Qeuroal"
age = 24

p = Person()
testDict = {
"key": "key",
"value": "value"
}
context = {
"username": "Qeuroal",
"gender": "男",
"age": 24,
"person": p,
"testDict": testDict
}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是HTML文件中出现的文字
<p>用户名: {{ username }}</p>
<hr>
<p>用户名: {{ person.name }}</p>
<p>年龄: {{ person["age"] }}</p>
<hr>
<p>key: {{ testDict.key }}</p>
<p>value: {{ testDict['value'] }}</p>
</body>
</html>

HTML

if判断

语法

1
2
3
4
5
6
7
{% if xxx and xxx %}
xxxx
{% elif xxx and xxx%}
xxxx
{% else %}
xxxx
{% endif %}

if的使用和python中相差无几。

for循环遍历列表和字典

字典遍历

字典的遍历,语法和 python 一样,可以使用 items(), keys(), values(), iteritems(), iterkeys(), itervalues()

1
2
3
{% for k,v in user.items() %}
<p>{{ k }}: {{ v }}</p>
{% endfor%}

列表遍历

语法和 python 一样

1
2
3
{% for website in websites %}
<p>{{ website }}</p>
{% endfor %}

过滤器

作用对象是模板中的变量:

介绍和语法

  • 介绍:过滤器可以处理变量,把原始的遍历经过处理后再展示出来。作用的对象是变量

  • 语法:

    1
    {{ avator|default("xxx")}}

default过滤器

如果当前变量不存在,这时候可以指定默认值。

length过滤器

求列表,字符串,字典,元组的长度。类似于:python 中的 len()

常用的过滤器

  • abs(value)
  • default(value, defaultValue, boolean=false)
  • escape(value)
  • first(value)
  • format(value, *args, **kwargs)
  • last(value)
  • length(value)
  • join(value, d="")
  • safe(value)

继承和block

继承

  • 作用:可以把一些公共的代码放在父模板中,避免每个模板写同样的代码

  • 语法:

    1
    {% extends "base.html" %}
  • 如果想在子模板中实现自己的内容:需要定义接口,类似于 java 的接口:html子模板需要实现自己内容,必须要放在父模板定义的接口里面,即block,不能在接口外面写代码

block

  • 作用:可以让子模板实现一些自己的需求。父模板需要提前定义好。
  • 注意点:
    • 子模板中的代码,必须放在block块中;
    • 可以定义多个 block

URL链接

使用 url_for(视图函数名称) 可以反转成url。

加载静态文件

1
2
3
4
5
6
<style>
a {
background: red;
color: black;
}
</style>

样式代码一般不会写在模板中,因为:维护性差,一般样式文件抽取成 css 文件(static 文件夹中是存放静态文件的)

语法

url_for("static", filename="路径")

  • 如:url_for("static", filename="css/index.css")

  • 静态文件,flask会从 static 文件夹中开始寻找,所以不需要写 static 这个路径了

  • 可以加载 css文件, js 文件,image 文件。

示例代码

1
2
3
4
5
6
{# 加载css文件 #}
<link rel="stylesheet" href="{{ url_for("static", filename="css/index.css") }}">
{# 加载js文件 #}
<script src="{{ url_for("static", filename="js/index.js") }}"></script>
{# 加载image文件 #}
<img src="{{ url_for("static", filename="images/pdx.jpg") }}" alt="">

注意点

装饰器

  • 装饰器为 @app.route('/login/'),网址可以是:http://127.0.0.1:5000/loginhttp://127.0.0.1:5000/login/
  • 装饰器为 @app.route('/login'),网址只能是:http://127.0.0.1:5000/login/

MySQL

安装(win10)

  • 我选择的是 Server only

  • 设置密码:

  • 打开命令行,并输入上一步设置好的密码

  • 下载地址

  • 一般直接下一步

  • 设置初始密码命令:

    1
    mysqladmin -uroot password [password]
  • 如果没有安装 .net Framework 4,就在那个提示框中,找到下载的 url 下载;

  • 如果没有安装 Microsoft visual C++ x64,那么就需要谷歌或者百度下载这个软件进行安装即可。

MySQL-python中间件介绍与安装

linux, macos

  1. 进入虚拟环境
  2. 输入 pip install mysql-python

windows系统

  • windows 在这里下载,看 python 安装的是32位还是64位,对应下载
  • 进入虚拟环境: conda activate 虚拟环境名
  • 安装:pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl

Flask-SQLAlchemy的介绍与安装

  • ORM: Object Relationship Mapping (关系模型映射);

  • flask-sqlalchemy 是一套 ORM框架

  • 模型关系映射

    • delete(article1):把 article表中和article1属性对应相同的那条数据删除。
    • 好处:ORM使得我们操作数据库非常简单,就像操作对象是一样的,非常方便。因为一个表就抽象成了一个类,一条数据就抽象成了该类的一个对象。
  • 安装:pip install flask-sqlalhemy,如果是在 macos 或者 linux 中没有权限的话这样安装: sudo pip install flask-sqlalhemy

Flask-SQLAlchemy的使用

  1. 初始化和设置数据库配置信息:

    • 使用 flask_sqlalchemy 中的 SQLAlchemy 进行初始化

      1
      2
      3
      from flask_sqlalchemy import SQLAlchemy
      app = Flask(__name__)
      db = SQLAlchemy(app)
  2. 设置配置信息:在 config.py 文件中添加以下配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # dialect+driver://username:password@host:port/database
    DIALECT = "mysql"
    DRIVER = "mysqldb"
    USERNAME = "root"
    PASSWORD = "toor"
    HOST = "127.0.0.1"
    PORT = "3306"
    DATABASE = "dbDemo1"

    SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
    # 去除警告
    SQLALCHEMY_TRACK_MODIFICATIONS = False
  3. 添加配置文件

    1
    2
    3
    import config

    app.config.from_object(config)
  4. 测试是否连接成功

    1
    db.create_all()

    如果没有报错,说明配置没有问题,如果有错误,可以根据错误进行修改。

建表

  1. cmd 进入 mysqlmysql -uroot -p
  2. create database dbDemo1 charset utf8

SQLAlchemy创建模型与表的映射

步骤

  1. 模型需要继承自 db.Model,然后需要映射到表中的属性,必须写成 db.Column()的数据类型

  2. 数据类型:

    • db.Integer: 整型
    • db.Sting: varcharchar,需要指定最长的长度
    • db.Text: text
  3. 其他参数

    • primary_key: 将这个字段设置为主键
    • autoincrement: 这个主键为自增长
    • nullable: 这个字段是否可以为空,默认为空,可以将这个值设置为 False ,在数据库中,这个值就不能为空了
  4. 最后需要调用 db.create_all() 来将模型真正的创建到数据库中,db.create_all()只会映射一次。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

"""
创建article表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null
)
"""
# 自己写的模型一定要继承sqlalchemy属性下的model模型, 字段->属性
class Article(db.Model):
# 如果不指定表名,默认为类名
__tablename__ = "article"
# 属性id映射到字段id
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
# varchar, char -> db.String(长度)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)

# 所有的模型映射到数据库的一张表
db.create_all()


@app.route('/')
def index():
return "index"

if __name__ == '__main__':
app.run()

SQLAlchemy数据增删改查

1
2
3
4
5
# 增加
article1 = Article(title="aaa", content="bbb")
db.session.add(article1)
# 事务
db.session.commit()

1
2
3
4
5
6
# 返回的是Query(<==> list); .all()查找所有数据, .first()返回的第一条数据,如果没有,返回null
result = Article.query.filter(Article.title == "aaa").all()
print(result[0], type(result[0]))
article1 = result[0]
print(article1.title)
print(article1.content)

1
2
3
4
5
6
# 1. 先把要修改的地方查找出来
article1 = Article.query.filter(Article.title == "aaa").first()
# 2. 把这条数据,你需要修改的地方进行修改
article1.title = "new title"
# 3. 做事务的提交
db.session.commit()

1
2
3
4
5
6
# 1. 把需要删除的数据查找出来
article1 = Article.query.filter(Article.content == "bbb").first()
# 2. 把这条数据删除掉
db.session.delete(article1)
# 3. 做事务提交
db.session.commit()

注意

  • 如果把增删改查放在 @app.route("/") 类似的视图函数中,访问一次 url,就会执行一遍相应的增删改查

Flask-SQLAlchemy外键及其关系

外键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"""
用户表
sql语句:
create table users (
id int primary key autoincrement,
username varchar(100) not null
)
"""
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
# password = db.Column(db.String(100), nullable=False)

"""
文章表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null,
authorId int,
foreign key "authorId" references "user.id"
)
"""
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
# 设置外键
authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
# params: 模型名, backref:反向引用;db.backref(), params: 反向引用的名字,
author = db.relationship("User", backref=db.backref("articles"))

解释

  • authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
    • Article 这个模型添加一个 author 属性,可以访问这篇文章的作者的数据,像访问普通模型一样
    • backref:定义反向引用,可以通过 User.articles 这个模型访问这个模型缩写的所有文章

补充

  • 添加数据(方法二):

    1
    2
    3
    4
    article = Article(title="aaa", content="bbb")
    article.author = User.query.filter(User.id==1).first()
    db.session.add(article)
    db.session.commit()

多对多

例子

在这张图中:

使用

  • 多对多的关系,要通过一个中间表进行关联;

  • 中间表,不能通过 class 的方式实现,只能通过 db.Table 的方式实现

  • 设置关联:

    1
    tags = db.relationship("Tag", secondary=articleTagRelation, backref=db.backref("articles"))

    需要使用一个关键字参数 secondary=中间表 进行关联

  • 访问和添加可以通过以下方式操作:

    • 添加数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      article1 = Article(title="aaa")
      article2 = Article(title="bbb")
      tag1 = Tag(name="111")
      tag2 = Tag(name="222")

      article1.tags.append(tag1)
      article1.tags.append(tag2)

      article2.tags.append(tag1)
      article2.tags.append(tag2)

      db.session.add(article1)
      db.session.add(article2)
      db.session.add(tag1)
      db.session.add(tag2)
      db.session.commit()
    • 访问数据

      1
      2
      3
      4
      article1 = Article.query.filter(Article.title=="aaa").first()
      tags = article1.tags
      for tag in tags:
      print(tag.name)

Flask-Script的介绍和安装

介绍

Flask-Script 的作用是可以通过命令行的形式来操作 Flask。例如通过命令跑一个开发版本的服务器、设置数据库、定时任务。

安装

  1. 进入到虚拟环境中
  2. pip install flask-script进行安装

使用

  1. 如果直接在主 manage.py 中写命令,那么在终端就只需要 python manage.py commandName 就可以了。

  2. 如果把一些命令集中在一个文件中,那么在终端就需要输入一个父命令,如 python manage.py db init

  3. 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from flask_script import Manager
    from flaskScriptDemo import app
    from dbScript import dbManager

    manager = Manager(app)

    @manager.command
    def runserver():
    print("服务器跑起来了")

    # params: 前缀(类似于别名),dbManager
    # 使用:python manage.py db init
    manager.add_command("db", dbManager)

    if __name__ == '__main__':
    manager.run()
  4. 有子命令的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from flask_script import Manager

    dbManager = Manager()

    @dbManager.command
    def init():
    print("数据库初始化完成")

    @dbManager.command
    def migrate():
    print("数据表迁移成功")

分开 models 及解决循环引用

循环引用

解决办法:

db 放在一个单独的文件中,切断循环引用的线条就可以了。

分开 models

  • 目的:为了让代码更加方便的管理

注意:

  • 使用了 db.init_app(app),就不能直接使用 db.create_all()了,会报错。

    1
    2
    3
    app = Flask(__name__)
    app.config.from_object(modelSepConfig)
    db.init_app(app)

    因为涉及到了上下文的问题:

    解决办法看 flask-migrate

flask-migrate

db.init_app(app)不能直接使用 db.create_all() 解决办法

手动推动 appapp栈

介绍

因为采用 db.create_all() 在后期修改字段的时候,不会自动的映射到数据库中,必须删除表,然后重新运行 db.create_all() 才会重新映射,这样不符合我们的需求。因此 flask-migrate 就是为了解决这个问题,它可以在每次修改模型后,可将修改的东西映射到数据库中

安装

  1. 进入虚拟环境
  2. pip install flask-migrate

使用

使用 flask_migrate 必须借助 flask-scripts,这个包的 MigrateCommand 中包含了所有和数据库相关的命令。

相关命令

  • python manage.py db init: 初始化一个迁移脚本的环境,只需要执行一次;
  • python manage.py db migrate: 将模型生成迁移文件,只要模型更改了,就需要执行一遍这个命令;
  • python manage.py db upgrade: 将掐你文件真正映射到数据库中,每次运行了 migrate 命令后,就记得要运行这个命令

注意点

  • 如果需要将你想要映射到数据库中的模型,都要导入到 manage.py 文件中,如果没有导入进去,就不会映射到数据库中

manage.py 的相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask_script import Manager
from migrateDemo import app
from flask_migrate import Migrate, MigrateCommand
from exts import db
from models import Article

# 模型 => 迁移文件 => 表
# init: 初始化迁移的环境
# migrate: 将模型生成迁移文件
# upgrade: 将迁移文件映射成表

manager = Manager(app)

# 1. 要使用 flask_migrate,必须绑定app和db
migrate = Migrate(app, db)
# 2. 把MigrateCommand命令添加到manager中
manager.add_command("db", MigrateCommand)




if __name__ == '__main__':
manager.run()

cookie和session

  • 出现的原因:在网站中,http 请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不知道当前请求是哪个用户。cookie的出现就是我了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的 cookie 数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是那个了;

  • 图解:

  • 如果服务器返回了 cookie 给浏览器,那么浏览器下次再请求相同的服务器的时候,就会自动的把 cookie 发送给服务器,这个过程,用户根本不需要管;

  • cookie 是保存在浏览器中的,相对对的是浏览器;

session

  • 介绍:sessioncookie 的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie 是存储在本地服务器的,而 session 存储在服务器。存储在服务器的数据会更加安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些 session 信息还是绰绰有余的。

  • 图解:

  • 使用 session 的好处:

    • 敏感数据不是直接发送给服务器,而是发送回一个 session_id ,服务器将 session_id 和敏感数据做一个映射存储在 session (服务器上面)中,更加安全;
    • session 可以设置过期时间,也可以从另外一方面,保证用户的账号安全。

注意

  • session_id:返回给浏览器的时候,是放在 cookie 中的。

flask中session工作机制

  • 讲解:把敏感数据经过加密后放入 session 中,然后再把 session 存放到 cookie 中,下次请求的时候,再从浏览器发送过来的 cookie 中读取 session,然后再从 session 中读取敏感数据,并进行解密,获取最终的用户数据;

  • flask 的这种 session 机制,可以节省服务器的开销,以内把所有的信息都存储到了客户端(浏览器);

  • 安全是相对的,没有绝对的安全,把 session 放到 cookie 中,经过机密,也是比较安全的;

  • 图解:

flask操作session

session的操作方式:

  • 使用 session 需要从 flask 中导入 session,以后所有和 session 相关的操作都是通过这个变量来的;
  • 使用 session 需要设置 SECRET_KEY,用来作为加密用的。并且这个 SECRET_KEY 如果每次服务器启动后都变化的话,那么之前的 session 就不能通过当前这个 SECRET_KEY 进行解密了;
  • 操作 session ,跟操作字典是一样的;
  • 添加 sessionsession[key]=value
  • 删除:session.pop(key) 或者 del session[key]
  • 清除所有 sessionsession.clear()
  • 获取 sessionsession.get(key)

设置session的过期时间

  • 如果没有指定session的过期时间,那么默认是浏览器关闭后就自动结束

  • 如果设置了 sessionpermanent 属性为 True,那么过期时间是31天

  • 可以通过给 app.config 设置 PERMANENT_SESSION_LIFETIME 来更改过期时间,这个值的数据类型是 datatime.timedelay 类型:

    1
    2
    3
    from datetime import timedelta

    PERMANENT_SESSION_LIFETIME = timedelta(days=7)

get请求和post请求

get请求

  • 使用场景:如果只对服务器获取数据,并没有对服务器产生任何影响,那么这时候使用 get 请求
  • 传参:get请求传参是防砸url中,并且是通过 ?的形式来指定 keyvalue

post请求

  • 使用场景:如果要对服务器产生影响,那么使用post请求,如: 登录:服务器要记录用户什么时候登陆过,要把数据保存在本地;
  • 传参:post请求传参不是放在 url 中,而是通过 form data 的形式发送给服务器的

获取参数

  • get请求:通过 flask.request.args 来获取
  • post请求:通过 flask.request.form 来获取

注意

  • post请求在模板中要注意几点:

    • input标签中,要写name来标识这个 valuekey,方便后台获取

    • 在写 form 表单的时候,要指定 method='post',并且要指定 action='/login/'

    • 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <form action="{{ url_for("login") }}" method="post">
      <table>
      <tbody>
      <tr>
      <td>用户名</td>
      <td><input type="text" placeholder="请输入用户名" name="username"></td>
      </tr>

      <tr>
      <td>密码</td>
      <td><input type="text" placeholder="请输入密码" name="password"></td>
      </tr>

      <tr>
      <td></td>
      <td><input type="submit" placeholder="登录"></td>
      </tr>

      </tbody>
      </table>
      </form>

保存全局遍历的g属性

g: global

  • g对象 是专门用来保存用户的数据的
  • g对象 在一次请求中的所有的代码的地方,都是可以使用的

钩子函数(hook)

钩子函数

正常执行情况:先执行A,然后再执行B。

钩子函数:可以在运行时,把C直接插入到AB之间

before_request

  • before_request:在请求之前执行的函数,且每次请求都会执行一遍
  • before_request是在视图函数执行之前执行的
  • before_request这个函数只是一个装饰器,他可以把需要设置为钩子函数的代码放到视图函数执行之前来执行

context_processor(上下文处理器)

  • 出现的原因:多个不同的页面需要相同的参数,如:username
  • 上下文处理器应该返回一个字典,字典中的 key 会被模板当成变量来渲染
  • 上下文处理器中返回的字典,在所有页面中都是可用的。
  • 被这个装饰器修饰的钩子函数,必须要返回一个字典,即使为空,也要返回

装饰器

  • 需求1:在打印run之前,先要打印hello world

    1
    2
    3
    4
    5
    def run():
    print("hello world")
    print("run")

    run()
  • 需求2:在所有函数执行之前,都要打印一个 hello world

    • 方法一:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      def run():
      print("hello world")
      print("run")

      def run1():
      print("hello world")
      print("run1")

      run()
    • 方法二(如果成千上万个函数,比较麻烦,且难以维护):装饰器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      # 装饰器的两个特别之处:
      # 1. 参数是一个函数
      # 2. 返回值是一个函数
      def myLog(func):

      def wrapper():
      print("hello world")
      func()

      """
      补充:
      wrapper: 函数体
      wrapper(): 执行wrapper这个函数
      """
      return wrapper

      # 如何用
      @myLog
      def run():
      print("run")
      """
      等价于上面
      run = myLog(run) <==> wrapper
      run() <==> wrapper() <==> print("hello world"); func() <==> print("hello world"); print("run")
      理解:func() 执行的才是真的 run() 方法
      """
      run()

讲解

  • 装饰器的两个特别之处:
    • 参数是一个函数
    • 返回值是一个函数
  • 上面的 myLog() 是无参情况下的装饰器

进阶:有参情况下的装饰器

需求:在所有函数执行之前,都要打印一个 hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 装饰器的两个特别之处:
# 1. 参数是一个函数
# 2. 返回值是一个函数
def myLog(func):

def wrapper(a, b):
print("hello world")
func(a, b)
return wrapper

@myLog
def add(a, b):
print("结果是", a + b)
"""
等价于上面
add = myLog(add) <==> wrapper
add(1, 2) <==> wrapper(1, 2) # 因此,如果wrapper()无参,就会报错 `TypeError: wrapper() takes 0 positional arguments but 2 were given`
"""
add(1, 2)

再进阶:有无参同时存在情况下的装饰器

  • 解决办法: *args, **kwargs:可以表示任何参数

    • *args: 位置参数,如:add(a, b) 中的ab,参数是一一对应的
    • **kwargs:关键字参数(key=value),如:add(a=abc)a就是传入的abc
    1
    2
    3
    4
    5
    6
    def myLog(func):

    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper

    run(), add() 同上

再进阶

  • 问题:加完装饰器后,函数名被偷偷更改掉了,因此如何保留住原来的函数的名字(把名字改掉是很危险的行为)?

    • 没有装饰器的情况

      1
      2
      3
      def run():
      print("run")
      print(run.__name__) # run.__name__代表的是run这个函数的名称

      输出:

      run

    • 有装饰器的情况

      1
      2
      3
      4
      @myLog
      def run():
      print("run")
      print(run.__name__)

      输出:

      wrapper

  • 解决办法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper
    • 没加 @wraps 的情况
      run() <=> myLog(run)() <=> wrapper()

    • 加了 @wraps(func) 的情况

      • run <=> myLog(run) <=> @wraps(func)装饰的wrapper <=> wraps(wrapper) <=> wrapsFunction(这个函数是透明的,即看不到的,但是不管怎么样,返回的函数的__name__是run) => wrapsFunction.__name__ == "run"
      • ``run() <=> myLog(run)() <=> @wraps装饰的wrapper() <=> wraps(wrapper)() <=> wrapsFunction()`

再进阶

  • 需求:如果run()有返回值,该怎么办?

  • 解决办法:在 wrapper()return func(*args, **kwargs)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    return func(*args, **kwargs)
    return wrapper

    @myLog
    def run():
    return 1+1
  • 讲解:

    1
    2
    3
    4
    5
    run = myLog(run) = wrapper
    ==>(推出,下同) run = wrapper
    ==> run() = wrapper() = print("hello world"); return run() = print("hello world"); return 1+1
    因为run()需要返回1+1,所以wrapper()也需要返回1+1
    如果wrapper没有return,那么func(*args, **kwargs),就只是执行了一下1+1,别的什么都没有了

小结

  • 装饰器的使用是通过 @ 符号,放在函数上面;
  • 装饰器中定义的函数,要使用 *args**kwargs 两对兄弟的组合,并且在这个函数中执行原始函数的时候也要把 *args**kwargs 传进去;
  • 需要使用 functools.wraps 在装饰器中的函数上把传进来的这个函数进行一个包裹,这样就不会丢失原来的函数 __name__ 等属性

实践