Flask-论坛开发-1-基础知识

时间:2020-12-24 23:43:15

对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 在配置文件中使用

  1. 在相同目录下,新建一个 python 文件,建议命名为 config,并在里面指定该程序配置了 DEBUG 模式,即 config.py 文件的内容如下:

     config.py
    
     # encoding:utf-8
    DEBUG = True
    # SECRET_KEY
    # SQLALCHEMY_DB # 数据库的一些参数配置
  2. 然后在主 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. 应用配置文件的两种方式

  1. 如上所示,在主 app 文件中导入 config 文件后,使用 app.config.from_object(config) 来将配置文件应用到 app 中,如下所示:

     from flask import Flask
    import config
    app = Flask(__name__)
    app.config.from_object(config)
  2. 不需要 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/abchttp://localhost:8000/article/def 中,两条 URL 的参数不同,我们可以获取这个参数并渲染后返回客户浏览器

如何传参:如何在 flask 中使用参数?有两种方式实现。

  1. 第一种,使用 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 对应同一个视图函数
  2. 第二种,使用查询的方式,调用 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 反转

实现反转的方法:

  1. 在 flask 框架中导入 url_for 模块

  2. 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
  3. 源代码如下

     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 ?

  1. 将来如果修改了 URL,但是没有修改 URL 对应的函数名,就不用到处去替换 URL 了。

  2. url_for 会自动地处理 URL 中的特殊字符(如"/"),不需要手动处理。

    如:print(url_for('article',next='/')) 会输出 /article/%2F,因为 "/" 在 URL 中有歧义。

6. 自定义 URL 转换器

客户提出两个需求

6.1 需求一:

在一个 URL 中,含有手机号码的变量,必须限定这个变量的字符串格式满足手机号码的格式。

为了实现这个需求,需要做如下几个步骤:

  1. 从 werkzeug.routing 导入 BaseConverter:from werzeug.routing import BaseConverter

  2. 实现一个类,继承自 BaseConverter,重写正则表达式

    查看 BaseConverter 中的底层定义:光标停留在 BaseConverter 上,按 Ctrl+B 组合键进入 BaseConverter 的定义代码,参考 PathConverter() 类的定义,依葫芦画瓢自定义一个手机号码格式的转换器。

     # PathConverter(葫芦)
    class PathConverter(BaseConverter):
    regex = '[^/].*?' # TelephoneConverter(瓢)
    class TelephoneConverter(BaseConverter):
    regex = '1[34578]\d{9}' # \d 代表数字,{9} 代表重复 9 次
  3. 添加自定义转换器到默认转换字典

    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
  4. 限制视图函数中的参数类型

     @app.route('/telephone/<tel:number>')	# 限制类型
    def telephone(number):
    return 'Number is %s' % number

6.2 需求二:

在一个 URL 中,包含 a+b 的 path,需要将其拆分为 ab,后台再进行处理。

传统方法如下:

在视图函数中对传进来的参数进行拆分:

@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()

  1. 前者的作用是:将 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>')

    这样,视图函数就不用再进行拆分处理,而是能直接拿到最终想要的内容。

  2. 后者的作用是:在使用 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 开发的小细节

  1. 想要让自己写的网站,在局域网中被其他网络设备访问,则必须指定 host,即 app.run(host='0.0.0.0'),同时还需要注意以下 3 点:

  2. 指定端口号:Flask 的默认端口号是 5000,如果想要改成 8000 端口,应将 app.run 写成 app.run(port=8000)

  3. URL 唯一

    在定义 URL 的时候,一定要记得在最后加一个 /,这是为了更好的用户体验。而且搜索引擎会将加了斜杠的和不加斜杠的 URL 视为两个不同的 URL,但实际上加不加斜杠都是同一个 URL,这样就会给搜索引擎造成一个误解。

  4. 指明 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 转换响应对象的逻辑是:

  1. 如果返回的是一个合法的相应对象,则直接返回;

  2. 如果返回的是一个字符串:

    则 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!')

  3. 如果返回的是一个元组:

    元组的数据类型是(response,status,headers),其中,status 会覆盖默认的 200 状态码,header 可以是一个列表或者字典,作为额外的消息头。

  4. 如果非以上内容:

    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 个步骤:

    1. 继承自 Response
    2. 实现方法 force_type(cls,response,environ=None)
    3. 指定 app.response_class 为你自定义的 Response 对象