Flask-论坛开发-1-基础知识
对Flask感兴趣的,可以看下这个视频教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002
1. 第一个 flask 程序
# 从 flask 框架中导入 flask 类
from flask import Flask
# 用 flask() 初始化一个 flask 对象,并赋给 app
# 需传递一个参数 __name__
# 1. 方便 flask 框架去寻找资源
# 2. 方便 flask 插件去定位问题
app = Flask(__name__)
# @app.route() 是一个装饰器,作用是对 url 与 视图函数进行映射
# 将 `/` 映射到 hello_world() 函数上
# 即用户访问 http://example:80/ 的时候,用 hello_world() 函数来响应
@app.route('/')
def hello_world():
return 'Hello World!'
# 如果当前文件作为程序入口,那么就执行 app.run()
if __name__ == '__main__':
# app.run() 是启动一个应用服务器来响应用户请求并不断监听
# app.run() 是 FLask 中的一个测试应用服务器(性能差,在部署的时候应该使用 uwsgi 而不是 app.run)
# app.run() 在底层实际上是一个 while(True) 循环,循环体是 listen() 函数,不断地监听指定端口号
app.run(debug=True,port=8000)
2. 使用 Debug 模式
使用 Debug 模式有很多好处:
1. 将报错信息显示到浏览器上,而不需要进入编辑器中查看报错信息,方便开发者查看错误
2. 当检测到程序代码(.py文件)发生改动,程序会自动加载而不需要手动重启服务器
对 flask 程序使用 debug 模式有 4 种方式,如下:
2.1 在 app.run() 中使用
在 app.run()
中直接传入一个关键字参数:app.run(debug=True)
。
2.2 在主 app 文件中使用 app.debug=True
from flask import Flask
app = Flask(__name__)
app.debug = True # 在主 app 文件中使用
…… 略 ……
2.3 在主 app 文件中使用 app.config.update(DEBUG=True)
from flask import Flask
app = Flask(__name__)
app.config.update(DEBUG=True) # 在主 app 文件中使用
…… 略 ……
使用 app.config.update(DEBUG=True)
这种方法实际上是借助了 Python 中的字典(Dict)属性,即:
a = {'a':1}
b = {'b':2}
a.update(b)
print(a) # {'a':1,'b':2}
a.update(c=3)
print(a) # {'a':1,'b':2,'c':3}
可以使用 print(isinstance(app.config,dict))
来判断 app.config
是不是一个字典类型的数据。但是要注意 DEBUG
一定要大写。
以上这三种开启 Debug 模式的方法比较简单,还有一种开启 Debug 模式的方法需要借助配置文件,借助配置文件开启 Debug 模式的方法也比较规范,如下。
2.4 在配置文件中使用
-
在相同目录下,新建一个
python
文件,建议命名为config
,并在里面指定该程序配置了DEBUG
模式,即config.py
文件的内容如下:config.py # encoding:utf-8 DEBUG = True # SECRET_KEY # SQLALCHEMY_DB # 数据库的一些参数配置
-
然后在主 app 文件中导入这个文件并配置到 app 中,主 app 文件内容如下:
First_Flask.py # encoding:utf-8 from flask import Flask import config # 导入 config 配置文件 app = Flask(__name__) app.config.from_object(config) # 将该配置文件的配置信息应用到 app 中 @app.route('/') def hello_world(): a = 3 b = 0 c = a/b return 'Hello,World.' if __name__ == '__main__': app.run()
2.5 PIN 码
开启了 DEBUG 模式后,当程序抛出异常,则可以在网页上对程序进行调试,在网页上报出的任何一行错误信息中都有一个命令行小图标,点击该图标就可以进行调试了。
但是要注意,如果想要在网页上对程序进行调试,那么就需要输入 PIN 码,PIN 码在 Pycharm 中执行程序的时候会显示,复制过去粘贴即可。其中,PIN 码的有效时间是 8 小时,保存在浏览器的 Cookie 中,所以也不用每次抛出异常时都要输入一遍 PIN 码。
PIN 码的作用是为了保证程序更加安全,比如我不想让其他开发者知道我的代码写的是什么,但是实际上并没有什么卵用,因为都是开发者,没必要隐藏自己的代码。这个机制反而更加麻烦了一些。
3. 应用配置文件的两种方式
-
如上所示,在主 app 文件中导入 config 文件后,使用
app.config.from_object(config)
来将配置文件应用到 app 中,如下所示:from flask import Flask import config app = Flask(__name__) app.config.from_object(config)
-
不需要
import config
,直接使用app.config.from_pyfile
来将配置文件应用到 app 中,如下所示:from flask import Flask app = Flask(__name__) app.config.from_pyfile('config.py')
实际上,
from_pyfile
并不局限于 python 文件,也可以是其他类型的文件,如config.txt
。from_pyfile
还有一个被隐藏的默认参数是silent=False
,所以完整格式是app.config.from_pyfile('config.py',silent=False)
。当 silent 被置为 False 时,如果前面输入的文件不存在,则抛出异常;当 silent 被置为 True 时,则会忽略这种错误。
4. URL 传参到视图
参数的作用:可以在相同的 URL 但是指定不同的参数时,来加载不同的数据。
如:http://localhost:8000/article/abc
和 http://localhost:8000/article/def
中,两条 URL 的参数不同,我们可以获取这个参数并渲染后返回客户浏览器
如何传参:如何在 flask 中使用参数?有两种方式实现。
-
第一种,使用 path 的形式,将参数嵌入到路径中
传递参数的语法是:
/<参数名>/
,在对应的视图函数中也要定义相同的参数名。如下代码所示:@app.route('/article/<id>') def article(id): return u'<h1>你请求的参数是:%s<h1>' % id
如果要限制传入参数的数据类型,可以这么定义:
/article/<数据类型:参数名>
,如/<int:id>/
,如下:@app.route('/article/<int:id>') def article(id): return u'<h1>你请求的参数是:%s<h1>' % id
对于这段代码,当浏览器中的 url 是
127.0.0.1:5000/article/1
时能正确返回,当 url 是127.0.0.1:5000/article/abc
时则提示 not found。如果没有指定数据类型,则默认是 str 型。其中,这些类型可以是:
string 默认的数据类型,接受没有斜杠"/"和"\"的任何字符 int 只接受整型 float 只接受浮点型 path 与 string 类似,但是接受斜杠 uuid 只接受 uuid 字符串,其中 uuid 是全球唯一的字符串,一般可以用来作为表的主键 any 可以指定多种路径,示例说明: any 示例: @app.route('/<any(article,user):url_path>/<id>') def detail(url_path,id): return '详情页面,id=%s' % id # 这个方法用于不同的 url 对应相同的视图函数的需求 # 例如:/article/1 和 /user/1 对应同一个视图函数
-
第二种,使用查询的方式,调用 request.args.get('args') 方法,获取问号传递的参数
from flask import request …… 略 …… @app.route('/keyword/') def keyword_args(): wd = request.args.get('wd') return '?后面的参数是:%s' % wd
这样,就可以获取 url 后面以问号的形式跟上的关键字了,如:
/keyword/?wd=python
。如果需要传递多个参数,那么问号后面可以跟&
符号进行连接,如:/keyword/wd=python&name=myyd
那么在视图函数中再加上
name = request.args.get('name')
即可。
5. URL 反转
正转指的是:在获取到用户输入的 URL 后将该 URL 映射到对应的视图函数中,让对应的视图函数去处理该用户的请求;
反转指的是:与正转相反,通过视图函数来查找对应的 URL。
反转的作用是:1. 在页面重定向的时候会使用 URL 反转;2. 在模板中会使用 URL 反转
实现反转的方法:
-
在 flask 框架中导入
url_for
模块 -
用
url_for('FunctionName',key=value)
实现反转第一个参数
FunctionName
是视图函数的函数名。第二个参数
key=value
是传递给视图函数的参数,即@app.route('/article/<id>')
中的 id。如果这个参数在@app.route()
时已被定义,则该参数则以 path 的形式出现在 url 中;如果这个参数在@app.route()
时未被定义,则该参数通过问号?
跟在 url 的后面传递给后台。例如:
@app.route('/article/<id>') def article(id): return 'article' print(url_for('article',id='123',name='myyd')) # /article/123?name=myyd
-
源代码如下
from flask import Flask,url_for import config app = Flask(__name__) app.config.from_object(config) @app.route('/') def hello_world(): print(url_for('article',id='123')) # /article/123 print(url_for('article',id='123',name='myyd')) # /article/123?name=myyd print url_for('my_list') # /list/ return 'Hello,World.' @app.route('/article/<id>/') def article(id): return u'<h1>你请求的参数是:%s<h1>' % id @app.route('/list/') def my_list(): return '<h1>list</h1>' if __name__ == '__main__': app.run()
为什么需要 url_for ?
-
将来如果修改了 URL,但是没有修改 URL 对应的函数名,就不用到处去替换 URL 了。
-
url_for
会自动地处理 URL 中的特殊字符(如"/"),不需要手动处理。如:
print(url_for('article',next='/'))
会输出 /article/%2F,因为 "/" 在 URL 中有歧义。
6. 自定义 URL 转换器
客户提出两个需求
6.1 需求一:
在一个 URL 中,含有手机号码的变量,必须限定这个变量的字符串格式满足手机号码的格式。
为了实现这个需求,需要做如下几个步骤:
-
从 werkzeug.routing 导入 BaseConverter:
from werzeug.routing import BaseConverter
-
实现一个类,继承自
BaseConverter
,重写正则表达式查看 BaseConverter 中的底层定义:光标停留在 BaseConverter 上,按 Ctrl+B 组合键进入 BaseConverter 的定义代码,参考
PathConverter()
类的定义,依葫芦画瓢自定义一个手机号码格式的转换器。# PathConverter(葫芦) class PathConverter(BaseConverter): regex = '[^/].*?' # TelephoneConverter(瓢) class TelephoneConverter(BaseConverter): regex = '1[34578]\d{9}' # \d 代表数字,{9} 代表重复 9 次
-
添加自定义转换器到默认转换字典
Flask 中原来就定义的转换器有:string、int、float、path、uuid、any 等,如下所示:
DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
所以我们需要将自定义的转换器也添加进来,有两种方式:第一种就是按照格式在 DEFAULT_CONVERTERS 中添加;第二种方式是借助
url_map
来实现,如下:app.url_map.converters['tel'] = TelephoneConverter
-
限制视图函数中的参数类型
@app.route('/telephone/<tel:number>') # 限制类型 def telephone(number): return 'Number is %s' % number
6.2 需求二:
在一个 URL 中,包含 a+b
的 path,需要将其拆分为 a
和 b
,后台再进行处理。
传统方法如下:
在视图函数中对传进来的参数进行拆分:
@app.route('/list/<id>')
def my_list(id):
id_list = id.split('+')
return 'the id list is %s' % id_list
# 若浏览器地址栏的 url 写的是:127.0.0.1:5000/list/a+b
# 则返回页面的信息是:the id list is ['a','b'],而不是:the id list is a+b
对于传进来的参数,在调用视图函数时都要进行一次拆分,会降低执行效率,能否在传到视图函数之前就已经对 a+b
格式的参数进行拆分?
借助 URL 转换器:
在 URL 转换器中定义了一个两个函数:to_python()
和 to_url()
。
-
前者的作用是:将 URL 中的参数经过解析后传递给视图函数,即在
to_python
函数体中解析 URL 中的参数,然后 return 到视图函数中作为参数,如下:class ListConverter(BaseConverter): def to_python(self,value): # value 就是 /list/ 后面本来要跟的 a+b return 'value.split('+')' # 然后以 ['a','b'] 的格式返回给视图函数 my_list()
然后要记得添加到默认的转换器字典中:
app.url_map.converters('List') = ListConverter
,最后限制类型:@app.route('/list/<List:id>')
。这样,视图函数就不用再进行拆分处理,而是能直接拿到最终想要的内容。
-
后者的作用是:在使用
url_for
时将参数还原成 url,如下:class ListConverter(BaseConverter): def to_url(self,value): # value 就是从视图函数中传过来的参数 ['a','b'] return "+".join(value) # 然后以 a+b 的格式返回给 url_for 的参数 @app.route('/') def index(): return url_for('my_list',id=['a','b'])
注意:这两个函数是成对出现的,一般情况下,要解析也要还原。
源代码如下
from flask import Flask,url_for
from werkzeug.routing import BaseConverter
class ListConverter(BaseConverter):
def to_python(self, value):
return value.split('+')
def to_url(self, value):
return "+".join(value)
app = Flask(__name__)
app.url_map.converters['List'] = ListConverter
@app.route('/')
def hello_world():
return url_for('my_list',id=['a','b'])
@app.route('/list/<List:id>/')
def my_list(id):
return 'The List is %s' % id
if __name__ == '__main__':
app.run(debug=True)
7. Flask 开发的小细节
-
想要让自己写的网站,在局域网中被其他网络设备访问,则必须指定 host,即
app.run(host='0.0.0.0')
,同时还需要注意以下 3 点: -
指定端口号:Flask 的默认端口号是 5000,如果想要改成 8000 端口,应将
app.run
写成app.run(port=8000)
。 -
URL 唯一
在定义 URL 的时候,一定要记得在最后加一个
/
,这是为了更好的用户体验。而且搜索引擎会将加了斜杠的和不加斜杠的 URL 视为两个不同的 URL,但实际上加不加斜杠都是同一个 URL,这样就会给搜索引擎造成一个误解。 -
指明 GET 或者 POST 请求
参考我的另一片博客:Flask基础,第 10 点:GET 、POST 和 钩子函数。
8. 页面跳转和重定向
重定向指的是浏览器自动地从一个页面跳转到另一个页面,可以用在一些需求上,如:用户访问某些需要登录的页面时,如果用户没登录,则可以让他重定向到登录页面。重定向分为两种:永久性重定向和暂时性重定向。
8.1 永久性重定向
状态码是 301,多用于旧网址被废弃了要转到一个新网址上,如京东网站,当你访问 www.jingdong.com
时会被重定向到 www.jd.com
,因为旧网址已经被弃用了,所以这种情况应该使用永久重定向。
8.2 暂时性重定向
状态码是 302,表示页面的暂时性跳转。比如访问一个需要权限的网页,如果当前用户没有登录,应该重定向到登录页面。这种情况下应该使用暂时性重定向。
8.3 重定向的实现
在 Flask 中,重定向是通过 flask.redirect(location,code=302)
这个函数来实现的。其中:locate
代表重定向之后的 URL,应该配合之前将的 url_for()
来使用,code
表示使用的状态码,默认是 302。
看一个具体的例子:
from flask import Flask,redirect,url_for,request
app = Flask(__name__)
app.debug = True
@app.route('/')
def index():
return 'hello world'
@app.route('/login/')
def login():
return '请登录!'
@app.route('/profile/')
def profile():
name = request.args.get('name')
if name:
return '个人详情页面'
else:
return redirect(url_for('login'))
if __name__ == '__main__':
app.run(host='0.0.0.0')
9. 视图函数 response 返回值详解
Flask 的视图函数默认只可以返回字符串、元组合 Response 对象,Flask 转换响应对象的逻辑是:
-
如果返回的是一个合法的相应对象,则直接返回;
-
如果返回的是一个字符串:
则 Flask 会构建一个
werkzeug.wrappers.response
对象,response
对象会将该字符串作为主体,状态码为 200,MIME 类型为 text/html,即构建成为:response('string',status=200,mimetype='text/html')
然后返回 response 对象。我们在视图函数中使用 return 返回内容,实际上就是 Flask 帮我们完成的这个动作。所以视图函数中的
return 'Hello World!'
等价于return response('Hello World!')
-
如果返回的是一个元组:
元组的数据类型是
(response,status,headers)
,其中,status 会覆盖默认的 200 状态码,header 可以是一个列表或者字典,作为额外的消息头。 -
如果非以上内容:
Flask 会去底层调用
Response.force_type(rv,request,environ)
返回一个 response 对象。这样,视图函数在调用 return 的时候就可以正确返回了。如下例:from flask import Flask,Response,jsonify app = Flask(__name__) app.debug = True class JSONResponse(Response): @classmethod def force_type(cls, response, environ=None): ''' 这个方法只有在视图函数返回非字符串、非元组、非response对象的时候被调用 response:视图函数的返回值 ''' print(response) print(type(response)) # jsonify:除了将字典转换成json对象,还将该对象包装成了一个Response对象 if isinstance(response,dict): return super(JSONResponse,cls).force_type(response,environ) app.response_class = JSONResponse @app.route('/dict/') def dict(): return {'username':'myyd','age':18} if __name__ == '__main__': app.run(host='0.0.0.0')
实现响应自定义内容,需要完成 3 个步骤:
- 继承自
Response
类 - 实现方法
force_type(cls,response,environ=None)
- 指定
app.response_class
为你自定义的Response
对象
- 继承自