Flask

Flask

Flask是一个基于Werkzeug和Jinja2的微框架。微框架意味着Flask的核心非常简单。Flask于2010年出现,这使得它吸收了其他框架的优点,并且把自己的主要领域定义在了微小项目上(Flask是一个面向简单需求和小型应用的微框架),同时具有很强的扩展功能。不像Django,它不会为开发者做太多技术决定,Flask可以自由的选择任何模板引擎或ORM。即使它带有默认的Jinja2模板引擎,开发者仍然可以选择自己喜欢的其他引擎。

  • 特点
  1. 内置开发服务器和调试器
  2. 与Python单元测试功能无缝衔接
  3. 使用Jinja2模板
  4. 完全兼容WSGI1.0标准
  5. 基于Unicode编码
  • 环境准备

这里我建立了虚环境来完成所有基于Flask的项目。

  1. 安装虚环境
    pip install virtualenv
  2. 使用虚环境
    cd [使用该虚环境的目录]
    virtualenv venv

该条命令执行后,将在这个目录中建立一个venv目录,该目录复制了一份完整的当前系统的Python环境(注意:如果系统中安装了多个Python版本,可以在该命令中使用-p参数指定从哪个版本的Python复制虚拟环境)。
Flask_第1张图片

  1. 在当前虚环境下安装Flask相关组件
    进入/venv/Scripts/环境下> pip install flask
    进入/venv/Scripts/环境下> pip install sqlalchemy flask-wtf
  • SQLAlchemy和WTForm分别为Flask应用提供数据库访问及表单封装功能。其中flask-wtf是对WTForm实现的一个简单封装包,在它的安装过程中会自动安装WTForm。
  • 组件被安装在venv/lib目录中,不会影响系统的Python环境。
    Flask_第2张图片
  1. 其他虚环境下相关命令示例
  • 用虚环境运行Python程序
    进入/venv/Scripts/环境下> python xxxx.py
  • 用activate命令启动虚环境(之后便不必再显式的调用虚环境bin文件夹中的命令)
    进入/venv/Scripts/环境下> activate
  • 用deactivate命令退出虚环境
    deactivate
  1. Sublime Text使用Python虚环境里的包运行Python程序
    参考这里。此处不再赘述。

实战演练:开发Flask站点

1. hello_flask程序

创建hello_flask.py程序:

'''
1. 导包
'''
from flask import Flask

'''
2. 建立一个Flask类的实例app
	2.1 应用模块或包的名字传给Flask构造函数的第一个参数,
		Flask在运行过程中将使用这个参数作为定位模板和其他静态文件的基础。
	2.2	__name__是Python语言的一个内置属性,它包含的内容指明了.py文件的调用方式。
		本例中将__name__传递给Flask,这样可以在用两种方式调用本模块时都能使代码工作:
			a. 直接运行本模块时,__name__的值为__main__;
			b. 通过其他模块调用本模块时,__name__的值为本模块的名称。
'''
app = Flask(__name__)

'''
3. 使用route()装饰器告诉Flask紧跟着函数装载在哪个URL地址中。
	本例中,HTTP的根地址被装载为hello_flask()函数。
	如果需要把函数装载在其他URL地址中,则只需要修改route()参数,
	如app.route('/say/hello')即可达到目标。
'''
'''
4. 在装载函数中直接返回'Hello, Flask!'内容传给HTTP客户端,
	除可以为字符串外,返回的内容还可以是HTML、XML、JSON消息体。
'''
@app.route('/')
def hello_flask():
	return 'Hello, Flask!'

'''
5. if __name__ == '__main__'语句用于指示:
	当本模块被直接启动时才运行其作用域中的代码。
	这里作用域中的app.run()进入Flask消息循环体,
	app.run()将一直运行,不要在其后面再写任何代码。
'''
if __name__ == '__main__':
	app.run()

运行效果如下:
Flask_第3张图片
现在已经完成了一个Flask程序,该程序挂载在127.0.0.1的5000端口上。它的功能是当访问http://127.0.0.1:5000/时,程序会返回“Hello, Flask!”:
Flask_第4张图片

为什么程序只运行在本地地址127.0.0.1上呢?这是Flask出于安全考虑而执行的默认行为。因为Flask启动时默认是处于调试模式的,所以在运行中如果发生错误会直接返回给客户端,让陌生人知道这些错误信息会威胁到网站的安全性,所以Flask以默认的启动方式监听本地的127.0.0.1地址,不允许外部客户端访问。但是,可以通过run()方法提供来自外部的访问:在run()参数中可以传入要监听的端口,并且设置是否处于调试模式运行。例如:
app.run(host='0.0.0.0', port=80, debug=False)
功能:系统将会监听所有地址的80端口并关闭调试模式。

2. 模板渲染

Flask使用Jinja模板引擎实现了自动模板渲染功能。Flask的Jinja2模板特性通过加载HTML模板文件,并在其中嵌入必要的参数和逻辑来达到简化HTML输出的目的。

2.1 用render_template实现模板渲染

把要加载的模板文件和参数传给Flask包的render_template方法,即可实现HTML的自动渲染。向render_template传入的参数,不仅可以是单纯的字符串,还可以包含HTML特殊字符(比如<>、空格、/等),这些特殊字符会被HTML客户端解释成特殊含义(这会给网站程序带来一定程度而安全隐患,解决方式即为接下来要讲解的Markup方法)。

from flask import render_template

@app.route('/hello')
@app.route('/hello/')
def hello(name=None):
	# render_template加载hello.html模板文件,并把name参数传给该模板
	return render_template('hello.html', name=name)

2.2 用Markup转换变量中的特殊字符

如果希望传入HTML的特殊字符,但又不想要其被解释成特殊含义,而仅仅是作为字符串,则应该通过Markup()函数将这些字符串做转义处理,然后传给render_template函数。

3. 重定向和错误处理

redirect():将一个网络请求重新指定URL并转到其他地址。
abort():终止一个请求并返回错误(不重定向到其他地址)。

路由详解

在上面第1节的学习中,我们将根URL路由绑定在一个输出‘Hello Flask!’字符串的函数中。接下来将学习如何配置更复杂的带参数的路由。

1. 带变量的路由

如果要使URL中包含可变的部分,则可以通过在需要的URL部分添加变量来实现,这个变量会作为参数传递给路由所关联的Python函数。

1.1 在路径中添加变量

@app.route('/login/')
def hello(name=username):
	return 'Hello %s' % username

1.2 为变量指定类型

@app.route('/add/')
def add_one(num):
	return '%d' % (num+1)

1.3 路径最后的分隔符的作用

在URL路径中,“/”被用作路径分隔符。当它被写在URL路径的开头时,则表明本路径是一个绝对路径;当它被写在路径中间时,它被用作隔离路径的层级;当它被写在路径最后时,它既接收对其本身的访问,也可以接受相同路径前缀但不带“/”结尾的路径访问(如果一个路径最后没有写“/”,则它只能接受对其本身的访问,而不能接受相同路径前缀但带“/”结尾的路径访问。也就是说,带“/”结尾的路径的接受程度更高)。

声明方式举例 访问方式举例 能否访问到
@app.route(’/school/’) http://localhost/school/ 可以
http://localhost/school 可以
@app.route(’/student’) http://localhost/student/ 不可以
http://localhost/student 可以

2. HTTP方法绑定

网站通过HTTP与浏览器或其他客户端进行交互,而HTTP访问一个URL时可以使用不同的访问方式,如GET、POST、HEAD、DELETE等。在Flask中,路由默认设置使用GET方式进行路径访问。

2.1 指定HTTP访问方式的方法

@app.route('/SendMessage', methods=['GET', 'POST'])
def Messaging():
	if request.method == 'POST':
		do_send()
	else:
		show_the_send_form()

'''
request是Flask框架的一个全局对象,可以获得很多和HTTP请求的客户端相关的信息。request.method属性获得本次HTTP请求的访问方式。
'''

2.2 将同一个URL根据访问方式映射到不同的函数

@app.route('/Message', methods=['POST'])
def do_send():
	return "This is for POST methods"
	
@app.route('/Message', methods=['GET'])
def show_the_send_form():
	return "This is for GET methods"

注意:由于HTTP的不同访问方式之间存在着关联,所以Flask定义了两组隐式的访问方式的设置规则——

  • 当在route装饰器中GET方式被指定时,HEAD方式也会被自动加入该装饰器中。
  • 在Flask 6.0 之后,当在route装饰器中有任意方式被指定时,OPTIONS访问方式也会被自动加入该装饰器中。

3. 路由地址反向生成

前面讲解的都是将URL绑定到映射函数的方法,下面,我们通过函数名称获得与其绑定的URL地址。Flask通过url_for()函数实现了这个功能:

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/')
def f_root():
	pass

@app.route('/industry')
def f_industry():
	pass

@app.route('/book/')
def f_book(book_name):
	pass

with app.test_request_context():
	print(url_for('f_root'))
	print(url_for('f_industry'))
	print(url_for('f_industry', name='web'))
	print(url_for('f_book', book_name='Python Book'))

'''
讲解1.
	url_for(需要获取URL的函数名, [对URL中的变量赋值])
讲解2. 
	test_request_context()方法用于告诉解释器在其作用域中的代码模拟一个HTTP请求上下文,
	使其好像被一个HTTP请求所调用。
	HTTP请求上下文是调用url_for()函数所必须的环境,下一节将进行学习。
'''

运行效果:
Flask_第5张图片
在程序中使用url_for()函数的原因如下:

  • 反向解析比硬编码有更好的可读性和可维护性。比如,当需要更换路由函数中URL的地址时,无需再更改和调用url_for()函数的代码。
  • url_for()函数会自动处理必须的特殊字符转换和Unicode编码转换。本节代码段中最后一个print中book_name的空格就自动被解析为%20

使用Context上下文

上下文(Context)是Web编程中的一个相当重要的概念,它是在服务器端获得应用及请求相关信息的对象。

1. 会话上下文

  • 会话(Session)是一种客户端与服务器端保持状态的解决方案。在服务器的程序端,就是用会话上下文(Session Context)这一存储结构来实现这种解决方案。
  • 每个不同的用户连接将得到不同的会话,即会话与用户之间是一对一的关系。同一用户的多个请求共享同一个会话。
  • 会话在用户进入网站时由服务器自动产生,并在用户离开站点时自动释放。
  • 会话通常基于Cookie实现。

基于Cookie对象的会话的实现原理:

  1. 在服务器收到客户端的请求时,检查客户端是否设置了表示SessionID的Cookie。如果不存在SessionID或者SessionID无效,则认为该请求是一个新的会话。
  2. 当服务器识别到新的会话时,生成一个新的SessionID并通过Cookie传送给客户端。
  • 在Flask框架中,开发者无需对上述原理进行编程,因为Flask会自动处理这些细节。开发者只需在需要时向会话保存或读取信息即可。通过flask.session对象操作会话的实例:
from flask import Flask, session
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'SET_ME_BEFORE_USE_SESSION'

'''
将当前时间写入会话的键值key_time中
'''
@app.route('/write_session')
def writeSession():
	session['key_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
	return session['key_time']

'''
读取上次调用writeSession()时写入的时间,并返回
'''
@app.route('/read_session')
def readSession():
	return session.get('key_time') or 'None'

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

Flask_第6张图片
Flask_第7张图片

2. 应用全局对象

  • 在Flask中,每个请求可能会触发多个响应函数,而如果想要在多个响应函数之间共享数据,则需要用到应用全局对象(Application Global),它提供了在一次请求的多个处理函数中共享信息的方式。
  • 应用全局对象是Flask为每一个请求自动建立的一个对象。相对于简单的全局对象,应用全局对象可以保证线程安全。
  • 在一般的网站中,通常需要下面的逻辑来实现数据库的连接:
  1. 在请求中第一次需要使用数据库时,建立数据库对象并进行连接;
  2. 在同一个请求后的任何需要用到数据库的操作中,直接使用前面已经建立好的数据库连接进行数据库操作;
  3. 当请求完成时,框架自动关闭数据库连接,有效的使用系统资源。
    以上逻辑通过flask.g进行实现(这是一个管理应用全局对象的实例):
from flask import g

class MyDB():
	def __init__(self):
		print('A db connection is created...')

	def close(self):
		print('A db is closed...')

def connect_to_db():
	return MyDB()

def get_db():
	# 从flask.g中获得数据库连接对象
	db = getattr(g, '_database', None)
	# 若没有从flask.g中获得数据库连接对象,则建立一个新的数据库连接,并将其保存在flask.g中
	if db is None:
		db = connect_to_db()
		g._database = db
	return db
	
'''
应用装饰器@app.teardown_request,
从而使得teardown_db()函数在请求结束时自动被Flask框架调用。
'''
@app.teardown_request
def teardown_db(response):
	db = getattr(g, '_database', None)
	if db is not None:
		db.close()

可以在请求处理函数的任何地方调用get_db()。下面的代码实例分别在验证登录信息和查询数据处调用了get_db()

from flask import Flask

app = Flask(__name__)

def login():
	db = get_db()
	session['has_login'] = True

@app.route('/view_list')
def view_list():
	if 'has_login' not in session:
		login()
	db = get_db()
	return "teardown_db() will be called automatically"

3. 请求上下文

  • 请求上下文(Request Context)主要是在服务器端获得从客户端提交的数据。这些数据包括:URL参数、Form表单数据、Cookie、HTML头信息、URL等。

3.1 访问URL参数和路径

  • 与URL相关的数据包括URL参数URL路径
  • Flask在获取URL参数和URL路径的实例如下:
from flask import request, url_for, redirect

app = Flask(__name__)

@app.route('/redirect_url')
def redirect_url():
	next = request.args.get('next') or url_for('index')
	return redirect(next)

@app.route('/echo_url')
def echo_url():
	return request.base_url

代码分析:在访问http://127.0.0.1:5000/readirect_url?next=http://127.0.0.1:5000/echo_url时(URL参数为next=http://127.0.0.1:5000/echo_url,根路径为http://127.0.0.1:5000/readirect_url),readirect_url()函数被调用,它通过request.args属性获取URL参数next,然后通过redirect重定向到next中的地址。在echo_url()中,函数通过request.base_url属性获得本次访问的路径并返回。

3.2 其他客户端数据的访问方法

除访问URL数据外,访问其他客户端数据的方式与上面的类似,不再一一演示。下面仅介绍用于访问客户端数据的属性:

  • args(上面使用过的)
  • from
  • values
  • cookies
  • headers
  • data
  • files
  • method
  • URL族属性(包括上面使用的base_url,此外还有path、url、url_root、script_root,它们的区别见P285)
  • is_xhr
  • json
  • on_json_loading_failed(e)

4. 回调接入点

  • 在Web编程中,有些处理逻辑需要对所有请求都进行相同的处理,倘若将这些代码写入每个URL映射处理函数中显然会使得程序十分臃肿,于是Flask提供了回调接入点,相当于把这些代码写入了公共逻辑中。这些接入点如下:
  1. before_request()
  2. after_request()
  3. teardown_request()
  • 实例:
from cachelib import SimpleCache # 引入缓存类SimpleCache
from flask import Flask, request, render_template 

app = Flask(__name__)

CACHE_TIMEOUT = 300
# 实例化一个缓存类,并设置其timeout属性为300秒
cache = SimpleCache()
cache.timeout = CACHE_TIMEOUT

'''
- 为return_cached()设置装饰器before_request,将其指定为一个在每个请求被处理之前调用的函数。
- return_cached()的内部逻辑为:
	如果客户端未提交任何参数,则在缓存中检查该页面是否已经存在,
	如果存在,则中断该次请求的调用链,直接将缓存结果返回给客户端。
'''
@app.before_request
def return_cached():
	if not request.values:
		response = cache.get(request.path)
		if response:
			print("Got the page from cache.")
			return response
	print("Will load the page.")

'''
- 为cache_response()设置装饰器after_request,将其指定为一个在每个请求被处理之后调用的函数。
- cache_response()的内部逻辑为:
	如果客户端未提交任何参数,则认为该次返回结果具有典型性,将其缓存到对象中以备后续访问。
'''
@app.after_request
def cache_response(response):
	if not request.values:
		cache.set(request.path, response, CACHE_TIMEOUT)
	return response

'''
- 用route装饰器定义URL的具体处理函数index()
'''
@app.route("/get_index")
def index():
	return render_template('index.html')

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

'''
整体流程:
	客户端在第一次访问页面"/get_index"时,页面内容被缓存在cache对象中;
	之后的客户端访问该页面时,Flask直接从cache对象中获取页面内容并直接返回。
'''

Jinja2模板编程

Jinja2是Python Web编程中主流的模板语言,从Django模板发展而来,比Django模板的性能更好。

1. Jinja2语法

Jinja2模板可以保存在任何基于文本的文件中,比如XML、HTML、CSV等,所以模板文件本身可以接受任何文件后缀,比如.html、.xml等。

2. 使用过滤器

3. 流程控制

4. 模板继承

SQLAlchemy数据库编程

1. SQLAlchemy入门

2. 主流数据库的连接方式

3. 查询条件设置

4. 关系操作

5. 级联

WTForm表单编程

1. 定义表单

2. 显示表单

3. 获取表单数据

你可能感兴趣的:(#,2.,Python框架)